Transformer
This commit is contained in:
@@ -99,7 +99,7 @@
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\src\Checkpoint;$(ProjectDir)..\..\src\SearchArchive;$(ProjectDir)..\..\src\OnlineSurrogate;$(ProjectDir)..\..\src\TrainingArchive;$(ProjectDir)..\..\src\CudaSearch;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\src\Checkpoint;$(ProjectDir)..\..\src\SearchArchive;$(ProjectDir)..\..\src\OnlineSurrogate;$(ProjectDir)..\..\src\TrainingArchive;$(ProjectDir)..\..\src\TransformerRanker;$(ProjectDir)..\..\src\CudaSearch;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@@ -117,7 +117,7 @@
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\src\Checkpoint;$(ProjectDir)..\..\src\SearchArchive;$(ProjectDir)..\..\src\OnlineSurrogate;$(ProjectDir)..\..\src\TrainingArchive;$(ProjectDir)..\..\src\CudaSearch;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\src\Checkpoint;$(ProjectDir)..\..\src\SearchArchive;$(ProjectDir)..\..\src\OnlineSurrogate;$(ProjectDir)..\..\src\TrainingArchive;$(ProjectDir)..\..\src\TransformerRanker;$(ProjectDir)..\..\src\CudaSearch;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||
<EnableEnhancedInstructionSet>AdvancedVectorExtensions2</EnableEnhancedInstructionSet>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
@@ -139,7 +139,7 @@
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\src\Checkpoint;$(ProjectDir)..\..\src\SearchArchive;$(ProjectDir)..\..\src\OnlineSurrogate;$(ProjectDir)..\..\src\TrainingArchive;$(ProjectDir)..\..\src\CudaSearch;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\src\Checkpoint;$(ProjectDir)..\..\src\SearchArchive;$(ProjectDir)..\..\src\OnlineSurrogate;$(ProjectDir)..\..\src\TrainingArchive;$(ProjectDir)..\..\src\TransformerRanker;$(ProjectDir)..\..\src\CudaSearch;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@@ -157,7 +157,7 @@
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\src\Checkpoint;$(ProjectDir)..\..\src\SearchArchive;$(ProjectDir)..\..\src\OnlineSurrogate;$(ProjectDir)..\..\src\TrainingArchive;$(ProjectDir)..\..\src\CudaSearch;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\src\Checkpoint;$(ProjectDir)..\..\src\SearchArchive;$(ProjectDir)..\..\src\OnlineSurrogate;$(ProjectDir)..\..\src\TrainingArchive;$(ProjectDir)..\..\src\TransformerRanker;$(ProjectDir)..\..\src\CudaSearch;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||
<EnableEnhancedInstructionSet>AdvancedVectorExtensions2</EnableEnhancedInstructionSet>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
@@ -179,6 +179,7 @@
|
||||
<ClCompile Include="..\..\src\SearchArchive\SearchArchive.cpp" />
|
||||
<ClCompile Include="..\..\src\OnlineSurrogate\OnlineSurrogate.cpp" />
|
||||
<ClCompile Include="..\..\src\TrainingArchive\TrainingArchive.cpp" />
|
||||
<ClCompile Include="..\..\src\TransformerRanker\TransformerRanker.cpp" />
|
||||
<ClCompile Include="..\..\src\NeighborlyCore\util.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(CudaToolkitAvailable)'!='true'">
|
||||
@@ -199,6 +200,7 @@
|
||||
<ClInclude Include="..\..\src\SearchArchive\SearchArchive.h" />
|
||||
<ClInclude Include="..\..\src\OnlineSurrogate\OnlineSurrogate.h" />
|
||||
<ClInclude Include="..\..\src\TrainingArchive\TrainingArchive.h" />
|
||||
<ClInclude Include="..\..\src\TransformerRanker\TransformerRanker.h" />
|
||||
<ClInclude Include="..\..\src\CudaSearch\cuda_search.h" />
|
||||
<ClInclude Include="..\..\src\NeighborlyCore\util.h" />
|
||||
<ClInclude Include="..\..\src\NeighborlyCore\wide_real.h" />
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
<ClCompile Include="..\..\src\TrainingArchive\TrainingArchive.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\TransformerRanker\TransformerRanker.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\CudaSearch\cuda_search_stub.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
@@ -59,6 +62,9 @@
|
||||
<ClInclude Include="..\..\src\TrainingArchive\TrainingArchive.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\TransformerRanker\TransformerRanker.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\CudaSearch\cuda_search.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
||||
+490
-50
@@ -6,6 +6,7 @@
|
||||
#include "SearchArchive.h"
|
||||
#include "OnlineSurrogate.h"
|
||||
#include "TrainingArchive.h"
|
||||
#include "TransformerRanker.h"
|
||||
#include "cuda_search.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
@@ -57,6 +58,7 @@ constexpr std::uint32_t MIN_COMPATIBLE_ARCHIVE_OBJECTIVE_VERSION = 3;
|
||||
constexpr int CUDA_SESSION_CACHE_LIMIT = NUM_TOPOLOGIES;
|
||||
constexpr std::uint64_t TRAINING_CACHE_LIMIT_BYTES = 200000000000ULL;
|
||||
constexpr std::uint64_t NEURAL_MODEL_BUDGET_PER_TOPOLOGY = 4ULL * 1024ULL * 1024ULL;
|
||||
constexpr std::uint64_t TRANSFORMER_MODEL_BUDGET = 8ULL * 1024ULL * 1024ULL;
|
||||
constexpr std::uint32_t TRAINING_ARCHIVE_SCHEMA_VERSION = 3;
|
||||
|
||||
struct StudyOptions {
|
||||
@@ -2238,6 +2240,8 @@ struct GlobalMetrics {
|
||||
|
||||
double g_global_degeneracy_weight = 0.01;
|
||||
std::string g_search_device_id = "CPU";
|
||||
bool g_transformer_active = false;
|
||||
std::string g_transformer_model_id = "none";
|
||||
|
||||
std::uint64_t topology_fingerprint() {
|
||||
std::uint64_t hash = 1469598103934665603ULL;
|
||||
@@ -2343,6 +2347,10 @@ struct GlobalTopologyState {
|
||||
std::uint64_t neural_samples_added = 0;
|
||||
std::uint64_t neural_training_steps = 0;
|
||||
std::uint64_t neural_seed_count = 0;
|
||||
std::uint64_t transformer_candidates_scored = 0;
|
||||
std::uint64_t transformer_candidates_selected = 0;
|
||||
std::uint64_t transformer_control_seeds = 0;
|
||||
std::uint64_t transformer_invalid_predictions = 0;
|
||||
std::uint64_t training_verified_records = 0;
|
||||
std::uint64_t training_fp32_records = 0;
|
||||
std::uint64_t training_seed_records = 0;
|
||||
@@ -2897,7 +2905,8 @@ szilassi::training::CommonContext make_training_context(
|
||||
context.topology_fingerprint = topology_fingerprint();
|
||||
context.algorithm_fingerprint = training_hash_text(
|
||||
options.use_cuda
|
||||
? "hybrid-quality-diversity-neural-v3-cuda-fp32"
|
||||
? "hybrid-quality-diversity-neural-v3-transformer-v1-cuda-fp32:" +
|
||||
g_transformer_model_id
|
||||
: "cpu-parallel-simulated-annealing-double");
|
||||
static const std::uint64_t build_fingerprint = training_hash_text(
|
||||
std::string(__DATE__) + " " + __TIME__);
|
||||
@@ -3036,14 +3045,44 @@ std::filesystem::path neural_model_relative_path(int topology) {
|
||||
"_s" + std::to_string(GLOBAL_NEURAL_SCHEMA_VERSION) + ".szonn");
|
||||
}
|
||||
|
||||
std::filesystem::path transformer_model_relative_path() {
|
||||
return std::filesystem::path("neural") / "transformer" / "current.sztf";
|
||||
}
|
||||
|
||||
std::string transformer_digest_hex(
|
||||
const std::array<std::uint8_t, szilassi::transformer::kTrainingDigestBytes>& digest
|
||||
) {
|
||||
static constexpr char digits[] = "0123456789abcdef";
|
||||
std::string result;
|
||||
result.reserve(digest.size() * 2);
|
||||
for (const std::uint8_t value : digest) {
|
||||
result.push_back(digits[value >> 4]);
|
||||
result.push_back(digits[value & 0x0fU]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string transformer_model_id(
|
||||
const szilassi::transformer::Metadata& metadata
|
||||
) {
|
||||
std::ostringstream out;
|
||||
out << transformer_digest_hex(metadata.training_digest)
|
||||
<< "-s" << metadata.training_seed
|
||||
<< "-p" << std::hex << std::setw(8) << std::setfill('0')
|
||||
<< metadata.payload_crc32;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
bool scan_neural_cache_bytes(
|
||||
const std::filesystem::path& root,
|
||||
std::uint64_t& bytes,
|
||||
std::array<std::uint64_t, NUM_TOPOLOGIES>& final_snapshot_bytes,
|
||||
std::uint64_t& transformer_snapshot_bytes,
|
||||
std::string& error
|
||||
) {
|
||||
bytes = 0;
|
||||
final_snapshot_bytes.fill(0);
|
||||
transformer_snapshot_bytes = 0;
|
||||
std::error_code exists_error;
|
||||
const bool exists = std::filesystem::exists(root, exists_error);
|
||||
if (exists_error) {
|
||||
@@ -3088,6 +3127,9 @@ bool scan_neural_cache_bytes(
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (relative == transformer_model_relative_path()) {
|
||||
transformer_snapshot_bytes = size;
|
||||
}
|
||||
}
|
||||
iterator.increment(iterator_error);
|
||||
}
|
||||
@@ -4328,6 +4370,9 @@ struct HybridSeedPool {
|
||||
std::vector<GlobalMetrics> injected_metrics;
|
||||
std::vector<NeuralPrediction> behavior_predictions;
|
||||
std::vector<std::uint8_t> neural_guided;
|
||||
// 0 = ordinary/MLP-only, 1 = selected by transformer rank, 2 =
|
||||
// score-independent control retained beside transformer-ranked seeds.
|
||||
std::vector<std::uint8_t> transformer_selection_mode;
|
||||
std::vector<int> plane_actions;
|
||||
std::vector<int> move_actions;
|
||||
std::vector<int> scale_actions;
|
||||
@@ -4340,6 +4385,10 @@ struct HybridSeedPool {
|
||||
move_sampling_probabilities;
|
||||
std::vector<std::array<float, szilassi::surrogate::kScaleCount>>
|
||||
scale_sampling_probabilities;
|
||||
std::uint64_t transformer_candidates_scored = 0;
|
||||
std::uint64_t transformer_candidates_selected = 0;
|
||||
std::uint64_t transformer_control_seeds = 0;
|
||||
std::uint64_t transformer_invalid_predictions = 0;
|
||||
};
|
||||
|
||||
NeuralInput make_neural_input(
|
||||
@@ -4466,7 +4515,9 @@ void apply_neural_repair_move(
|
||||
HybridSeedPool make_hybrid_seed_pool(
|
||||
GlobalTopologyState& topology_state,
|
||||
std::uint64_t seed,
|
||||
int maximum_states
|
||||
int maximum_states,
|
||||
const szilassi::transformer::TransformerRanker* transformer_ranker = nullptr,
|
||||
const szilassi::transformer::SearchBudget& transformer_budget = {}
|
||||
) {
|
||||
HybridSeedPool pool;
|
||||
if (maximum_states <= 0) {
|
||||
@@ -4479,6 +4530,7 @@ HybridSeedPool make_hybrid_seed_pool(
|
||||
pool.injected_metrics.reserve(static_cast<std::size_t>(maximum_states));
|
||||
pool.behavior_predictions.reserve(static_cast<std::size_t>(maximum_states));
|
||||
pool.neural_guided.reserve(static_cast<std::size_t>(maximum_states));
|
||||
pool.transformer_selection_mode.reserve(static_cast<std::size_t>(maximum_states));
|
||||
pool.plane_actions.reserve(static_cast<std::size_t>(maximum_states));
|
||||
pool.move_actions.reserve(static_cast<std::size_t>(maximum_states));
|
||||
pool.scale_actions.reserve(static_cast<std::size_t>(maximum_states));
|
||||
@@ -4489,7 +4541,9 @@ HybridSeedPool make_hybrid_seed_pool(
|
||||
pool.move_sampling_probabilities.reserve(static_cast<std::size_t>(maximum_states));
|
||||
pool.scale_sampling_probabilities.reserve(static_cast<std::size_t>(maximum_states));
|
||||
|
||||
auto append = [&](const VectorXd& injected_x,
|
||||
auto append_to = [&](HybridSeedPool& destination,
|
||||
int destination_limit,
|
||||
const VectorXd& injected_x,
|
||||
const GlobalMetrics& injected_metrics,
|
||||
const VectorXd& training_x,
|
||||
const NeuralInput& training_input,
|
||||
@@ -4507,8 +4561,9 @@ HybridSeedPool make_hybrid_seed_pool(
|
||||
const std::array<float, szilassi::surrogate::kMoveCount>&
|
||||
move_sampling_probabilities,
|
||||
const std::array<float, szilassi::surrogate::kScaleCount>&
|
||||
scale_sampling_probabilities) {
|
||||
if (static_cast<int>(pool.states.size()) >= maximum_states ||
|
||||
scale_sampling_probabilities,
|
||||
std::uint8_t transformer_selection_mode) {
|
||||
if (static_cast<int>(destination.states.size()) >= destination_limit ||
|
||||
injected_x.size() != GLOBAL_PLANE_VALUE_COUNT ||
|
||||
training_x.size() != GLOBAL_PLANE_VALUE_COUNT ||
|
||||
!injected_x.allFinite() || !std::isfinite(injected_metrics.energy) ||
|
||||
@@ -4523,22 +4578,23 @@ HybridSeedPool make_hybrid_seed_pool(
|
||||
training_value.values[static_cast<std::size_t>(component)] =
|
||||
static_cast<float>(training_x[component]);
|
||||
}
|
||||
pool.states.push_back(value);
|
||||
pool.training_states.push_back(training_value);
|
||||
pool.inputs.push_back(training_input);
|
||||
pool.metrics.push_back(training_metrics);
|
||||
pool.injected_metrics.push_back(injected_metrics);
|
||||
pool.behavior_predictions.push_back(behavior_prediction);
|
||||
pool.neural_guided.push_back(guided ? 1u : 0u);
|
||||
pool.plane_actions.push_back(plane_action);
|
||||
pool.move_actions.push_back(move_action);
|
||||
pool.scale_actions.push_back(scale_action);
|
||||
pool.plane_propensities.push_back(static_cast<float>(plane_propensity));
|
||||
pool.move_propensities.push_back(static_cast<float>(move_propensity));
|
||||
pool.scale_propensities.push_back(static_cast<float>(scale_propensity));
|
||||
pool.plane_sampling_probabilities.push_back(plane_sampling_probabilities);
|
||||
pool.move_sampling_probabilities.push_back(move_sampling_probabilities);
|
||||
pool.scale_sampling_probabilities.push_back(scale_sampling_probabilities);
|
||||
destination.states.push_back(value);
|
||||
destination.training_states.push_back(training_value);
|
||||
destination.inputs.push_back(training_input);
|
||||
destination.metrics.push_back(training_metrics);
|
||||
destination.injected_metrics.push_back(injected_metrics);
|
||||
destination.behavior_predictions.push_back(behavior_prediction);
|
||||
destination.neural_guided.push_back(guided ? 1u : 0u);
|
||||
destination.transformer_selection_mode.push_back(transformer_selection_mode);
|
||||
destination.plane_actions.push_back(plane_action);
|
||||
destination.move_actions.push_back(move_action);
|
||||
destination.scale_actions.push_back(scale_action);
|
||||
destination.plane_propensities.push_back(static_cast<float>(plane_propensity));
|
||||
destination.move_propensities.push_back(static_cast<float>(move_propensity));
|
||||
destination.scale_propensities.push_back(static_cast<float>(scale_propensity));
|
||||
destination.plane_sampling_probabilities.push_back(plane_sampling_probabilities);
|
||||
destination.move_sampling_probabilities.push_back(move_sampling_probabilities);
|
||||
destination.scale_sampling_probabilities.push_back(scale_sampling_probabilities);
|
||||
};
|
||||
|
||||
// Base seeds do not sample an action. A zero distribution records that
|
||||
@@ -4565,7 +4621,9 @@ HybridSeedPool make_hybrid_seed_pool(
|
||||
x[component] = static_cast<double>(value.values[static_cast<std::size_t>(component)]);
|
||||
}
|
||||
const GlobalMetrics metrics = evaluate_global_state(x, true, scratch);
|
||||
append(
|
||||
append_to(
|
||||
pool,
|
||||
maximum_states,
|
||||
x,
|
||||
metrics,
|
||||
x,
|
||||
@@ -4581,7 +4639,8 @@ HybridSeedPool make_hybrid_seed_pool(
|
||||
1.0,
|
||||
no_plane_sampling_distribution,
|
||||
no_move_sampling_distribution,
|
||||
no_scale_sampling_distribution);
|
||||
no_scale_sampling_distribution,
|
||||
0);
|
||||
}
|
||||
|
||||
if (guided_target == 0) {
|
||||
@@ -4593,6 +4652,9 @@ HybridSeedPool make_hybrid_seed_pool(
|
||||
GlobalMetrics metrics;
|
||||
NeuralInput input{};
|
||||
NeuralPrediction prediction{};
|
||||
NeuralPrediction control_prediction{};
|
||||
bool learned_prediction = false;
|
||||
bool control_learned_prediction = false;
|
||||
double acquisition = 0.0;
|
||||
};
|
||||
std::vector<Anchor> anchors;
|
||||
@@ -4619,27 +4681,117 @@ HybridSeedPool make_hybrid_seed_pool(
|
||||
0.0});
|
||||
}
|
||||
|
||||
const bool model_ready = topology_state.neural != nullptr &&
|
||||
const bool online_model_ready = topology_state.neural != nullptr &&
|
||||
topology_state.neural->replay_size() >= 128;
|
||||
const bool transformer_ready = transformer_ranker != nullptr &&
|
||||
transformer_ranker->ready();
|
||||
auto default_prediction = []() {
|
||||
NeuralPrediction prediction;
|
||||
prediction.plane_probabilities.fill(
|
||||
1.0f / static_cast<float>(cuda_search::kPlaneCount));
|
||||
prediction.move_probabilities.fill(
|
||||
1.0f / static_cast<float>(szilassi::surrogate::kMoveCount));
|
||||
prediction.scale_probabilities = {{0.25f, 0.55f, 0.20f}};
|
||||
// This is a valid neutral policy/value fallback, not a failed model
|
||||
// inference. Keeping it finite also prevents transformer value heads
|
||||
// trained on injected proposals from leaking into anchor telemetry.
|
||||
prediction.finite = true;
|
||||
return prediction;
|
||||
};
|
||||
auto transformer_as_neural = [](const szilassi::transformer::Prediction& source) {
|
||||
NeuralPrediction result;
|
||||
result.improvement_logit = source.improvement_logit;
|
||||
result.improvement_probability = source.improvement_probability;
|
||||
result.expected_defect_gain = source.expected_defect_gain;
|
||||
result.uncertainty = source.improvement_probability_variance;
|
||||
result.plane_probabilities = source.plane_probabilities;
|
||||
result.move_probabilities = source.move_probabilities;
|
||||
result.scale_probabilities = source.scale_probabilities;
|
||||
result.finite = source.finite;
|
||||
return result;
|
||||
};
|
||||
auto blend_predictions = [](const NeuralPrediction& local,
|
||||
const NeuralPrediction& global) {
|
||||
if (!global.finite) return local;
|
||||
if (!local.finite) return global;
|
||||
NeuralPrediction blended;
|
||||
constexpr float global_weight = 0.65f;
|
||||
constexpr float local_weight = 1.0f - global_weight;
|
||||
// Transformer value heads are trained on injected proposals. Anchor
|
||||
// inference is used only for its policy heads; keep the local online
|
||||
// value estimate instead of evaluating the global value head OOD.
|
||||
blended.improvement_logit = local.improvement_logit;
|
||||
blended.improvement_probability = local.improvement_probability;
|
||||
blended.expected_defect_gain = local.expected_defect_gain;
|
||||
blended.uncertainty = local.uncertainty;
|
||||
for (std::size_t i = 0; i < blended.plane_probabilities.size(); ++i) {
|
||||
blended.plane_probabilities[i] =
|
||||
local_weight * local.plane_probabilities[i] +
|
||||
global_weight * global.plane_probabilities[i];
|
||||
}
|
||||
for (std::size_t i = 0; i < blended.move_probabilities.size(); ++i) {
|
||||
blended.move_probabilities[i] =
|
||||
local_weight * local.move_probabilities[i] +
|
||||
global_weight * global.move_probabilities[i];
|
||||
}
|
||||
for (std::size_t i = 0; i < blended.scale_probabilities.size(); ++i) {
|
||||
blended.scale_probabilities[i] =
|
||||
local_weight * local.scale_probabilities[i] +
|
||||
global_weight * global.scale_probabilities[i];
|
||||
}
|
||||
blended.finite = true;
|
||||
return blended;
|
||||
};
|
||||
auto make_transformer_features = [&](const VectorXd& x,
|
||||
const GlobalMetrics& metrics,
|
||||
bool guided,
|
||||
szilassi::transformer::Features& features) {
|
||||
szilassi::transformer::FeatureInput input;
|
||||
input.state = training_plane_state(x);
|
||||
input.metrics = training_metrics(metrics);
|
||||
input.budget = transformer_budget;
|
||||
input.topology = static_cast<std::uint32_t>(topology_state.topology);
|
||||
input.guided_context = guided;
|
||||
return szilassi::transformer::make_features(input, features, nullptr);
|
||||
};
|
||||
std::uint64_t transformer_anchor_invalid_predictions = 0;
|
||||
for (Anchor& anchor : anchors) {
|
||||
if (model_ready) {
|
||||
if (online_model_ready) {
|
||||
anchor.prediction = topology_state.neural->predict(anchor.input);
|
||||
}
|
||||
if (!anchor.prediction.finite) {
|
||||
anchor.prediction.plane_probabilities.fill(
|
||||
1.0f / static_cast<float>(cuda_search::kPlaneCount));
|
||||
anchor.prediction.move_probabilities.fill(
|
||||
1.0f / static_cast<float>(szilassi::surrogate::kMoveCount));
|
||||
anchor.prediction.scale_probabilities = {{0.25f, 0.55f, 0.20f}};
|
||||
anchor.prediction = default_prediction();
|
||||
}
|
||||
anchor.control_prediction = anchor.prediction;
|
||||
anchor.control_learned_prediction = online_model_ready &&
|
||||
anchor.prediction.finite;
|
||||
const double uncertainty = std::sqrt(std::max(
|
||||
0.0,
|
||||
static_cast<double>(anchor.prediction.uncertainty)));
|
||||
anchor.acquisition = model_ready
|
||||
anchor.acquisition = online_model_ready
|
||||
? static_cast<double>(anchor.prediction.expected_defect_gain) +
|
||||
0.75 * uncertainty +
|
||||
0.10 * static_cast<double>(anchor.prediction.improvement_probability)
|
||||
: 0.0;
|
||||
if (transformer_ready) {
|
||||
szilassi::transformer::Features features;
|
||||
if (make_transformer_features(anchor.x, anchor.metrics, true, features)) {
|
||||
const NeuralPrediction global_prediction = transformer_as_neural(
|
||||
transformer_ranker->predict(features));
|
||||
if (global_prediction.finite) {
|
||||
anchor.prediction = blend_predictions(
|
||||
anchor.prediction,
|
||||
global_prediction);
|
||||
anchor.learned_prediction = true;
|
||||
} else {
|
||||
++transformer_anchor_invalid_predictions;
|
||||
}
|
||||
} else {
|
||||
++transformer_anchor_invalid_predictions;
|
||||
}
|
||||
}
|
||||
anchor.learned_prediction = anchor.learned_prediction ||
|
||||
(online_model_ready && anchor.prediction.finite);
|
||||
}
|
||||
std::stable_sort(anchors.begin(), anchors.end(), [](const Anchor& left, const Anchor& right) {
|
||||
if (left.acquisition != right.acquisition) {
|
||||
@@ -4650,9 +4802,18 @@ HybridSeedPool make_hybrid_seed_pool(
|
||||
|
||||
RNG rng(static_cast<RNG::result_type>(seed));
|
||||
std::normal_distribution<double> normal(0.0, 1.0);
|
||||
HybridSeedPool proposals;
|
||||
proposals.transformer_invalid_predictions =
|
||||
transformer_anchor_invalid_predictions;
|
||||
const int proposal_target = transformer_ready
|
||||
? std::max(guided_target, guided_target * 4)
|
||||
: guided_target;
|
||||
const int control_target = transformer_ready
|
||||
? std::min(4, guided_target)
|
||||
: 0;
|
||||
int attempts = 0;
|
||||
while (std::count(pool.neural_guided.begin(), pool.neural_guided.end(), 1u) < guided_target &&
|
||||
attempts < guided_target * 12) {
|
||||
while (static_cast<int>(proposals.states.size()) < proposal_target &&
|
||||
attempts < proposal_target * 16) {
|
||||
++attempts;
|
||||
std::size_t anchor_index = 0;
|
||||
if (attempts % 4 == 0) {
|
||||
@@ -4663,6 +4824,14 @@ HybridSeedPool make_hybrid_seed_pool(
|
||||
std::min<std::size_t>(8, anchors.size());
|
||||
}
|
||||
const Anchor& anchor = anchors[anchor_index];
|
||||
const bool score_independent_control =
|
||||
static_cast<int>(proposals.states.size()) < control_target;
|
||||
const NeuralPrediction& action_prediction = score_independent_control
|
||||
? anchor.control_prediction
|
||||
: anchor.prediction;
|
||||
const bool learned_action_prediction = score_independent_control
|
||||
? anchor.control_learned_prediction
|
||||
: anchor.learned_prediction;
|
||||
std::array<double, szilassi::surrogate::kPlaneCount> plane_bias{};
|
||||
const std::uint16_t failure_mask = static_cast<std::uint16_t>(
|
||||
anchor.metrics.crossing_face_mask | anchor.metrics.intersection_face_mask);
|
||||
@@ -4681,23 +4850,23 @@ HybridSeedPool make_hybrid_seed_pool(
|
||||
std::array<float, szilassi::surrogate::kScaleCount>
|
||||
scale_sampling_probabilities{};
|
||||
const int plane = sample_neural_action(
|
||||
anchor.prediction.plane_probabilities,
|
||||
model_ready ? 0.25 : 0.65,
|
||||
action_prediction.plane_probabilities,
|
||||
learned_action_prediction ? 0.25 : 0.65,
|
||||
rng,
|
||||
&plane_propensity,
|
||||
&plane_bias,
|
||||
&plane_sampling_probabilities);
|
||||
const int move = sample_neural_action(
|
||||
anchor.prediction.move_probabilities,
|
||||
model_ready ? 0.25 : 0.65,
|
||||
action_prediction.move_probabilities,
|
||||
learned_action_prediction ? 0.25 : 0.65,
|
||||
rng,
|
||||
&move_propensity,
|
||||
static_cast<const std::array<
|
||||
double, szilassi::surrogate::kMoveCount>*>(nullptr),
|
||||
&move_sampling_probabilities);
|
||||
const int scale = sample_neural_action(
|
||||
anchor.prediction.scale_probabilities,
|
||||
model_ready ? 0.25 : 0.50,
|
||||
action_prediction.scale_probabilities,
|
||||
learned_action_prediction ? 0.25 : 0.50,
|
||||
rng,
|
||||
&scale_propensity,
|
||||
static_cast<const std::array<
|
||||
@@ -4709,13 +4878,15 @@ HybridSeedPool make_hybrid_seed_pool(
|
||||
if (!round_trip_global_state_to_fp32(candidate, candidate_metrics)) {
|
||||
continue;
|
||||
}
|
||||
append(
|
||||
append_to(
|
||||
proposals,
|
||||
proposal_target,
|
||||
candidate,
|
||||
candidate_metrics,
|
||||
anchor.x,
|
||||
anchor.input,
|
||||
anchor.metrics,
|
||||
anchor.prediction,
|
||||
action_prediction,
|
||||
true,
|
||||
plane,
|
||||
move,
|
||||
@@ -4725,7 +4896,141 @@ HybridSeedPool make_hybrid_seed_pool(
|
||||
scale_propensity,
|
||||
plane_sampling_probabilities,
|
||||
move_sampling_probabilities,
|
||||
scale_sampling_probabilities);
|
||||
scale_sampling_probabilities,
|
||||
0);
|
||||
}
|
||||
|
||||
auto copy_proposal = [&](std::size_t index, std::uint8_t selection_mode) {
|
||||
if (index >= proposals.states.size()) return;
|
||||
VectorXd injected_x(GLOBAL_PLANE_VALUE_COUNT);
|
||||
VectorXd training_x(GLOBAL_PLANE_VALUE_COUNT);
|
||||
for (int component = 0; component < GLOBAL_PLANE_VALUE_COUNT; ++component) {
|
||||
injected_x[component] = static_cast<double>(
|
||||
proposals.states[index].values[static_cast<std::size_t>(component)]);
|
||||
training_x[component] = static_cast<double>(
|
||||
proposals.training_states[index].values[static_cast<std::size_t>(component)]);
|
||||
}
|
||||
append_to(
|
||||
pool,
|
||||
maximum_states,
|
||||
injected_x,
|
||||
proposals.injected_metrics[index],
|
||||
training_x,
|
||||
proposals.inputs[index],
|
||||
proposals.metrics[index],
|
||||
proposals.behavior_predictions[index],
|
||||
true,
|
||||
proposals.plane_actions[index],
|
||||
proposals.move_actions[index],
|
||||
proposals.scale_actions[index],
|
||||
proposals.plane_propensities[index],
|
||||
proposals.move_propensities[index],
|
||||
proposals.scale_propensities[index],
|
||||
proposals.plane_sampling_probabilities[index],
|
||||
proposals.move_sampling_probabilities[index],
|
||||
proposals.scale_sampling_probabilities[index],
|
||||
selection_mode);
|
||||
};
|
||||
|
||||
if (!transformer_ready || proposals.states.size() <=
|
||||
static_cast<std::size_t>(guided_target)) {
|
||||
for (std::size_t index = 0;
|
||||
index < proposals.states.size() &&
|
||||
index < static_cast<std::size_t>(guided_target);
|
||||
++index) {
|
||||
copy_proposal(index, 0);
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
const std::size_t controls = std::min<std::size_t>(
|
||||
static_cast<std::size_t>(control_target),
|
||||
proposals.states.size());
|
||||
for (std::size_t index = 0; index < controls; ++index) {
|
||||
copy_proposal(index, 2);
|
||||
++pool.transformer_control_seeds;
|
||||
}
|
||||
|
||||
struct RankedProposal {
|
||||
std::size_t index = 0;
|
||||
double acquisition = -std::numeric_limits<double>::infinity();
|
||||
};
|
||||
std::vector<szilassi::transformer::Features> rank_features;
|
||||
std::vector<std::size_t> rank_indices;
|
||||
rank_features.reserve(proposals.states.size() - controls);
|
||||
rank_indices.reserve(proposals.states.size() - controls);
|
||||
for (std::size_t index = controls; index < proposals.states.size(); ++index) {
|
||||
VectorXd candidate(GLOBAL_PLANE_VALUE_COUNT);
|
||||
for (int component = 0; component < GLOBAL_PLANE_VALUE_COUNT; ++component) {
|
||||
candidate[component] = static_cast<double>(
|
||||
proposals.states[index].values[static_cast<std::size_t>(component)]);
|
||||
}
|
||||
szilassi::transformer::Features features;
|
||||
if (make_transformer_features(
|
||||
candidate,
|
||||
proposals.injected_metrics[index],
|
||||
true,
|
||||
features)) {
|
||||
rank_features.push_back(features);
|
||||
rank_indices.push_back(index);
|
||||
} else {
|
||||
++pool.transformer_invalid_predictions;
|
||||
}
|
||||
}
|
||||
const std::vector<szilassi::transformer::Prediction> rank_predictions =
|
||||
transformer_ranker->predict_batch(rank_features);
|
||||
std::vector<RankedProposal> ranked;
|
||||
ranked.reserve(rank_predictions.size());
|
||||
for (std::size_t item = 0;
|
||||
item < rank_predictions.size() && item < rank_indices.size();
|
||||
++item) {
|
||||
const szilassi::transformer::Prediction& prediction = rank_predictions[item];
|
||||
if (!prediction.finite) {
|
||||
++pool.transformer_invalid_predictions;
|
||||
continue;
|
||||
}
|
||||
++pool.transformer_candidates_scored;
|
||||
const double probability = std::clamp(
|
||||
static_cast<double>(prediction.improvement_probability),
|
||||
0.0,
|
||||
1.0);
|
||||
const double expected_gain = std::clamp(
|
||||
static_cast<double>(prediction.expected_defect_gain),
|
||||
-8.0,
|
||||
8.0);
|
||||
const double uncertainty = std::sqrt(std::max(
|
||||
0.0,
|
||||
static_cast<double>(prediction.improvement_probability_variance)));
|
||||
ranked.push_back(RankedProposal{
|
||||
rank_indices[item],
|
||||
probability * (1.0 + std::max(0.0, expected_gain)) +
|
||||
0.15 * uncertainty});
|
||||
}
|
||||
std::stable_sort(ranked.begin(), ranked.end(), [](const auto& left, const auto& right) {
|
||||
if (left.acquisition != right.acquisition) {
|
||||
return left.acquisition > right.acquisition;
|
||||
}
|
||||
return left.index < right.index;
|
||||
});
|
||||
const std::size_t ranked_target = static_cast<std::size_t>(guided_target) - controls;
|
||||
std::unordered_set<std::size_t> selected_indices;
|
||||
std::size_t filled_ranked_slots = 0;
|
||||
for (std::size_t item = 0;
|
||||
item < ranked.size() && item < ranked_target;
|
||||
++item) {
|
||||
copy_proposal(ranked[item].index, 1);
|
||||
selected_indices.insert(ranked[item].index);
|
||||
++filled_ranked_slots;
|
||||
++pool.transformer_candidates_selected;
|
||||
}
|
||||
for (std::size_t index = controls;
|
||||
filled_ranked_slots < ranked_target &&
|
||||
index < proposals.states.size();
|
||||
++index) {
|
||||
if (selected_indices.insert(index).second) {
|
||||
copy_proposal(index, 0);
|
||||
++filled_ranked_slots;
|
||||
}
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
@@ -4738,6 +5043,7 @@ bool complete_hybrid_seed(const HybridSeedPool& pool, std::size_t index) {
|
||||
index < pool.injected_metrics.size() &&
|
||||
index < pool.behavior_predictions.size() &&
|
||||
index < pool.neural_guided.size() &&
|
||||
index < pool.transformer_selection_mode.size() &&
|
||||
index < pool.plane_actions.size() &&
|
||||
index < pool.move_actions.size() &&
|
||||
index < pool.scale_actions.size() &&
|
||||
@@ -4833,6 +5139,11 @@ std::uint64_t archive_hybrid_seed_pool(
|
||||
if (pool.neural_guided[index] != 0) {
|
||||
record.flags |= szilassi::training::SeedProposalNeuralGuided;
|
||||
}
|
||||
if (pool.transformer_selection_mode[index] == 1) {
|
||||
record.flags |= szilassi::training::SeedProposalTransformerRanked;
|
||||
} else if (pool.transformer_selection_mode[index] == 2) {
|
||||
record.flags |= szilassi::training::SeedProposalTransformerControl;
|
||||
}
|
||||
if (!append_training_record(archive, record)) {
|
||||
if (archive->fatal_error || !archive->writer->collection_enabled()) {
|
||||
break;
|
||||
@@ -5687,7 +5998,7 @@ bool write_run_manifest(
|
||||
<< "cuda_session_cache\t" << effective_cuda_session_cache << "\n"
|
||||
<< "algorithm\t"
|
||||
<< (options.use_cuda
|
||||
? "hybrid-quality-diversity-neural-v3"
|
||||
? "hybrid-quality-diversity-neural-v3-transformer-ranker-v1"
|
||||
: "cpu-parallel-simulated-annealing") << "\n"
|
||||
<< "control_baseline_min_fraction\t0.25\n"
|
||||
<< "strategy_weights\tadaptive-total:16,floors:baseline4/replica1/adaptive1/pbt1/injected3,max6\n"
|
||||
@@ -5715,6 +6026,11 @@ bool write_run_manifest(
|
||||
? "per-topology-ensemble:5,residual-mlp:45-128-128-128,value-policy,online-adamw,replay:4096,recent:25%"
|
||||
: "disabled") << "\n"
|
||||
<< "neural_safety\tbaseline-floor:25%,policy-exploration:25%,exact-cuda-plus-cpu-dd-authoritative\n"
|
||||
<< "transformer_enabled\t" << (g_transformer_active ? 1 : 0) << "\n"
|
||||
<< "transformer_model_id\t" << g_transformer_model_id << "\n"
|
||||
<< "transformer\tglobal-ensemble:3,set-transformer:12-faces-plus-cls,width:64,heads:4,layers:3,ff:256,host-fp32-ranker\n"
|
||||
<< "transformer_scope\tguided-seeds-only,ranked:12/64,score-independent-controls:4/64,map-cem-random:48/64\n"
|
||||
<< "transformer_failure_mode\tmissing-invalid-nonfinite:fallback-to-online-mlp\n"
|
||||
<< "training_archive_schema\t" << TRAINING_ARCHIVE_SCHEMA_VERSION << "\n"
|
||||
<< "training_archive_format\t" << szilassi::training::kTrainingShardFormatVersion << "\n"
|
||||
<< "training_archive_layout\trun-uuid/immutable-64MiB-crc-shards-plus-durable-wal\n"
|
||||
@@ -5900,7 +6216,8 @@ bool run_global_topology_round_cuda(
|
||||
int round_seed,
|
||||
int effective_chain_count,
|
||||
cuda_search::BatchSession& session,
|
||||
TrainingArchiveSession* training_archive
|
||||
TrainingArchiveSession* training_archive,
|
||||
const szilassi::transformer::TransformerRanker* transformer_ranker
|
||||
) {
|
||||
const auto round_started_at = std::chrono::steady_clock::now();
|
||||
state.last_round = {};
|
||||
@@ -5951,6 +6268,7 @@ bool run_global_topology_round_cuda(
|
||||
initial_pool_for_archive.injected_metrics.push_back(state.best);
|
||||
initial_pool_for_archive.behavior_predictions.emplace_back();
|
||||
initial_pool_for_archive.neural_guided.push_back(0);
|
||||
initial_pool_for_archive.transformer_selection_mode.push_back(0);
|
||||
initial_pool_for_archive.plane_actions.push_back(-1);
|
||||
initial_pool_for_archive.move_actions.push_back(-1);
|
||||
initial_pool_for_archive.scale_actions.push_back(-1);
|
||||
@@ -6001,17 +6319,37 @@ bool run_global_topology_round_cuda(
|
||||
std::max(0.002, options.step * (depth ? 0.025 : 0.05)));
|
||||
run_config.injected_state_jitter = static_cast<float>(
|
||||
std::max(0.002, options.step * (depth ? 0.018 : 0.04)));
|
||||
const int estimated_injected_chains = std::max(
|
||||
1,
|
||||
(effective_chain_count * std::max(0, strategy_weights[4]) + 15) / 16);
|
||||
const int estimated_chains_per_seed = std::max(
|
||||
1,
|
||||
(estimated_injected_chains + 63) / 64);
|
||||
HybridSeedPool injected_pool = make_hybrid_seed_pool(
|
||||
state,
|
||||
static_cast<std::uint64_t>(static_cast<std::uint32_t>(round_seed)) +
|
||||
static_cast<std::uint64_t>(batch + 1) * 0x9e3779b97f4a7c15ULL,
|
||||
64);
|
||||
64,
|
||||
transformer_ranker,
|
||||
szilassi::transformer::SearchBudget{
|
||||
static_cast<std::uint64_t>(std::max(0, options.cuda_iterations)),
|
||||
static_cast<std::uint32_t>(estimated_chains_per_seed),
|
||||
static_cast<std::uint32_t>(estimated_chains_per_seed),
|
||||
64U});
|
||||
run_config.injected_states = injected_pool.states;
|
||||
state.last_round.neural_seed_count += static_cast<std::uint64_t>(
|
||||
std::count(
|
||||
injected_pool.neural_guided.begin(),
|
||||
injected_pool.neural_guided.end(),
|
||||
static_cast<std::uint8_t>(1)));
|
||||
state.last_round.transformer_candidates_scored +=
|
||||
injected_pool.transformer_candidates_scored;
|
||||
state.last_round.transformer_candidates_selected +=
|
||||
injected_pool.transformer_candidates_selected;
|
||||
state.last_round.transformer_control_seeds +=
|
||||
injected_pool.transformer_control_seeds;
|
||||
state.last_round.transformer_invalid_predictions +=
|
||||
injected_pool.transformer_invalid_predictions;
|
||||
if (batch == 0 && (previous_has_state || session_was_initialized)) {
|
||||
run_config.fresh_numerator = depth ? 1 : 7;
|
||||
run_config.fresh_denominator = depth ? 4 : 8;
|
||||
@@ -6433,6 +6771,7 @@ bool run_global_topology_round_cuda(
|
||||
seed_index >= injected_pool.injected_metrics.size() ||
|
||||
seed_index >= injected_pool.behavior_predictions.size() ||
|
||||
seed_index >= injected_pool.neural_guided.size() ||
|
||||
seed_index >= injected_pool.transformer_selection_mode.size() ||
|
||||
seed_index >= injected_pool.plane_actions.size() ||
|
||||
seed_index >= injected_pool.move_actions.size() ||
|
||||
seed_index >= injected_pool.scale_actions.size() ||
|
||||
@@ -6488,7 +6827,9 @@ bool run_global_topology_round_cuda(
|
||||
record.replica_count = rollout.replica_count;
|
||||
// Best CPU-verified descendant among all chains assigned to
|
||||
// this proposal in the batch.
|
||||
record.selection_rule = 1;
|
||||
record.selection_rule = 1U |
|
||||
(static_cast<std::uint32_t>(
|
||||
injected_pool.transformer_selection_mode[seed_index]) << 8U);
|
||||
record.plane_action = injected_pool.plane_actions[seed_index];
|
||||
record.move_action = injected_pool.move_actions[seed_index];
|
||||
record.scale_action = injected_pool.scale_actions[seed_index];
|
||||
@@ -6508,6 +6849,11 @@ bool run_global_topology_round_cuda(
|
||||
if (injected_pool.neural_guided[seed_index] != 0) {
|
||||
record.flags |= szilassi::training::TrajectoryNeuralGuided;
|
||||
}
|
||||
if (injected_pool.transformer_selection_mode[seed_index] == 1) {
|
||||
record.flags |= szilassi::training::TrajectoryTransformerRanked;
|
||||
} else if (injected_pool.transformer_selection_mode[seed_index] == 2) {
|
||||
record.flags |= szilassi::training::TrajectoryTransformerControl;
|
||||
}
|
||||
if (injected_pool.behavior_predictions[seed_index].finite) {
|
||||
record.flags |= szilassi::training::TrajectoryPredictionSupplied;
|
||||
}
|
||||
@@ -6784,7 +7130,8 @@ bool run_global_topology_round(
|
||||
int round_seed,
|
||||
int effective_cuda_chains,
|
||||
cuda_search::BatchSession* cuda_session,
|
||||
TrainingArchiveSession* training_archive
|
||||
TrainingArchiveSession* training_archive,
|
||||
const szilassi::transformer::TransformerRanker* transformer_ranker
|
||||
) {
|
||||
if (options.use_cuda) {
|
||||
if (cuda_session == nullptr) {
|
||||
@@ -6798,7 +7145,8 @@ bool run_global_topology_round(
|
||||
round_seed,
|
||||
effective_cuda_chains,
|
||||
*cuda_session,
|
||||
training_archive);
|
||||
training_archive,
|
||||
transformer_ranker);
|
||||
}
|
||||
const auto round_started_at = std::chrono::steady_clock::now();
|
||||
state.last_round = {};
|
||||
@@ -7008,6 +7356,8 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
}
|
||||
const std::filesystem::path root(options.global_dir);
|
||||
std::filesystem::create_directories(root);
|
||||
g_transformer_active = false;
|
||||
g_transformer_model_id = "none";
|
||||
const szilassi::checkpoint::RunIdentity run_identity =
|
||||
szilassi::checkpoint::make_run_identity();
|
||||
g_global_degeneracy_weight = options.degeneracy_weight;
|
||||
@@ -7042,6 +7392,35 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
szilassi::transformer::TransformerRanker transformer_ranker;
|
||||
if (options.use_cuda) {
|
||||
const std::filesystem::path model_path =
|
||||
root / transformer_model_relative_path();
|
||||
if (std::filesystem::exists(model_path)) {
|
||||
std::string transformer_error;
|
||||
if (transformer_ranker.load(model_path, &transformer_error) &&
|
||||
transformer_ranker.ready()) {
|
||||
const szilassi::transformer::Metadata metadata =
|
||||
transformer_ranker.metadata();
|
||||
g_transformer_active = true;
|
||||
g_transformer_model_id = transformer_model_id(metadata);
|
||||
std::cout << "Transformer ranker: " << g_transformer_model_id
|
||||
<< ", validation AP " << metadata.validation.average_precision
|
||||
<< ", top-k gain " << metadata.validation.top_k_gain
|
||||
<< std::endl;
|
||||
} else {
|
||||
std::cerr << "Ignoring invalid transformer ranker: "
|
||||
<< transformer_error
|
||||
<< "; online MLP and ordinary search remain active."
|
||||
<< std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cout << "Transformer ranker: no approved model yet; "
|
||||
"online MLP and ordinary search remain active."
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
const unsigned int hardware_threads = std::thread::hardware_concurrency();
|
||||
const int worker_count = std::max(
|
||||
1,
|
||||
@@ -7093,11 +7472,13 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
std::uint64_t existing_neural_bytes = 0;
|
||||
std::array<std::uint64_t, NUM_TOPOLOGIES>
|
||||
existing_final_neural_snapshot_bytes{};
|
||||
std::uint64_t existing_transformer_snapshot_bytes = 0;
|
||||
std::string training_setup_error;
|
||||
if (!scan_neural_cache_bytes(
|
||||
root,
|
||||
existing_neural_bytes,
|
||||
existing_final_neural_snapshot_bytes,
|
||||
existing_transformer_snapshot_bytes,
|
||||
training_setup_error)) {
|
||||
std::cerr << "Cannot account existing neural cache: "
|
||||
<< training_setup_error << std::endl;
|
||||
@@ -7119,6 +7500,17 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
watcher.join();
|
||||
return 2;
|
||||
}
|
||||
const std::uint64_t maximum_transformer_snapshot =
|
||||
szilassi::transformer::maximum_model_bytes();
|
||||
if (maximum_transformer_snapshot > TRANSFORMER_MODEL_BUDGET) {
|
||||
std::cerr << "Configured transformer snapshot no longer fits its exact cache budget; "
|
||||
<< "revise the program before collecting more training data."
|
||||
<< std::endl;
|
||||
stop_requested.store(true, std::memory_order_relaxed);
|
||||
watcher_done.store(true, std::memory_order_relaxed);
|
||||
watcher.join();
|
||||
return 2;
|
||||
}
|
||||
// Count every byte that is already present, then reserve the missing
|
||||
// capacity of each canonical final-model slot independently. Taking the
|
||||
// maximum of aggregate existing/reserved bytes would undercount orphan or
|
||||
@@ -7142,6 +7534,19 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
}
|
||||
accounted_neural_files += missing_slot_bytes;
|
||||
}
|
||||
if (existing_transformer_snapshot_bytes < TRANSFORMER_MODEL_BUDGET) {
|
||||
const std::uint64_t missing_transformer_slot =
|
||||
TRANSFORMER_MODEL_BUDGET - existing_transformer_snapshot_bytes;
|
||||
if (missing_transformer_slot >
|
||||
std::numeric_limits<std::uint64_t>::max() - accounted_neural_files) {
|
||||
std::cerr << "Transformer cache byte-count overflow." << std::endl;
|
||||
stop_requested.store(true, std::memory_order_relaxed);
|
||||
watcher_done.store(true, std::memory_order_relaxed);
|
||||
watcher.join();
|
||||
return 2;
|
||||
}
|
||||
accounted_neural_files += missing_transformer_slot;
|
||||
}
|
||||
if (maximum_neural_snapshot >
|
||||
std::numeric_limits<std::uint64_t>::max() - accounted_neural_files) {
|
||||
std::cerr << "Neural cache byte-count overflow." << std::endl;
|
||||
@@ -7152,8 +7557,24 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
}
|
||||
// Atomic save temporarily keeps the previous final snapshot and one full
|
||||
// replacement. Existing orphan/foreign files remain separately accounted.
|
||||
// Transformer publication retains the current file while creating one
|
||||
// immutable generation and one atomic replacement temporary.
|
||||
const std::uint64_t transformer_publication_reserve =
|
||||
maximum_transformer_snapshot * 2ULL;
|
||||
if (maximum_neural_snapshot >
|
||||
std::numeric_limits<std::uint64_t>::max() - accounted_neural_files ||
|
||||
transformer_publication_reserve >
|
||||
std::numeric_limits<std::uint64_t>::max() -
|
||||
(accounted_neural_files + maximum_neural_snapshot)) {
|
||||
std::cerr << "Neural cache byte-count overflow." << std::endl;
|
||||
stop_requested.store(true, std::memory_order_relaxed);
|
||||
watcher_done.store(true, std::memory_order_relaxed);
|
||||
watcher.join();
|
||||
return 2;
|
||||
}
|
||||
const std::uint64_t neural_model_reserve =
|
||||
accounted_neural_files + maximum_neural_snapshot;
|
||||
accounted_neural_files + maximum_neural_snapshot +
|
||||
transformer_publication_reserve;
|
||||
szilassi::training::RecoveryReport training_recovery;
|
||||
const std::uint64_t training_archive_allocation =
|
||||
neural_model_reserve < TRAINING_CACHE_LIMIT_BYTES
|
||||
@@ -7279,6 +7700,7 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
<< "\trex_attempts\trex_accepts\tspsa_attempted\tspsa_accepted"
|
||||
<< "\tbest_crossing_loss\tbest_intersection_loss"
|
||||
<< "\tneural_replay\tneural_positive\tneural_samples\tneural_train_steps\tneural_seeds"
|
||||
<< "\ttransformer_scored\ttransformer_selected\ttransformer_controls\ttransformer_invalid"
|
||||
<< "\ttraining_verified\ttraining_fp32\ttraining_seeds"
|
||||
<< "\ttraining_rollouts\ttraining_refinements"
|
||||
<< "\ttraining_cache_accounted_bytes\ttraining_wal_bytes"
|
||||
@@ -7331,6 +7753,9 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
<< std::endl;
|
||||
if (options.use_cuda) {
|
||||
std::cout << "Guidance : smooth-I repair + per-topology online neural ensemble"
|
||||
<< (g_transformer_active
|
||||
? " + global transformer ranker"
|
||||
: " + transformer fallback inactive")
|
||||
<< std::endl;
|
||||
}
|
||||
std::cout << "Training data: CPU-verified states, sampled chain bests and injected trajectories, "
|
||||
@@ -7533,7 +7958,8 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
round_seed,
|
||||
effective_cuda_chains,
|
||||
cuda_session,
|
||||
&training_archive);
|
||||
&training_archive,
|
||||
g_transformer_active ? &transformer_ranker : nullptr);
|
||||
if (training_archive.fatal_error) {
|
||||
fatal_search_error = true;
|
||||
stop_requested.store(true, std::memory_order_relaxed);
|
||||
@@ -7584,6 +8010,16 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
<< (state.neural != nullptr
|
||||
? ", neural replay " + std::to_string(state.neural->replay_size())
|
||||
: std::string{})
|
||||
<< (g_transformer_active
|
||||
? ", transformer " +
|
||||
std::to_string(
|
||||
state.last_round.transformer_candidates_selected) +
|
||||
"/" + std::to_string(
|
||||
state.last_round.transformer_candidates_scored) +
|
||||
" + " + std::to_string(
|
||||
state.last_round.transformer_control_seeds) +
|
||||
" control"
|
||||
: std::string{})
|
||||
<< ", bandit " << std::fixed << std::setprecision(3)
|
||||
<< selection_score
|
||||
<< ", elapsed " << std::fixed << std::setprecision(1)
|
||||
@@ -7624,6 +8060,10 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
<< state.last_round.neural_samples_added << "\t"
|
||||
<< state.last_round.neural_training_steps << "\t"
|
||||
<< state.last_round.neural_seed_count << "\t"
|
||||
<< state.last_round.transformer_candidates_scored << "\t"
|
||||
<< state.last_round.transformer_candidates_selected << "\t"
|
||||
<< state.last_round.transformer_control_seeds << "\t"
|
||||
<< state.last_round.transformer_invalid_predictions << "\t"
|
||||
<< state.last_round.training_verified_records << "\t"
|
||||
<< state.last_round.training_fp32_records << "\t"
|
||||
<< state.last_round.training_seed_records << "\t"
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>18.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{D92AA5A6-F198-44F0-B8C1-0EB2FAE167D9}</ProjectGuid>
|
||||
<RootNamespace>TransformerTrainingExport</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<RepositoryRoot>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\..\'))</RepositoryRoot>
|
||||
<OutDir>$(RepositoryRoot)build\msbuild\bin\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(RepositoryRoot)build\msbuild\obj\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
|
||||
<LocalDebuggerWorkingDirectory>$(RepositoryRoot)</LocalDebuggerWorkingDirectory>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<PreprocessorDefinitions>UNICODE;_UNICODE;NOMINMAX;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(RepositoryRoot)src\TrainingArchive;$(RepositoryRoot)src\TransformerRanker;$(RepositoryRoot)external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalOptions>/utf-8 /permissive- %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="transformer_training_export.cpp" />
|
||||
<ClCompile Include="..\..\src\TrainingArchive\TrainingArchive.cpp" />
|
||||
<ClCompile Include="..\..\src\TransformerRanker\TransformerRanker.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\src\TrainingArchive\TrainingArchive.h" />
|
||||
<ClInclude Include="..\..\src\TransformerRanker\TransformerRanker.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,683 @@
|
||||
#include "../../src/TrainingArchive/TrainingArchive.h"
|
||||
#include "../../src/TransformerRanker/TransformerRanker.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
namespace training = szilassi::training;
|
||||
namespace transformer = szilassi::transformer;
|
||||
|
||||
constexpr std::array<char, 8> kExportMagic{{'S', 'Z', 'T', 'X', 'P', '0', '0', '1'}};
|
||||
constexpr std::uint32_t kExportVersion = 1;
|
||||
constexpr std::size_t kFaceCount = 12;
|
||||
constexpr std::size_t kFaceFeatureCount = 16;
|
||||
constexpr std::size_t kGlobalFeatureCount = 24;
|
||||
constexpr std::size_t kFeatureFloatCount =
|
||||
kFaceCount * kFaceFeatureCount + kGlobalFeatureCount;
|
||||
|
||||
// Stable packed little-endian record layout (there is no native-struct padding):
|
||||
// 0 u8[16] canonical UUID bytes
|
||||
// 16 u64 record_sequence
|
||||
// 24 u32 topology
|
||||
// 28 f32[216] proposal features (12x16 face, then 24 global)
|
||||
// 892 f32[216] anchor features (12x16 face, then 24 global)
|
||||
// 1756 i32 crossing_gain = anchor.C - result.C
|
||||
// 1760 i32 intersection_gain = anchor.I - result.I
|
||||
// 1764 i32 total_defect_gain
|
||||
// 1768 u8 defect_improved (total_defect_gain > 0)
|
||||
// 1769 u8 objective_better (TrajectoryImproved)
|
||||
// 1770 u8 defect_count_tie (total_defect_gain == 0)
|
||||
// 1771 u8 entered_archive
|
||||
// 1772 i32[3] plane, move, scale actions (-1 means absent)
|
||||
// 1784 f32[3] actual plane, move, scale propensities
|
||||
// 1796 u32 replica_count / assigned chains
|
||||
// 1800 u64 rollout_iterations
|
||||
// 1808 u32 InjectedTrajectory flags
|
||||
// 1812 u32 anchor Metrics flags
|
||||
// 1816 u32 injected/proposal Metrics flags
|
||||
// 1820 u32 result Metrics flags (contains MetricDdVerified when applicable)
|
||||
constexpr std::uint32_t kExportRecordSize = 1824;
|
||||
|
||||
using FeatureFaceArray =
|
||||
decltype(std::declval<transformer::Features>().face);
|
||||
using FeatureFaceRow = typename FeatureFaceArray::value_type;
|
||||
using FeatureGlobalArray =
|
||||
decltype(std::declval<transformer::Features>().global);
|
||||
static_assert(std::tuple_size<FeatureFaceArray>::value == kFaceCount,
|
||||
"Transformer face count changed; bump the export format");
|
||||
static_assert(std::tuple_size<FeatureFaceRow>::value == kFaceFeatureCount,
|
||||
"Transformer face feature count changed; bump the export format");
|
||||
static_assert(std::tuple_size<FeatureGlobalArray>::value == kGlobalFeatureCount,
|
||||
"Transformer global feature count changed; bump the export format");
|
||||
|
||||
struct Options {
|
||||
std::filesystem::path root;
|
||||
std::set<std::string> included_runs;
|
||||
bool root_supplied = false;
|
||||
};
|
||||
|
||||
struct ShardSnapshot {
|
||||
std::filesystem::path path;
|
||||
std::string run_name;
|
||||
training::RunId expected_run_id{};
|
||||
std::optional<std::uint64_t> filename_index;
|
||||
std::uint64_t size = 0;
|
||||
};
|
||||
|
||||
void print_usage() {
|
||||
std::cerr
|
||||
<< "Usage: TransformerTrainingExport --root <results/search> "
|
||||
"[--include-run <UUID>]...\n"
|
||||
<< "Writes SZTXP001 version-1 packed little-endian records to stdout.\n";
|
||||
}
|
||||
|
||||
int hexadecimal_digit(char value) {
|
||||
if (value >= '0' && value <= '9') return value - '0';
|
||||
if (value >= 'a' && value <= 'f') return 10 + value - 'a';
|
||||
if (value >= 'A' && value <= 'F') return 10 + value - 'A';
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool parse_canonical_uuid(
|
||||
const std::string& text,
|
||||
std::string& normalized,
|
||||
training::RunId& run_id
|
||||
) {
|
||||
constexpr std::array<std::size_t, 4> kHyphens{{8, 13, 18, 23}};
|
||||
if (text.size() != 36) return false;
|
||||
for (std::size_t position : kHyphens) {
|
||||
if (text[position] != '-') return false;
|
||||
}
|
||||
normalized.clear();
|
||||
normalized.reserve(text.size());
|
||||
std::array<char, 32> hexadecimal{};
|
||||
std::size_t digit_count = 0;
|
||||
for (std::size_t index = 0; index < text.size(); ++index) {
|
||||
if (std::find(kHyphens.begin(), kHyphens.end(), index) != kHyphens.end()) {
|
||||
normalized.push_back('-');
|
||||
continue;
|
||||
}
|
||||
const int digit = hexadecimal_digit(text[index]);
|
||||
if (digit < 0 || digit_count >= hexadecimal.size()) return false;
|
||||
const char lower = static_cast<char>(std::tolower(
|
||||
static_cast<unsigned char>(text[index])));
|
||||
hexadecimal[digit_count++] = lower;
|
||||
normalized.push_back(lower);
|
||||
}
|
||||
if (digit_count != hexadecimal.size()) return false;
|
||||
for (std::size_t index = 0; index < run_id.bytes.size(); ++index) {
|
||||
const int high = hexadecimal_digit(hexadecimal[index * 2]);
|
||||
const int low = hexadecimal_digit(hexadecimal[index * 2 + 1]);
|
||||
run_id.bytes[index] = static_cast<std::uint8_t>((high << 4) | low);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_options(int argc, char* argv[], Options& options) {
|
||||
for (int index = 1; index < argc; ++index) {
|
||||
const std::string argument = argv[index];
|
||||
if (argument == "--help" || argument == "-h") {
|
||||
print_usage();
|
||||
return false;
|
||||
}
|
||||
if (argument == "--root") {
|
||||
if (++index >= argc || options.root_supplied) {
|
||||
std::cerr << "--root requires exactly one path\n";
|
||||
return false;
|
||||
}
|
||||
options.root = std::filesystem::path(argv[index]);
|
||||
options.root_supplied = true;
|
||||
continue;
|
||||
}
|
||||
if (argument == "--include-run") {
|
||||
if (++index >= argc) {
|
||||
std::cerr << "--include-run requires a canonical UUID\n";
|
||||
return false;
|
||||
}
|
||||
std::string normalized;
|
||||
training::RunId ignored;
|
||||
if (!parse_canonical_uuid(argv[index], normalized, ignored)) {
|
||||
std::cerr << "Invalid run UUID: " << argv[index] << '\n';
|
||||
return false;
|
||||
}
|
||||
options.included_runs.insert(std::move(normalized));
|
||||
continue;
|
||||
}
|
||||
std::cerr << "Unknown argument: " << argument << '\n';
|
||||
return false;
|
||||
}
|
||||
if (!options.root_supplied || options.root.empty()) {
|
||||
std::cerr << "--root is required\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::uint64_t> parse_shard_filename(
|
||||
const std::filesystem::path& path
|
||||
) {
|
||||
const std::string filename = path.filename().string();
|
||||
constexpr const char* kPrefix = "shard_";
|
||||
constexpr const char* kSuffix = ".sztd";
|
||||
constexpr std::size_t kPrefixSize = 6;
|
||||
constexpr std::size_t kSuffixSize = 5;
|
||||
if (filename.size() <= kPrefixSize + kSuffixSize ||
|
||||
filename.compare(0, kPrefixSize, kPrefix) != 0 ||
|
||||
filename.compare(filename.size() - kSuffixSize, kSuffixSize, kSuffix) != 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::string digits = filename.substr(
|
||||
kPrefixSize,
|
||||
filename.size() - kPrefixSize - kSuffixSize);
|
||||
std::uint64_t value = 0;
|
||||
for (char digit : digits) {
|
||||
if (digit < '0' || digit > '9' ||
|
||||
value > (std::numeric_limits<std::uint64_t>::max() - 9U) / 10U) {
|
||||
return std::nullopt;
|
||||
}
|
||||
value = value * 10U + static_cast<unsigned>(digit - '0');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
bool snapshot_shards(
|
||||
const Options& options,
|
||||
std::vector<ShardSnapshot>& shards,
|
||||
std::string& error
|
||||
) {
|
||||
std::error_code path_error;
|
||||
const std::filesystem::path absolute_root =
|
||||
std::filesystem::absolute(options.root, path_error);
|
||||
if (path_error) {
|
||||
error = "Cannot resolve archive root: " + path_error.message();
|
||||
return false;
|
||||
}
|
||||
const std::filesystem::path runs_root = absolute_root / "runs";
|
||||
if (!std::filesystem::is_directory(runs_root, path_error) || path_error) {
|
||||
error = "Archive runs directory does not exist: " + runs_root.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
struct RunDirectory {
|
||||
std::filesystem::path path;
|
||||
std::string normalized_name;
|
||||
training::RunId run_id{};
|
||||
};
|
||||
std::vector<RunDirectory> runs;
|
||||
std::set<std::string> found_requested_runs;
|
||||
std::filesystem::directory_iterator iterator(runs_root, path_error);
|
||||
const std::filesystem::directory_iterator end;
|
||||
if (path_error) {
|
||||
error = "Cannot enumerate archive runs: " + path_error.message();
|
||||
return false;
|
||||
}
|
||||
while (iterator != end) {
|
||||
std::error_code type_error;
|
||||
const bool is_directory = iterator->is_directory(type_error);
|
||||
if (type_error) {
|
||||
error = "Cannot inspect run directory entry: " + type_error.message();
|
||||
return false;
|
||||
}
|
||||
if (is_directory) {
|
||||
std::string normalized;
|
||||
training::RunId run_id;
|
||||
const std::string name = iterator->path().filename().string();
|
||||
if (parse_canonical_uuid(name, normalized, run_id) &&
|
||||
(options.included_runs.empty() ||
|
||||
options.included_runs.count(normalized) != 0)) {
|
||||
runs.push_back({iterator->path(), normalized, run_id});
|
||||
found_requested_runs.insert(normalized);
|
||||
}
|
||||
}
|
||||
iterator.increment(path_error);
|
||||
if (path_error) {
|
||||
error = "Cannot continue enumerating archive runs: " + path_error.message();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const std::string& requested : options.included_runs) {
|
||||
if (found_requested_runs.count(requested) == 0) {
|
||||
error = "Requested run does not exist below the archive root: " + requested;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::sort(runs.begin(), runs.end(), [](const RunDirectory& left, const RunDirectory& right) {
|
||||
return left.normalized_name < right.normalized_name;
|
||||
});
|
||||
|
||||
for (const RunDirectory& run : runs) {
|
||||
const std::filesystem::path training_directory = run.path / "training";
|
||||
std::error_code exists_error;
|
||||
const bool has_training =
|
||||
std::filesystem::is_directory(training_directory, exists_error);
|
||||
if (exists_error) {
|
||||
error = "Cannot inspect training directory for run " +
|
||||
run.normalized_name + ": " + exists_error.message();
|
||||
return false;
|
||||
}
|
||||
if (!has_training) {
|
||||
if (!options.included_runs.empty()) {
|
||||
error = "Requested run has no training directory: " + run.normalized_name;
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
std::filesystem::directory_iterator shard_iterator(
|
||||
training_directory,
|
||||
exists_error);
|
||||
if (exists_error) {
|
||||
error = "Cannot enumerate training directory for run " +
|
||||
run.normalized_name + ": " + exists_error.message();
|
||||
return false;
|
||||
}
|
||||
while (shard_iterator != end) {
|
||||
std::error_code file_error;
|
||||
const bool regular = shard_iterator->is_regular_file(file_error);
|
||||
if (file_error) {
|
||||
error = "Cannot inspect training file: " + file_error.message();
|
||||
return false;
|
||||
}
|
||||
const std::filesystem::path path = shard_iterator->path();
|
||||
if (regular && path.extension() == ".sztd") {
|
||||
const std::uint64_t size = shard_iterator->file_size(file_error);
|
||||
if (file_error) {
|
||||
error = "Cannot size training shard: " + file_error.message();
|
||||
return false;
|
||||
}
|
||||
shards.push_back({
|
||||
path,
|
||||
run.normalized_name,
|
||||
run.run_id,
|
||||
parse_shard_filename(path),
|
||||
size});
|
||||
}
|
||||
shard_iterator.increment(exists_error);
|
||||
if (exists_error) {
|
||||
error = "Cannot continue enumerating training shards: " +
|
||||
exists_error.message();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(shards.begin(), shards.end(), [](const ShardSnapshot& left,
|
||||
const ShardSnapshot& right) {
|
||||
if (left.run_name != right.run_name) return left.run_name < right.run_name;
|
||||
if (left.filename_index.has_value() != right.filename_index.has_value()) {
|
||||
return left.filename_index.has_value();
|
||||
}
|
||||
if (left.filename_index && right.filename_index &&
|
||||
*left.filename_index != *right.filename_index) {
|
||||
return *left.filename_index < *right.filename_index;
|
||||
}
|
||||
return left.path.filename().string() < right.path.filename().string();
|
||||
});
|
||||
if (shards.empty()) {
|
||||
error = "No sealed .sztd shards matched the requested runs";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class PackedRecord {
|
||||
public:
|
||||
void bytes(const std::uint8_t* values, std::size_t count) {
|
||||
if (!room(count)) return;
|
||||
std::memcpy(data_.data() + size_, values, count);
|
||||
size_ += count;
|
||||
}
|
||||
|
||||
void u8(std::uint8_t value) {
|
||||
if (!room(1)) return;
|
||||
data_[size_++] = value;
|
||||
}
|
||||
|
||||
void u32(std::uint32_t value) {
|
||||
if (!room(4)) return;
|
||||
for (std::size_t byte = 0; byte < 4; ++byte) {
|
||||
data_[size_++] = static_cast<std::uint8_t>(value >> (byte * 8));
|
||||
}
|
||||
}
|
||||
|
||||
void i32(std::int32_t value) {
|
||||
std::uint32_t bits = 0;
|
||||
std::memcpy(&bits, &value, sizeof(bits));
|
||||
u32(bits);
|
||||
}
|
||||
|
||||
void u64(std::uint64_t value) {
|
||||
if (!room(8)) return;
|
||||
for (std::size_t byte = 0; byte < 8; ++byte) {
|
||||
data_[size_++] = static_cast<std::uint8_t>(value >> (byte * 8));
|
||||
}
|
||||
}
|
||||
|
||||
void f32(float value) {
|
||||
std::uint32_t bits = 0;
|
||||
static_assert(sizeof(bits) == sizeof(value), "Unexpected float width");
|
||||
std::memcpy(&bits, &value, sizeof(bits));
|
||||
u32(bits);
|
||||
}
|
||||
|
||||
const std::uint8_t* data() const { return data_.data(); }
|
||||
std::size_t size() const { return size_; }
|
||||
bool overflowed() const { return overflowed_; }
|
||||
|
||||
private:
|
||||
bool room(std::size_t count) {
|
||||
if (count > data_.size() - size_) {
|
||||
overflowed_ = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::array<std::uint8_t, kExportRecordSize> data_{};
|
||||
std::size_t size_ = 0;
|
||||
bool overflowed_ = false;
|
||||
};
|
||||
|
||||
bool append_features(
|
||||
PackedRecord& packed,
|
||||
const transformer::Features& features,
|
||||
std::uint32_t topology,
|
||||
std::string& error
|
||||
) {
|
||||
if (features.topology != topology) {
|
||||
error = "Transformer feature topology does not match the archive record";
|
||||
return false;
|
||||
}
|
||||
for (const auto& face : features.face) {
|
||||
for (float value : face) {
|
||||
if (!std::isfinite(value)) {
|
||||
error = "Transformer face feature is not finite";
|
||||
return false;
|
||||
}
|
||||
packed.f32(value);
|
||||
}
|
||||
}
|
||||
for (float value : features.global) {
|
||||
if (!std::isfinite(value)) {
|
||||
error = "Transformer global feature is not finite";
|
||||
return false;
|
||||
}
|
||||
packed.f32(value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool checked_i32(std::int64_t value, std::int32_t& result) {
|
||||
if (value < std::numeric_limits<std::int32_t>::min() ||
|
||||
value > std::numeric_limits<std::int32_t>::max()) {
|
||||
return false;
|
||||
}
|
||||
result = static_cast<std::int32_t>(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool encode_trajectory(
|
||||
const training::InjectedTrajectory& trajectory,
|
||||
const training::RunId& expected_run_id,
|
||||
PackedRecord& packed,
|
||||
std::string& error
|
||||
) {
|
||||
if (trajectory.context.run_id.bytes != expected_run_id.bytes) {
|
||||
error = "Training record run UUID does not match its parent run directory";
|
||||
return false;
|
||||
}
|
||||
|
||||
const transformer::SearchBudget budget{
|
||||
trajectory.rollout_iterations,
|
||||
trajectory.replica_count,
|
||||
trajectory.replica_count,
|
||||
trajectory.pool_size};
|
||||
const bool guided =
|
||||
(trajectory.flags & training::TrajectoryNeuralGuided) != 0;
|
||||
const transformer::FeatureInput proposal_input{
|
||||
trajectory.injected_state,
|
||||
trajectory.injected_metrics,
|
||||
budget,
|
||||
trajectory.context.topology,
|
||||
guided};
|
||||
const transformer::FeatureInput anchor_input{
|
||||
trajectory.anchor_state,
|
||||
trajectory.anchor_metrics,
|
||||
budget,
|
||||
trajectory.context.topology,
|
||||
guided};
|
||||
transformer::Features proposal_features;
|
||||
transformer::Features anchor_features;
|
||||
std::string feature_error;
|
||||
if (!transformer::make_features(
|
||||
proposal_input,
|
||||
proposal_features,
|
||||
&feature_error)) {
|
||||
error = "Cannot build proposal features: " + feature_error;
|
||||
return false;
|
||||
}
|
||||
if (!transformer::make_features(
|
||||
anchor_input,
|
||||
anchor_features,
|
||||
&feature_error)) {
|
||||
error = "Cannot build anchor features: " + feature_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::int64_t crossing_gain_wide =
|
||||
static_cast<std::int64_t>(trajectory.anchor_metrics.crossings) -
|
||||
static_cast<std::int64_t>(trajectory.result_metrics.crossings);
|
||||
const std::int64_t intersection_gain_wide =
|
||||
static_cast<std::int64_t>(trajectory.anchor_metrics.intersections) -
|
||||
static_cast<std::int64_t>(trajectory.result_metrics.intersections);
|
||||
const std::int64_t defect_gain_wide =
|
||||
crossing_gain_wide + intersection_gain_wide;
|
||||
std::int32_t crossing_gain = 0;
|
||||
std::int32_t intersection_gain = 0;
|
||||
std::int32_t defect_gain = 0;
|
||||
if (!checked_i32(crossing_gain_wide, crossing_gain) ||
|
||||
!checked_i32(intersection_gain_wide, intersection_gain) ||
|
||||
!checked_i32(defect_gain_wide, defect_gain)) {
|
||||
error = "Trajectory label does not fit the export format";
|
||||
return false;
|
||||
}
|
||||
|
||||
packed.bytes(trajectory.context.run_id.bytes.data(),
|
||||
trajectory.context.run_id.bytes.size());
|
||||
packed.u64(trajectory.context.record_sequence);
|
||||
packed.u32(trajectory.context.topology);
|
||||
if (!append_features(
|
||||
packed,
|
||||
proposal_features,
|
||||
trajectory.context.topology,
|
||||
error) ||
|
||||
!append_features(
|
||||
packed,
|
||||
anchor_features,
|
||||
trajectory.context.topology,
|
||||
error)) {
|
||||
return false;
|
||||
}
|
||||
packed.i32(crossing_gain);
|
||||
packed.i32(intersection_gain);
|
||||
packed.i32(defect_gain);
|
||||
packed.u8(defect_gain > 0 ? 1U : 0U);
|
||||
packed.u8((trajectory.flags & training::TrajectoryImproved) != 0 ? 1U : 0U);
|
||||
packed.u8(defect_gain == 0 ? 1U : 0U);
|
||||
packed.u8((trajectory.flags & training::TrajectoryEnteredArchive) != 0 ? 1U : 0U);
|
||||
packed.i32(trajectory.plane_action);
|
||||
packed.i32(trajectory.move_action);
|
||||
packed.i32(trajectory.scale_action);
|
||||
packed.f32(trajectory.plane_propensity);
|
||||
packed.f32(trajectory.move_propensity);
|
||||
packed.f32(trajectory.scale_propensity);
|
||||
packed.u32(trajectory.replica_count);
|
||||
packed.u64(trajectory.rollout_iterations);
|
||||
packed.u32(trajectory.flags);
|
||||
packed.u32(trajectory.anchor_metrics.flags);
|
||||
packed.u32(trajectory.injected_metrics.flags);
|
||||
packed.u32(trajectory.result_metrics.flags);
|
||||
if (packed.overflowed() || packed.size() != kExportRecordSize) {
|
||||
error = "Internal export record-size mismatch";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool write_u32(std::ostream& output, std::uint32_t value) {
|
||||
std::array<char, 4> bytes{};
|
||||
for (std::size_t index = 0; index < bytes.size(); ++index) {
|
||||
bytes[index] = static_cast<char>(
|
||||
static_cast<unsigned char>(value >> (index * 8)));
|
||||
}
|
||||
output.write(bytes.data(), static_cast<std::streamsize>(bytes.size()));
|
||||
return static_cast<bool>(output);
|
||||
}
|
||||
|
||||
bool write_header(std::ostream& output) {
|
||||
output.write(kExportMagic.data(), static_cast<std::streamsize>(kExportMagic.size()));
|
||||
return static_cast<bool>(output) &&
|
||||
write_u32(output, kExportVersion) &&
|
||||
write_u32(output, kExportRecordSize);
|
||||
}
|
||||
|
||||
void lower_windows_priority() {
|
||||
#ifdef _WIN32
|
||||
if (SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS) == 0) {
|
||||
std::cerr << "Warning: cannot set below-normal process priority (Windows error "
|
||||
<< GetLastError() << ")\n";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool enable_binary_stdout() {
|
||||
#ifdef _WIN32
|
||||
return _setmode(_fileno(stdout), _O_BINARY) != -1;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
Options options;
|
||||
if (!parse_options(argc, argv, options)) {
|
||||
return 2;
|
||||
}
|
||||
lower_windows_priority();
|
||||
if (!enable_binary_stdout()) {
|
||||
std::cerr << "Cannot switch stdout to binary mode\n";
|
||||
return 3;
|
||||
}
|
||||
|
||||
std::vector<ShardSnapshot> shards;
|
||||
std::string error;
|
||||
if (!snapshot_shards(options, shards, error)) {
|
||||
std::cerr << "Cannot snapshot training shards: " << error << '\n';
|
||||
return 4;
|
||||
}
|
||||
std::uint64_t snapshot_bytes = 0;
|
||||
for (const ShardSnapshot& shard : shards) {
|
||||
if (shard.size > std::numeric_limits<std::uint64_t>::max() - snapshot_bytes) {
|
||||
std::cerr << "Training shard snapshot byte count overflow\n";
|
||||
return 4;
|
||||
}
|
||||
snapshot_bytes += shard.size;
|
||||
}
|
||||
std::cerr << "Training export snapshot: " << shards.size()
|
||||
<< " sealed shards, " << snapshot_bytes << " bytes";
|
||||
if (!options.included_runs.empty()) {
|
||||
std::cerr << ", " << options.included_runs.size() << " selected run(s)";
|
||||
}
|
||||
std::cerr << '\n';
|
||||
|
||||
std::ios::sync_with_stdio(false);
|
||||
if (!write_header(std::cout)) {
|
||||
std::cerr << "Cannot write training export header to stdout\n";
|
||||
return 5;
|
||||
}
|
||||
|
||||
std::uint64_t exported_records = 0;
|
||||
for (std::size_t shard_ordinal = 0; shard_ordinal < shards.size(); ++shard_ordinal) {
|
||||
const ShardSnapshot& snapshot = shards[shard_ordinal];
|
||||
bool output_failed = false;
|
||||
std::string record_error;
|
||||
training::StreamCallbacks callbacks;
|
||||
callbacks.injected_trajectory = [&](const training::InjectedTrajectory& trajectory) {
|
||||
PackedRecord packed;
|
||||
if (!encode_trajectory(
|
||||
trajectory,
|
||||
snapshot.expected_run_id,
|
||||
packed,
|
||||
record_error)) {
|
||||
return false;
|
||||
}
|
||||
std::cout.write(
|
||||
reinterpret_cast<const char*>(packed.data()),
|
||||
static_cast<std::streamsize>(packed.size()));
|
||||
if (!std::cout) {
|
||||
output_failed = true;
|
||||
return false;
|
||||
}
|
||||
++exported_records;
|
||||
return true;
|
||||
};
|
||||
training::ShardInfo info;
|
||||
std::string shard_error;
|
||||
if (!training::stream_read_shard(
|
||||
snapshot.path,
|
||||
callbacks,
|
||||
&info,
|
||||
&shard_error)) {
|
||||
if (output_failed) {
|
||||
std::cerr << "stdout failed while exporting "
|
||||
<< snapshot.path.string() << '\n';
|
||||
return 5;
|
||||
}
|
||||
std::cerr << "Cannot export " << snapshot.path.string() << ": "
|
||||
<< (record_error.empty() ? shard_error : record_error) << '\n';
|
||||
return 6;
|
||||
}
|
||||
if (snapshot.filename_index && info.shard_index != *snapshot.filename_index) {
|
||||
std::cerr << "Shard header index does not match filename: "
|
||||
<< snapshot.path.string() << '\n';
|
||||
return 6;
|
||||
}
|
||||
if ((shard_ordinal + 1U) % 16U == 0U ||
|
||||
shard_ordinal + 1U == shards.size()) {
|
||||
std::cerr << "Validated " << (shard_ordinal + 1U) << '/'
|
||||
<< shards.size() << " shards; exported "
|
||||
<< exported_records << " trajectories\n";
|
||||
}
|
||||
}
|
||||
std::cout.flush();
|
||||
if (!std::cout) {
|
||||
std::cerr << "Cannot finish writing the training export stream\n";
|
||||
return 5;
|
||||
}
|
||||
std::cerr << "Training export complete: " << exported_records
|
||||
<< " fixed records, record_size=" << kExportRecordSize << '\n';
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user