#define DROP7_FAIR_ONLY_DEPTH4_LIBRARY
#include "../../fair-expectimax/reference/fair-only-depth4.cpp"
#include <array>
#include <atomic>
#include <bit>
#include <chrono>
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <future>
#include <numeric>
#include <mutex>
#include <iomanip>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <string>
#include <string_view>
#include <sys/resource.h>
#include <vector>
// Performs scaled, leakage-controlled compression of the reference public
// fair-D4 root-Q policy. A separate D4 label artifact is available only to
// the explicit architecture-design command. The main run uses disjoint
// whole-game fitting and heldout families and never reads gameplay
// screen, validation, or final seeds.
namespace drop7::scaled_d4_distill {
namespace fair = drop7::fair_only_depth4;
constexpr double kTieTolerance = 1.0e-9;
constexpr std::array<int, kBoardSize> kActionOrder{{3, 2, 4, 1, 5, 0, 6}};
struct RootLabel {
Board board{};
std::uint8_t next_disc = 1;
int moves_remaining = kMovesPerLevel;
int labeled_action = -1;
std::array<double, kBoardSize> q{};
std::array<bool, kBoardSize> legal{};
int game = -1;
int move_in_game = -1;
};
int integerAfter(std::string_view line, std::string_view marker) {
const std::size_t found = line.find(marker);
if (found == std::string_view::npos) {
throw std::runtime_error("missing label integer field");
}
const char* begin = line.data() + found + marker.size();
char* end = nullptr;
const long parsed = std::strtol(begin, &end, 10);
if (end == begin || parsed < std::numeric_limits<int>::min() ||
parsed > std::numeric_limits<int>::max()) {
throw std::runtime_error("invalid label integer field");
}
return static_cast<int>(parsed);
}
RootLabel parseLabel(std::string_view line, std::string_view split) {
const std::string split_marker = "\"split\":\"" + std::string(split) + "\"";
if (line.find(split_marker) == std::string_view::npos) {
throw std::runtime_error("label split mismatch");
}
constexpr std::string_view board_marker = "\"board\":\"";
const std::size_t board_at = line.find(board_marker);
if (board_at == std::string_view::npos ||
board_at + board_marker.size() + kCellCount > line.size()) {
throw std::runtime_error("invalid label board");
}
RootLabel result;
for (int cell = 0; cell < kCellCount; ++cell) {
const char encoded = line[board_at + board_marker.size() + cell];
if (encoded < '0' || encoded > '9') {
throw std::runtime_error("invalid label cell");
}
result.board[cell] = static_cast<std::uint8_t>(encoded - '0');
}
result.next_disc =
static_cast<std::uint8_t>(integerAfter(line, "\"nextDisc\":"));
result.moves_remaining = integerAfter(line, "\"movesRemaining\":");
result.labeled_action = integerAfter(line, "\"action\":");
if (result.next_disc < 1 || result.next_disc > kBoardSize ||
result.moves_remaining < 1 || result.moves_remaining > kMovesPerLevel ||
result.labeled_action < 0 || result.labeled_action >= kBoardSize) {
throw std::runtime_error("invalid label public fields");
}
constexpr std::string_view q_marker = "\"rootQ\":[";
std::size_t cursor = line.find(q_marker);
if (cursor == std::string_view::npos) {
throw std::runtime_error("missing root-Q vector");
}
cursor += q_marker.size();
const std::string owned(line);
result.q.fill(-std::numeric_limits<double>::infinity());
for (int action = 0; action < kBoardSize; ++action) {
while (cursor < line.size() &&
(line[cursor] == ' ' || line[cursor] == ',')) {
++cursor;
}
if (line.substr(cursor, 4) == "null") {
cursor += 4;
result.legal[action] = false;
continue;
}
char* end = nullptr;
const char* begin = owned.c_str() + cursor;
result.q[action] = std::strtod(begin, &end);
if (end == begin || !std::isfinite(result.q[action])) {
throw std::runtime_error("invalid root-Q value");
}
cursor = static_cast<std::size_t>(end - owned.c_str());
result.legal[action] = true;
}
for (int action = 0; action < kBoardSize; ++action) {
if (result.legal[action] != isLegal(result.board, action)) {
throw std::runtime_error("root-Q legal mask mismatch");
}
}
return result;
}
std::vector<RootLabel> loadSplit(const std::string& path,
std::string_view split) {
std::ifstream input(path);
if (!input) throw std::runtime_error("could not open root-label file");
std::string line;
if (!std::getline(input, line) ||
(line.find("drop7-public-d4-root-labels-v1") == std::string::npos &&
line.find("drop7-public-d4-root-labels-v2") == std::string::npos)) {
throw std::runtime_error("invalid root-label header");
}
std::vector<RootLabel> result;
int game = -1;
int move = -1;
while (std::getline(input, line)) {
if (line.find("\"split\":\"" + std::string(split) + "\"") ==
std::string::npos) {
continue;
}
RootLabel label = parseLabel(line, split);
if (label.board == initialBoard()) {
++game;
move = 0;
} else if (game < 0) {
throw std::runtime_error("label split does not start at game boundary");
} else {
++move;
}
label.game = game;
label.move_in_game = move;
result.push_back(std::move(label));
}
if (result.empty()) throw std::runtime_error("empty root-label split");
return result;
}
bool qTied(double first, double second) {
return std::abs(first - second) <=
kTieTolerance *
(1.0 + std::max(std::abs(first), std::abs(second)));
}
struct Ranking {
int roots = 0;
int top1 = 0;
int top2 = 0;
std::uint64_t pairs = 0;
double pairwise_credit = 0.0;
double normalized_regret = 0.0;
};
State publicState(const RootLabel& label) {
State state;
state.board = label.board;
state.next_disc = label.next_disc;
state.moves_remaining = label.moves_remaining;
state.score = 0;
state.level = 1;
state.moves_played = 0;
state.game_over = false;
return state;
}
struct ShallowRoot {
std::array<double, kBoardSize> values{};
std::array<double, kBoardSize> expected_scores{};
};
ShallowRoot shallowRoot(const RootLabel& label, int depth) {
fair::SearchContext context;
const fair::RootEvaluation root =
fair::rootDecision(publicState(label), depth, context);
return {root.values, root.expected_scores};
}
std::array<double, kBoardSize> normalized(
const std::array<double, kBoardSize>& values,
const std::array<bool, kBoardSize>& legal) {
double minimum = std::numeric_limits<double>::infinity();
double maximum = -std::numeric_limits<double>::infinity();
for (int action = 0; action < kBoardSize; ++action) {
if (!legal[action]) continue;
minimum = std::min(minimum, values[action]);
maximum = std::max(maximum, values[action]);
}
const double range = std::max(1.0e-9, maximum - minimum);
std::array<double, kBoardSize> result{};
for (int action = 0; action < kBoardSize; ++action) {
if (legal[action]) result[action] = (values[action] - minimum) / range;
}
return result;
}
constexpr int kRelativeBoardFeatures = kBoardSize * 13 * 10;
constexpr int kRelativeHeightFeatures = 13 * 8;
constexpr int kActionFeatures = kBoardSize;
constexpr int kActionNextFeatures = kBoardSize * kBoardSize;
constexpr int kActionPhaseFeatures = kBoardSize * kMovesPerLevel;
constexpr int kNextOwnCellFeatures = kBoardSize * kBoardSize * 10;
constexpr int kPhaseHeightFeatures = kMovesPerLevel * 8;
constexpr int kDenseFeatures = 12;
constexpr int kFeatureCount =
kRelativeBoardFeatures + kRelativeHeightFeatures + kActionFeatures +
kActionNextFeatures + kActionPhaseFeatures + kNextOwnCellFeatures +
kPhaseHeightFeatures + kDenseFeatures;
constexpr double kBaseLogitScale = 5.0;
constexpr double kPairwiseLossWeight = 0.25;
constexpr int kDesignEpochs = 100;
constexpr int kDesignBatchSize = 64;
constexpr double kDesignLearningRate = 0.01;
constexpr std::uint32_t kTrainingSeedStart = 0x3df2'0000u;
constexpr std::uint32_t kHeldoutSeedStart = 0x3df3'0000u;
constexpr int kTrainingGames = 24;
constexpr int kHeldoutGames = 12;
constexpr int kCollectionMaximumMoves = 250;
constexpr int kParallelism = 4;
constexpr double kMaximumProjectedWallSeconds = 45.0 * 60.0;
constexpr double kProjectionSafetyFactor = 1.25;
constexpr int kMinimumTrainingLabels = 1'200;
constexpr int kMinimumHeldoutLabels = 600;
constexpr double kMinimumHeldoutTop1 = 0.55;
constexpr double kMinimumHeldoutTop2 = 0.70;
constexpr double kMinimumHeldoutPairwise = 0.72;
constexpr double kMinimumHalfTop1 = 0.50;
constexpr double kMinimumHalfPairwise = 0.72;
constexpr double kMaximumHeldoutRegret = 0.18;
constexpr double kMinimumTop1ImprovementOverD2 = 0.01;
constexpr double kMinimumTop2ImprovementOverD2 = 0.005;
constexpr double kMinimumPairwiseImprovementOverD2 = 0.005;
constexpr double kMaximumRegretRatioToD2 = 0.95;
constexpr std::uint64_t kMaximumCheckpointBytes = 32'768;
constexpr double kMinimumInferenceRootsPerSecond = 250.0;
constexpr double kMinimumRolloutScoreRatio = 0.55;
constexpr double kMinimumRolloutMoveRatio = 0.55;
constexpr double kMinimumRolloutThroughputRatio = 0.85;
static_assert(kTrainingSeedStart + kTrainingGames < kHeldoutSeedStart);
static_assert(kHeldoutSeedStart + kHeldoutGames < 0x3e00'0000u);
static_assert((kTrainingSeedStart >> 24) != 0x7du &&
(kTrainingSeedStart >> 24) != 0xd7u);
static_assert((kHeldoutSeedStart >> 24) != 0x7du &&
(kHeldoutSeedStart >> 24) != 0xd7u);
static_assert(kFeatureCount == 1'647);
struct SparseFeature {
std::uint16_t index = 0;
double value = 0.0;
};
using FeatureVector = std::vector<SparseFeature>;
std::array<int, kBoardSize> columnHeights(const Board& board) {
return cfpi::detail::columnHeights(board);
}
FeatureVector actionFeatures(
const Board& board, std::uint8_t next_disc, int moves_remaining,
int action, double d1, double immediate, double d2) {
FeatureVector result;
result.reserve(84);
const auto heights = columnHeights(board);
int offset = 0;
for (int row = 0; row < kBoardSize; ++row) {
for (int column = 0; column < kBoardSize; ++column) {
const int relative = column - action + 6;
const int cell = row * kBoardSize + column;
const int kind = static_cast<int>(board[cell]);
result.push_back({static_cast<std::uint16_t>(
offset + (row * 13 + relative) * 10 + kind),
1.0});
}
}
offset += kRelativeBoardFeatures;
for (int column = 0; column < kBoardSize; ++column) {
const int relative = column - action + 6;
result.push_back({static_cast<std::uint16_t>(
offset + relative * 8 + heights[column]),
1.0});
}
offset += kRelativeHeightFeatures;
result.push_back(
{static_cast<std::uint16_t>(offset + action), 1.0});
offset += kActionFeatures;
result.push_back({static_cast<std::uint16_t>(
offset + action * kBoardSize + next_disc - 1),
1.0});
offset += kActionNextFeatures;
result.push_back({static_cast<std::uint16_t>(
offset + action * kMovesPerLevel + moves_remaining - 1),
1.0});
offset += kActionPhaseFeatures;
for (int row = 0; row < kBoardSize; ++row) {
const int kind = static_cast<int>(board[row * kBoardSize + action]);
result.push_back({static_cast<std::uint16_t>(
offset + ((next_disc - 1) * kBoardSize + row) * 10 +
kind),
1.0});
}
offset += kNextOwnCellFeatures;
result.push_back({static_cast<std::uint16_t>(
offset + (moves_remaining - 1) * 8 + heights[action]),
1.0});
offset += kPhaseHeightFeatures;
const double height = static_cast<double>(heights[action]) / kBoardSize;
const double left = action > 0
? static_cast<double>(heights[action - 1]) / kBoardSize
: 1.0;
const double right = action + 1 < kBoardSize
? static_cast<double>(heights[action + 1]) /
kBoardSize
: 1.0;
const double center = 1.0 - std::abs(action - 3) / 3.0;
const double phase = static_cast<double>(moves_remaining) / kMovesPerLevel;
const std::array<double, kDenseFeatures> dense{{
d1,
immediate,
height,
0.5 * (left + right),
std::abs(left - right),
center,
height * height,
d1 * height,
immediate * phase,
heights[action] >= kBoardSize - 1 ? 1.0 : 0.0,
d1 - d2,
d2 * height,
}};
for (int index = 0; index < kDenseFeatures; ++index) {
result.push_back({static_cast<std::uint16_t>(offset + index),
dense[index]});
}
if (offset + kDenseFeatures != kFeatureCount) {
throw std::logic_error("scaled-distill feature layout mismatch");
}
return result;
}
struct PreparedRoot {
RootLabel label{};
std::array<double, kBoardSize> target{};
std::array<double, kBoardSize> d1{};
std::array<double, kBoardSize> d2{};
std::array<double, kBoardSize> immediate{};
std::array<FeatureVector, kBoardSize> direct{};
std::array<FeatureVector, kBoardSize> reflected{};
};
PreparedRoot prepare(const RootLabel& label) {
PreparedRoot result;
result.label = label;
const ShallowRoot d1 = shallowRoot(label, 1);
const ShallowRoot d2 = shallowRoot(label, 2);
result.target = normalized(label.q, label.legal);
result.d1 = normalized(d1.values, label.legal);
result.d2 = normalized(d2.values, label.legal);
result.immediate = normalized(d1.expected_scores, label.legal);
const Board reflected_board = cfpi::detail::mirrorBoard(label.board);
for (int action = 0; action < kBoardSize; ++action) {
if (!label.legal[action]) continue;
result.direct[action] = actionFeatures(
label.board, label.next_disc, label.moves_remaining, action,
result.d1[action], result.immediate[action], result.d2[action]);
result.reflected[action] = actionFeatures(
reflected_board, label.next_disc, label.moves_remaining,
kBoardSize - 1 - action, result.d1[action], result.immediate[action],
result.d2[action]);
}
return result;
}
std::vector<PreparedRoot> prepareAll(const std::vector<RootLabel>& labels) {
std::vector<PreparedRoot> result;
result.reserve(labels.size());
for (const RootLabel& label : labels) result.push_back(prepare(label));
return result;
}
using LinearModel = std::array<double, kFeatureCount>;
double dot(const LinearModel& model, const FeatureVector& features) {
double result = 0.0;
for (const SparseFeature& feature : features) {
result += model[feature.index] * feature.value;
}
return result;
}
std::array<double, kBoardSize> modelScores(const LinearModel& model,
const PreparedRoot& root) {
std::array<double, kBoardSize> result{};
for (int action = 0; action < kBoardSize; ++action) {
if (!root.label.legal[action]) {
result[action] = -std::numeric_limits<double>::infinity();
continue;
}
const double residual =
0.5 * (dot(model, root.direct[action]) +
dot(model, root.reflected[action]));
result[action] = kBaseLogitScale * root.d2[action] + residual;
}
return result;
}
std::array<double, kBoardSize> softmax(
const std::array<double, kBoardSize>& values,
const std::array<bool, kBoardSize>& legal) {
double maximum = -std::numeric_limits<double>::infinity();
for (int action = 0; action < kBoardSize; ++action) {
if (legal[action]) maximum = std::max(maximum, values[action]);
}
std::array<double, kBoardSize> result{};
double total = 0.0;
for (int action = 0; action < kBoardSize; ++action) {
if (!legal[action]) continue;
result[action] = std::exp(values[action] - maximum);
total += result[action];
}
if (!(total > 0.0)) throw std::logic_error("scaled softmax failed");
for (double& value : result) value /= total;
return result;
}
void addFeatureGradient(LinearModel& gradient, const FeatureVector& features,
double scale) {
for (const SparseFeature& feature : features) {
gradient[feature.index] += scale * feature.value;
}
}
void accumulateGradient(const LinearModel& model, const PreparedRoot& root,
LinearModel& gradient) {
const auto score = modelScores(model, root);
std::array<double, kBoardSize> target_logit{};
for (int action = 0; action < kBoardSize; ++action) {
if (root.label.legal[action]) {
target_logit[action] = kBaseLogitScale * root.target[action];
}
}
const auto predicted = softmax(score, root.label.legal);
const auto target = softmax(target_logit, root.label.legal);
std::array<double, kBoardSize> score_gradient{};
for (int action = 0; action < kBoardSize; ++action) {
if (root.label.legal[action]) {
score_gradient[action] = predicted[action] - target[action];
}
}
double pair_weight = 0.0;
std::array<double, kBoardSize> pair_gradient{};
for (int first = 0; first < kBoardSize; ++first) {
if (!root.label.legal[first]) continue;
for (int second = first + 1; second < kBoardSize; ++second) {
if (!root.label.legal[second]) continue;
const double difference = root.target[first] - root.target[second];
if (std::abs(difference) <= kTieTolerance) continue;
const int better = difference > 0.0 ? first : second;
const int worse = difference > 0.0 ? second : first;
const double weight = 0.25 + 0.75 * std::abs(difference);
const double margin = score[better] - score[worse];
const double derivative = -weight / (1.0 + std::exp(margin));
pair_gradient[better] += derivative;
pair_gradient[worse] -= derivative;
pair_weight += weight;
}
}
if (pair_weight > 0.0) {
for (int action = 0; action < kBoardSize; ++action) {
score_gradient[action] +=
kPairwiseLossWeight * pair_gradient[action] / pair_weight;
}
}
for (int action = 0; action < kBoardSize; ++action) {
if (!root.label.legal[action]) continue;
addFeatureGradient(gradient, root.direct[action],
0.5 * score_gradient[action]);
addFeatureGradient(gradient, root.reflected[action],
0.5 * score_gradient[action]);
}
}
void deterministicShuffle(std::vector<std::size_t>& values,
std::uint32_t seed) {
for (std::size_t remaining = values.size(); remaining > 1; --remaining) {
seed = mix32(seed + static_cast<std::uint32_t>(remaining));
std::swap(values[remaining - 1], values[seed % remaining]);
}
}
LinearModel trainLinear(const std::vector<PreparedRoot>& training, double l2) {
LinearModel model{};
LinearModel first_moment{};
LinearModel second_moment{};
std::vector<std::size_t> order(training.size());
std::iota(order.begin(), order.end(), 0);
std::uint64_t step = 0;
for (int epoch = 0; epoch < kDesignEpochs; ++epoch) {
deterministicShuffle(order,
0x5ca1'0000u + static_cast<std::uint32_t>(epoch));
for (std::size_t begin = 0; begin < training.size();
begin += kDesignBatchSize) {
const std::size_t end =
std::min(training.size(), begin + kDesignBatchSize);
LinearModel gradient{};
for (std::size_t index = begin; index < end; ++index) {
accumulateGradient(model, training[order[index]], gradient);
}
++step;
const double inverse_batch = 1.0 / static_cast<double>(end - begin);
const double first_correction = 1.0 - std::pow(0.9, step);
const double second_correction = 1.0 - std::pow(0.999, step);
for (int parameter = 0; parameter < kFeatureCount; ++parameter) {
const double value =
gradient[parameter] * inverse_batch + l2 * model[parameter];
first_moment[parameter] =
0.9 * first_moment[parameter] + 0.1 * value;
second_moment[parameter] =
0.999 * second_moment[parameter] + 0.001 * value * value;
model[parameter] -=
kDesignLearningRate * (first_moment[parameter] / first_correction) /
(std::sqrt(second_moment[parameter] / second_correction) + 1.0e-8);
}
}
}
return model;
}
void observe(const RootLabel& label,
const std::array<double, kBoardSize>& prediction,
Ranking& result);
Ranking evaluateModel(const LinearModel& model,
const std::vector<PreparedRoot>& roots) {
Ranking result;
for (const PreparedRoot& root : roots) {
observe(root.label, modelScores(model, root), result);
}
return result;
}
void observe(const RootLabel& label,
const std::array<double, kBoardSize>& prediction,
Ranking& result) {
std::vector<int> ranked;
for (const int action : kActionOrder) {
if (label.legal[action]) ranked.push_back(action);
}
std::stable_sort(ranked.begin(), ranked.end(), [&](int left, int right) {
return prediction[left] > prediction[right];
});
if (ranked.empty()) throw std::logic_error("empty ranking root");
double maximum = -std::numeric_limits<double>::infinity();
double minimum = std::numeric_limits<double>::infinity();
for (int action = 0; action < kBoardSize; ++action) {
if (!label.legal[action]) continue;
maximum = std::max(maximum, label.q[action]);
minimum = std::min(minimum, label.q[action]);
}
++result.roots;
result.top1 += qTied(label.q[ranked.front()], maximum);
for (std::size_t index = 0; index < std::min<std::size_t>(2, ranked.size());
++index) {
if (qTied(label.q[ranked[index]], maximum)) {
++result.top2;
break;
}
}
const double range = std::max(1.0e-9, maximum - minimum);
result.normalized_regret +=
(maximum - label.q[ranked.front()]) / range;
for (int first = 0; first < kBoardSize; ++first) {
if (!label.legal[first]) continue;
for (int second = first + 1; second < kBoardSize; ++second) {
if (!label.legal[second] ||
qTied(label.q[first], label.q[second])) {
continue;
}
const double predicted = prediction[first] - prediction[second];
if (std::abs(predicted) <= kTieTolerance) {
result.pairwise_credit += 0.5;
} else {
result.pairwise_credit +=
((predicted > 0.0) == (label.q[first] > label.q[second])) ? 1.0
: 0.0;
}
++result.pairs;
}
}
}
double top1Rate(const Ranking& value) {
return static_cast<double>(value.top1) / value.roots;
}
double top2Rate(const Ranking& value) {
return static_cast<double>(value.top2) / value.roots;
}
double pairwiseRate(const Ranking& value) {
return value.pairwise_credit / value.pairs;
}
double regret(const Ranking& value) {
return value.normalized_regret / value.roots;
}
Ranking evaluateRange(const LinearModel* model,
const std::vector<PreparedRoot>& roots, int game_begin,
int game_end) {
Ranking result;
for (const PreparedRoot& root : roots) {
if (root.label.game < game_begin || root.label.game >= game_end) continue;
observe(root.label, model == nullptr ? root.d2 : modelScores(*model, root),
result);
}
if (result.roots <= 0 || result.pairs == 0) {
throw std::logic_error("empty scaled-distill game range");
}
return result;
}
std::uint64_t fingerprint(const LinearModel& model) {
std::uint64_t hash = 0xcbf2'9ce4'8422'2325ull;
for (const double value : model) {
std::uint64_t bits = std::bit_cast<std::uint64_t>(value);
for (int byte = 0; byte < 8; ++byte) {
hash ^= bits & 0xffu;
hash *= 0x0000'0100'0000'01b3ull;
bits >>= 8;
}
}
return hash;
}
constexpr std::array<char, 8> kCheckpointMagic{{
'D', '7', 'S', 'D', '4', 'R', '1', '\0',
}};
struct CheckpointHeader {
std::array<char, 8> magic{};
std::uint32_t feature_count = 0;
std::uint32_t epochs = 0;
double l2 = 0.0;
double base_scale = 0.0;
std::uint64_t fingerprint = 0;
};
void writeCheckpoint(const std::string& path, const LinearModel& model) {
std::ofstream output(path, std::ios::binary);
if (!output) throw std::runtime_error("could not write distill checkpoint");
const CheckpointHeader header{kCheckpointMagic, kFeatureCount, kDesignEpochs,
0.03, kBaseLogitScale, fingerprint(model)};
output.write(reinterpret_cast<const char*>(&header), sizeof(header));
output.write(reinterpret_cast<const char*>(model.data()),
static_cast<std::streamsize>(sizeof(model)));
if (!output) throw std::runtime_error("distill checkpoint write failed");
}
LinearModel readCheckpoint(const std::string& path) {
std::ifstream input(path, std::ios::binary);
if (!input) throw std::runtime_error("could not read distill checkpoint");
CheckpointHeader header;
LinearModel model{};
input.read(reinterpret_cast<char*>(&header), sizeof(header));
input.read(reinterpret_cast<char*>(model.data()),
static_cast<std::streamsize>(sizeof(model)));
const bool payload_ok = static_cast<bool>(input);
char trailing = 0;
const bool has_trailing = static_cast<bool>(input.read(&trailing, 1));
if (!payload_ok || !input.eof() || has_trailing ||
header.magic != kCheckpointMagic ||
header.feature_count != kFeatureCount ||
header.epochs != kDesignEpochs || header.l2 != 0.03 ||
header.base_scale != kBaseLogitScale ||
header.fingerprint != fingerprint(model)) {
throw std::runtime_error("invalid distill checkpoint");
}
return model;
}
std::uint64_t fileBytes(const std::string& path) {
std::ifstream input(path, std::ios::binary | std::ios::ate);
if (!input) throw std::runtime_error("could not size distill checkpoint");
const std::streampos end = input.tellg();
if (end < 0) throw std::runtime_error("invalid distill checkpoint size");
return static_cast<std::uint64_t>(end);
}
PreparedRoot prepareInference(const State& canonical) {
PreparedRoot result;
RootLabel& label = result.label;
label.board = canonical.board;
label.next_disc = canonical.next_disc;
label.moves_remaining = canonical.moves_remaining;
for (int action = 0; action < kBoardSize; ++action) {
label.legal[action] = isLegal(label.board, action);
}
const ShallowRoot d1 = shallowRoot(label, 1);
const ShallowRoot d2 = shallowRoot(label, 2);
result.target = normalized(d2.values, label.legal);
result.d1 = normalized(d1.values, label.legal);
result.d2 = result.target;
result.immediate = normalized(d1.expected_scores, label.legal);
const Board reflected_board = cfpi::detail::mirrorBoard(label.board);
for (int action = 0; action < kBoardSize; ++action) {
if (!label.legal[action]) continue;
result.direct[action] = actionFeatures(
label.board, label.next_disc, label.moves_remaining, action,
result.d1[action], result.immediate[action], result.d2[action]);
result.reflected[action] = actionFeatures(
reflected_board, label.next_disc, label.moves_remaining,
kBoardSize - 1 - action, result.d1[action], result.immediate[action],
result.d2[action]);
}
return result;
}
int studentAction(const LinearModel& model, const State& source) {
if (source.game_over) return -1;
bool mirrored = false;
const State canonical = cfpi::detail::canonicalState(source, mirrored);
const PreparedRoot root = prepareInference(canonical);
const auto score = modelScores(model, root);
int selected = -1;
double best = -std::numeric_limits<double>::infinity();
for (const int action : kActionOrder) {
if (!root.label.legal[action]) continue;
if (selected < 0 || score[action] > best) {
selected = action;
best = score[action];
}
}
if (selected < 0) return -1;
return mirrored ? kBoardSize - 1 - selected : selected;
}
int d2Action(const State& source) {
if (source.game_over) return -1;
bool mirrored = false;
const State canonical = cfpi::detail::canonicalState(source, mirrored);
fair::SearchContext context;
const fair::RootEvaluation root = fair::rootDecision(canonical, 2, context);
int selected = -1;
double best = -std::numeric_limits<double>::infinity();
for (const int action : kActionOrder) {
if (!isLegal(canonical.board, action)) continue;
if (selected < 0 || root.values[action] > best) {
selected = action;
best = root.values[action];
}
}
if (selected < 0) return -1;
return mirrored ? kBoardSize - 1 - selected : selected;
}
struct Throughput {
int roots = 0;
double seconds = 0.0;
double roots_per_second = 0.0;
std::uint64_t checksum = 0;
};
Throughput benchmarkInference(const LinearModel& model,
const std::vector<RootLabel>& labels) {
constexpr int evaluations = 2'000;
const auto started = std::chrono::steady_clock::now();
std::uint64_t checksum = 0;
for (int index = 0; index < evaluations; ++index) {
const RootLabel& label = labels[static_cast<std::size_t>(index) %
labels.size()];
checksum = checksum * 11u +
static_cast<std::uint64_t>(studentAction(model,
publicState(label)) +
1);
}
const double seconds = std::chrono::duration<double>(
std::chrono::steady_clock::now() - started)
.count();
return {evaluations, seconds, evaluations / seconds, checksum};
}
Throughput benchmarkD2Inference(const std::vector<RootLabel>& labels) {
constexpr int evaluations = 2'000;
const auto started = std::chrono::steady_clock::now();
std::uint64_t checksum = 0;
for (int index = 0; index < evaluations; ++index) {
const RootLabel& label = labels[static_cast<std::size_t>(index) %
labels.size()];
checksum = checksum * 11u +
static_cast<std::uint64_t>(d2Action(publicState(label)) + 1);
}
const double seconds = std::chrono::duration<double>(
std::chrono::steady_clock::now() - started)
.count();
return {evaluations, seconds, evaluations / seconds, checksum};
}
struct SearchCost {
int roots = 0;
double d2_work_per_root = 0.0;
double d2_nodes_per_root = 0.0;
double d2_cache_hits_per_root = 0.0;
double d1_plus_d2_work_per_root = 0.0;
std::uint64_t maximum_d2_work = 0;
std::size_t maximum_d2_cache_entries = 0;
};
SearchCost measureSearchCost(const std::vector<RootLabel>& labels) {
SearchCost result;
result.roots = static_cast<int>(labels.size());
std::uint64_t d2_work = 0;
std::uint64_t d2_nodes = 0;
std::uint64_t d2_cache_hits = 0;
std::uint64_t combined_work = 0;
for (const RootLabel& label : labels) {
const State state = publicState(label);
fair::SearchContext d1_context;
fair::SearchContext d2_context;
(void)fair::rootDecision(state, 1, d1_context);
(void)fair::rootDecision(state, 2, d2_context);
d2_work += d2_context.work;
d2_nodes += d2_context.nodes;
d2_cache_hits += d2_context.cache_hits;
combined_work += d1_context.work + d2_context.work;
result.maximum_d2_work =
std::max(result.maximum_d2_work, d2_context.work);
result.maximum_d2_cache_entries =
std::max(result.maximum_d2_cache_entries, d2_context.cache.size());
}
result.d2_work_per_root = static_cast<double>(d2_work) / result.roots;
result.d2_nodes_per_root = static_cast<double>(d2_nodes) / result.roots;
result.d2_cache_hits_per_root =
static_cast<double>(d2_cache_hits) / result.roots;
result.d1_plus_d2_work_per_root =
static_cast<double>(combined_work) / result.roots;
return result;
}
std::mutex progress_mutex;
std::uint64_t peakRssBytes() {
rusage usage{};
if (getrusage(RUSAGE_SELF, &usage) != 0) return 0;
#if defined(__APPLE__)
return static_cast<std::uint64_t>(usage.ru_maxrss);
#else
return static_cast<std::uint64_t>(usage.ru_maxrss) * 1024u;
#endif
}
struct GameResult {
std::uint32_t seed = 0;
std::int64_t score = 0;
int moves = 0;
bool censored = false;
std::uint64_t cleared = 0;
std::uint64_t revealed = 0;
int maximum_chain = 0;
std::uint64_t teacher_work = 0;
double elapsed_seconds = 0.0;
};
struct CollectedGame {
GameResult game{};
std::vector<RootLabel> labels;
};
void observeMove(const MoveResult& move, GameResult& result) {
result.maximum_chain =
std::max(result.maximum_chain, static_cast<int>(move.waves.size()));
for (const Wave& wave : move.waves) {
result.cleared += static_cast<std::uint64_t>(wave.cleared);
result.revealed += static_cast<std::uint64_t>(wave.revealed);
}
}
RootLabel behaviorLabel(const State& source,
const fair::SearchDecision& decision) {
const bool mirrored =
cfpi::detail::mirroredRepresentationIsSmaller(source.board);
RootLabel result;
result.board = mirrored ? cfpi::detail::mirrorBoard(source.board)
: source.board;
result.next_disc = source.next_disc;
result.moves_remaining = source.moves_remaining;
result.labeled_action =
mirrored ? kBoardSize - 1 - decision.action : decision.action;
for (int canonical_action = 0; canonical_action < kBoardSize;
++canonical_action) {
const int source_action = mirrored ? kBoardSize - 1 - canonical_action
: canonical_action;
result.legal[canonical_action] = isLegal(result.board, canonical_action);
result.q[canonical_action] = decision.root_values[source_action];
if (result.legal[canonical_action] !=
std::isfinite(result.q[canonical_action])) {
throw std::runtime_error("teacher label legal/Q mismatch");
}
}
double maximum = -std::numeric_limits<double>::infinity();
for (int action = 0; action < kBoardSize; ++action) {
if (result.legal[action]) maximum = std::max(maximum, result.q[action]);
}
if (!result.legal[result.labeled_action] ||
!qTied(result.q[result.labeled_action], maximum)) {
throw std::runtime_error("teacher label action is not Q-optimal");
}
return result;
}
void reportGame(std::string_view label, const GameResult& game) {
const std::lock_guard<std::mutex> lock(progress_mutex);
std::cerr << "scaled-d4 " << label << " seed 0x" << std::hex << game.seed
<< std::dec << ' ' << game.score << " (" << game.moves
<< " moves" << (game.censored ? ", capped" : "")
<< ", clears " << game.cleared << ", reveals " << game.revealed
<< ", " << std::fixed << std::setprecision(3)
<< game.elapsed_seconds << "s)\n";
}
CollectedGame collectTeacherGame(std::uint32_t seed,
std::string_view split) {
const auto started = std::chrono::steady_clock::now();
State state = initialHeadlessState(seed);
CollectedGame result;
result.game.seed = seed;
result.labels.reserve(kCollectionMaximumMoves);
while (!state.game_over && state.moves_played < kCollectionMaximumMoves) {
const fair::SearchDecision decision = fair::chooseDepth4Action(state);
if (!decision.complete || decision.completed_depth != fair::kCandidateDepth ||
!isLegal(state.board, decision.action)) {
throw std::runtime_error("scaled-distill teacher decision failed");
}
RootLabel label = behaviorLabel(state, decision);
label.move_in_game = state.moves_played;
result.labels.push_back(std::move(label));
result.game.teacher_work += decision.work;
MoveResult move;
if (!playHeadlessMove(state, seed, decision.action, move)) {
throw std::runtime_error("scaled-distill teacher transition failed");
}
observeMove(move, result.game);
}
result.game.score = state.score;
result.game.moves = state.moves_played;
result.game.censored = !state.game_over;
result.game.elapsed_seconds = std::chrono::duration<double>(
std::chrono::steady_clock::now() - started)
.count();
reportGame(split, result.game);
return result;
}
std::vector<CollectedGame> collectTeacherRange(std::uint32_t seed_start,
int games,
std::string_view split) {
std::vector<CollectedGame> result(games);
std::atomic<int> next_game{0};
std::vector<std::future<void>> workers;
for (int worker = 0; worker < std::min(kParallelism, games); ++worker) {
workers.push_back(std::async(std::launch::async, [&] {
for (;;) {
const int game = next_game.fetch_add(1);
if (game >= games) return;
result[game] = collectTeacherGame(
seed_start + static_cast<std::uint32_t>(game), split);
}
}));
}
for (auto& worker : workers) worker.get();
return result;
}
std::vector<RootLabel> flattenLabels(
const std::vector<CollectedGame>& games) {
std::vector<RootLabel> result;
for (std::size_t game = 0; game < games.size(); ++game) {
for (RootLabel label : games[game].labels) {
label.game = static_cast<int>(game);
result.push_back(std::move(label));
}
}
return result;
}
GameResult runStudentGame(std::uint32_t seed, const LinearModel& model) {
const auto started = std::chrono::steady_clock::now();
State state = initialHeadlessState(seed);
GameResult result;
result.seed = seed;
while (!state.game_over && state.moves_played < kCollectionMaximumMoves) {
const int action = studentAction(model, state);
if (!isLegal(state.board, action)) {
throw std::runtime_error("scaled-distill student chose illegal action");
}
MoveResult move;
if (!playHeadlessMove(state, seed, action, move)) {
throw std::runtime_error("scaled-distill student transition failed");
}
observeMove(move, result);
}
result.score = state.score;
result.moves = state.moves_played;
result.censored = !state.game_over;
result.elapsed_seconds = std::chrono::duration<double>(
std::chrono::steady_clock::now() - started)
.count();
reportGame("student-sanity", result);
return result;
}
std::vector<GameResult> runStudentRange(std::uint32_t seed_start, int games,
const LinearModel& model) {
std::vector<GameResult> result(games);
std::atomic<int> next_game{0};
std::vector<std::future<void>> workers;
for (int worker = 0; worker < std::min(kParallelism, games); ++worker) {
workers.push_back(std::async(std::launch::async, [&] {
for (;;) {
const int game = next_game.fetch_add(1);
if (game >= games) return;
result[game] = runStudentGame(
seed_start + static_cast<std::uint32_t>(game), model);
}
}));
}
for (auto& worker : workers) worker.get();
return result;
}
struct GameSummary {
int games = 0;
double mean_score = 0.0;
double mean_moves = 0.0;
int censored = 0;
double clears_per_move = 0.0;
double reveals_per_move = 0.0;
double moves_per_second = 0.0;
};
GameSummary summarize(const std::vector<GameResult>& games) {
if (games.empty()) throw std::logic_error("empty game summary");
GameSummary result;
result.games = static_cast<int>(games.size());
double total_seconds = 0.0;
std::uint64_t total_moves = 0;
std::uint64_t total_cleared = 0;
std::uint64_t total_revealed = 0;
for (const GameResult& game : games) {
result.mean_score +=
static_cast<double>(game.score) / static_cast<double>(games.size());
result.mean_moves +=
static_cast<double>(game.moves) / static_cast<double>(games.size());
result.censored += game.censored;
total_moves += static_cast<std::uint64_t>(game.moves);
total_cleared += game.cleared;
total_revealed += game.revealed;
total_seconds += game.elapsed_seconds;
}
if (total_moves > 0) {
result.clears_per_move =
static_cast<double>(total_cleared) / total_moves;
result.reveals_per_move =
static_cast<double>(total_revealed) / total_moves;
}
if (total_seconds > 0.0) result.moves_per_second = total_moves / total_seconds;
return result;
}
GameSummary summarizeTeacher(const std::vector<CollectedGame>& games) {
std::vector<GameResult> values;
values.reserve(games.size());
for (const CollectedGame& game : games) values.push_back(game.game);
return summarize(values);
}
void writeRanking(std::ostream& output, std::string_view label,
const Ranking& value) {
if (value.roots <= 0 || value.pairs == 0) {
throw std::logic_error("empty ranking summary");
}
output << std::setprecision(10) << '"' << label << "\":{\"roots\":"
<< value.roots << ",\"top1WithTies\":" << top1Rate(value)
<< ",\"top2ContainsOptimal\":" << top2Rate(value)
<< ",\"pairwisePairs\":" << value.pairs
<< ",\"pairwiseAccuracy\":" << pairwiseRate(value)
<< ",\"normalizedRegret\":" << regret(value) << '}';
}
void printRanking(std::string_view label, const Ranking& value) {
writeRanking(std::cout, label, value);
}
int designOld(const std::string& labels) {
const std::vector<RootLabel> training = loadSplit(labels, "training");
const std::vector<RootLabel> heldout = loadSplit(labels, "heldout");
const std::vector<PreparedRoot> prepared_training = prepareAll(training);
const std::vector<PreparedRoot> prepared_heldout = prepareAll(heldout);
Ranking train_d1;
Ranking train_d2;
Ranking heldout_d1;
Ranking heldout_d2;
for (const PreparedRoot& root : prepared_training) {
observe(root.label, root.d1, train_d1);
observe(root.label, root.d2, train_d2);
}
for (const PreparedRoot& root : prepared_heldout) {
observe(root.label, root.d1, heldout_d1);
observe(root.label, root.d2, heldout_d2);
}
std::cout << "SCALED_D4_DESIGN {";
printRanking("trainingD1", train_d1);
std::cout << ',';
printRanking("trainingD2", train_d2);
std::cout << ',';
printRanking("heldoutD1", heldout_d1);
std::cout << ',';
printRanking("heldoutD2", heldout_d2);
constexpr std::array<double, 6> l2_grid{{
0.001, 0.003, 0.01, 0.03, 0.1, 0.3,
}};
std::cout << ",\"linearResidualGrid\":[";
for (std::size_t index = 0; index < l2_grid.size(); ++index) {
if (index != 0) std::cout << ',';
const LinearModel model = trainLinear(prepared_training, l2_grid[index]);
const Ranking training_metrics =
evaluateModel(model, prepared_training);
const Ranking heldout_metrics = evaluateModel(model, prepared_heldout);
std::cout << "{\"l2\":" << l2_grid[index] << ',';
printRanking("training", training_metrics);
std::cout << ',';
printRanking("heldout", heldout_metrics);
std::cout << '}';
}
std::cout << ']';
std::cout << "}\n";
return 0;
}
struct Options {
std::string output = "/tmp/drop7-scaled-d4-distill.json";
std::string checkpoint = "/tmp/drop7-scaled-d4-distill.bin";
std::string labels = "/tmp/drop7-scaled-d4-distill-labels.jsonl";
};
Options parseOptions(int argc, char** argv, int begin) {
Options result;
for (int index = begin; index < argc; index += 2) {
if (index + 1 >= argc) {
throw std::invalid_argument("missing scaled-distill option value");
}
const std::string flag = argv[index];
if (flag == "--output") {
result.output = argv[index + 1];
} else if (flag == "--checkpoint") {
result.checkpoint = argv[index + 1];
} else if (flag == "--labels") {
result.labels = argv[index + 1];
} else {
throw std::invalid_argument("unknown scaled-distill option " + flag);
}
}
return result;
}
void writeLabels(const std::string& path,
const std::vector<RootLabel>& training,
const std::vector<RootLabel>& heldout) {
std::ofstream output(path);
if (!output) throw std::runtime_error("could not write scaled labels");
output << std::setprecision(17)
<< "{\"format\":\"drop7-public-d4-root-labels-v2\","
"\"wholeGameSplit\":true,\"trainingSeedStart\":"
<< kTrainingSeedStart << ",\"trainingGames\":" << kTrainingGames
<< ",\"trainingRecords\":" << training.size()
<< ",\"heldoutSeedStart\":" << kHeldoutSeedStart
<< ",\"heldoutGames\":" << kHeldoutGames
<< ",\"heldoutRecords\":" << heldout.size()
<< ",\"maximumMoves\":" << kCollectionMaximumMoves
<< ",\"excluded\":[\"gameSeed\",\"score\",\"level\","
"\"moveIndex\",\"history\",\"futureTape\"]}\n";
const auto write_split = [&](std::string_view split,
const std::vector<RootLabel>& labels) {
for (const RootLabel& label : labels) {
output << "{\"split\":\"" << split << "\",\"board\":\"";
for (const std::uint8_t cell : label.board) {
output << static_cast<char>('0' + cell);
}
output << "\",\"nextDisc\":" << static_cast<int>(label.next_disc)
<< ",\"movesRemaining\":" << label.moves_remaining
<< ",\"action\":" << label.labeled_action
<< ",\"rootQ\":[";
for (int action = 0; action < kBoardSize; ++action) {
if (action != 0) output << ',';
if (label.legal[action]) {
output << label.q[action];
} else {
output << "null";
}
}
output << "]}\n";
}
};
write_split("training", training);
write_split("heldout", heldout);
if (!output) throw std::runtime_error("scaled label write failed");
}
void writeGameSummary(std::ostream& output, const GameSummary& value) {
output << "{\"games\":" << value.games << ",\"meanScore\":"
<< value.mean_score << ",\"meanMoves\":" << value.mean_moves
<< ",\"censored\":" << value.censored
<< ",\"clearsPerMove\":" << value.clears_per_move
<< ",\"revealsPerMove\":" << value.reveals_per_move
<< ",\"movesPerSecond\":" << value.moves_per_second << '}';
}
void writeGames(std::ostream& output,
const std::vector<CollectedGame>& games) {
output << '[';
for (std::size_t index = 0; index < games.size(); ++index) {
if (index != 0) output << ',';
const GameResult& game = games[index].game;
output << "{\"seed\":" << game.seed << ",\"score\":" << game.score
<< ",\"moves\":" << game.moves << ",\"censored\":"
<< (game.censored ? "true" : "false")
<< ",\"labels\":" << games[index].labels.size()
<< ",\"cleared\":" << game.cleared
<< ",\"revealed\":" << game.revealed
<< ",\"maximumChain\":" << game.maximum_chain
<< ",\"teacherWork\":" << game.teacher_work
<< ",\"elapsedSeconds\":" << game.elapsed_seconds << '}';
}
output << ']';
}
void writeStudentGames(std::ostream& output,
const std::vector<GameResult>& games) {
output << '[';
for (std::size_t index = 0; index < games.size(); ++index) {
if (index != 0) output << ',';
const GameResult& game = games[index];
output << "{\"seed\":" << game.seed << ",\"score\":" << game.score
<< ",\"moves\":" << game.moves << ",\"censored\":"
<< (game.censored ? "true" : "false")
<< ",\"cleared\":" << game.cleared
<< ",\"revealed\":" << game.revealed
<< ",\"maximumChain\":" << game.maximum_chain
<< ",\"elapsedSeconds\":" << game.elapsed_seconds << '}';
}
output << ']';
}
bool absoluteRankingGate(const Ranking& all, const Ranking& first_half,
const Ranking& second_half) {
return top1Rate(all) >= kMinimumHeldoutTop1 &&
top2Rate(all) >= kMinimumHeldoutTop2 &&
pairwiseRate(all) >= kMinimumHeldoutPairwise &&
regret(all) <= kMaximumHeldoutRegret &&
top1Rate(first_half) >= kMinimumHalfTop1 &&
top1Rate(second_half) >= kMinimumHalfTop1 &&
pairwiseRate(first_half) >= kMinimumHalfPairwise &&
pairwiseRate(second_half) >= kMinimumHalfPairwise;
}
bool residualImprovesD2(const Ranking& residual, const Ranking& d2) {
return top1Rate(residual) >=
top1Rate(d2) + kMinimumTop1ImprovementOverD2 &&
top2Rate(residual) >=
top2Rate(d2) + kMinimumTop2ImprovementOverD2 &&
pairwiseRate(residual) >=
pairwiseRate(d2) + kMinimumPairwiseImprovementOverD2 &&
regret(residual) <= kMaximumRegretRatioToD2 * regret(d2);
}
bool rolloutGate(const GameSummary& teacher, const GameSummary& candidate) {
return candidate.mean_score >= kMinimumRolloutScoreRatio * teacher.mean_score &&
candidate.mean_moves >= kMinimumRolloutMoveRatio * teacher.mean_moves &&
candidate.clears_per_move >=
kMinimumRolloutThroughputRatio * teacher.clears_per_move &&
candidate.reveals_per_move >=
kMinimumRolloutThroughputRatio * teacher.reveals_per_move;
}
void writePausedArtifact(const Options& options, const CollectedGame& pilot,
double projected_wall, std::string_view reason) {
std::ofstream output(options.output);
if (!output) throw std::runtime_error("could not write paused artifact");
output << std::setprecision(10)
<< "{\n \"experiment\":\"scaled-fair-d4-distillation\",\n"
" \"status\":\"paused-runtime-gate\",\n"
" \"reason\":\""
<< reason << "\",\n \"seedProtocol\":{\"trainingSeedStart\":"
<< kTrainingSeedStart << ",\"trainingSeedsRead\":1,"
"\"heldoutSeedStart\":"
<< kHeldoutSeedStart << ",\"heldoutSeedsRead\":0},\n"
" \"runtimeProjection\":{\"pilotSeconds\":"
<< pilot.game.elapsed_seconds << ",\"projectedWallSeconds\":"
<< projected_wall << ",\"limitSeconds\":"
<< kMaximumProjectedWallSeconds << "},\n \"pilot\":";
writeGames(output, std::vector<CollectedGame>{pilot});
output << ",\n \"protectedSeedFamiliesRead\":false,\n"
" \"peakRssBytes\":"
<< peakRssBytes() << "\n}\n";
}
void writeArtifact(
const Options& options, const std::vector<CollectedGame>& training_games,
const std::vector<CollectedGame>& heldout_games,
const Ranking& training_d2, const Ranking& training_residual,
const Ranking& heldout_d2, const Ranking& heldout_d2_first,
const Ranking& heldout_d2_second, const Ranking& heldout_residual,
const Ranking& heldout_residual_first,
const Ranking& heldout_residual_second, const Throughput& d2_throughput,
const Throughput& residual_throughput, const GameSummary& teacher_summary,
const std::vector<GameResult>* d2_games,
const GameSummary* d2_summary, bool d2_ranking_passed,
bool d2_rollout_passed, const std::vector<GameResult>* residual_games,
const GameSummary* residual_summary, bool residual_ranking_passed,
bool residual_rollout_passed, std::uint64_t checkpoint_bytes,
std::uint64_t model_fingerprint, double pilot_projection,
double total_wall_seconds) {
std::ofstream output(options.output);
if (!output) throw std::runtime_error("could not write distill artifact");
output << std::setprecision(10)
<< "{\n \"experiment\":\"scaled-fair-d4-distillation\",\n"
" \"status\":\"complete\",\n"
" \"claimBoundary\":\"D4 behavior compression only; no stronger-policy claim\",\n"
" \"seedProtocol\":{\"trainingSeedStart\":"
<< kTrainingSeedStart << ",\"trainingGames\":" << kTrainingGames
<< ",\"heldoutSeedStart\":" << kHeldoutSeedStart
<< ",\"heldoutGames\":" << kHeldoutGames
<< ",\"maximumMoves\":" << kCollectionMaximumMoves
<< ",\"wholeGameSplit\":true,\"fresh3eSeedsRead\":0,"
"\"validation7dSeedsRead\":0,\"finalD7SeedsRead\":0},\n"
" \"runtimeGate\":{\"pilotProjectedWallSeconds\":"
<< pilot_projection << ",\"limitSeconds\":"
<< kMaximumProjectedWallSeconds << ",\"passed\":true},\n"
" \"inputBoundary\":{\"included\":[\"board\",\"nextDisc\",\"movesRemaining\"],"
"\"excluded\":[\"gameSeed\",\"score\",\"level\",\"movesPlayed\",\"history\",\"futureTape\"],"
"\"reflection\":\"canonical public state plus two-pass residual\"},\n"
" \"architecture\":{\"anchor\":\"full-width public fair D2/five strata\","
"\"residual\":\"action-relative sparse linear NNUE\","
"\"featureCount\":"
<< kFeatureCount << ",\"checkpointBytes\":" << checkpoint_bytes
<< ",\"checkpointLimitBytes\":" << kMaximumCheckpointBytes
<< ",\"checkpoint\":\"" << options.checkpoint
<< "\",\"fingerprintFnv1a64\":\"0x" << std::hex
<< model_fingerprint << std::dec << "\",\"labels\":\""
<< options.labels << "\"},\n"
" \"training\":{\"epochs\":"
<< kDesignEpochs << ",\"batchSize\":" << kDesignBatchSize
<< ",\"learningRate\":" << kDesignLearningRate
<< ",\"l2\":0.03,\"pairwiseLossWeight\":"
<< kPairwiseLossWeight << ",\"baseLogitScale\":"
<< kBaseLogitScale << ",\"records\":";
std::size_t training_records = 0;
for (const CollectedGame& game : training_games) {
training_records += game.labels.size();
}
std::size_t heldout_records = 0;
for (const CollectedGame& game : heldout_games) {
heldout_records += game.labels.size();
}
output << training_records << ',';
writeRanking(output, "d2", training_d2);
output << ',';
writeRanking(output, "residual", training_residual);
output << "},\n \"heldout\":{\"records\":" << heldout_records << ',';
writeRanking(output, "d2All", heldout_d2);
output << ',';
writeRanking(output, "d2FirstSixGames", heldout_d2_first);
output << ',';
writeRanking(output, "d2SecondSixGames", heldout_d2_second);
output << ',';
writeRanking(output, "residualAll", heldout_residual);
output << ',';
writeRanking(output, "residualFirstSixGames", heldout_residual_first);
output << ',';
writeRanking(output, "residualSecondSixGames", heldout_residual_second);
output << "},\n \"gates\":{\"absoluteMinimums\":{"
"\"top1\":"
<< kMinimumHeldoutTop1 << ",\"top2\":" << kMinimumHeldoutTop2
<< ",\"pairwise\":" << kMinimumHeldoutPairwise
<< ",\"halfTop1\":" << kMinimumHalfTop1
<< ",\"halfPairwise\":" << kMinimumHalfPairwise
<< ",\"maximumRegret\":" << kMaximumHeldoutRegret
<< "},\"residualImprovementMinimums\":{\"top1\":"
<< kMinimumTop1ImprovementOverD2 << ",\"top2\":"
<< kMinimumTop2ImprovementOverD2 << ",\"pairwise\":"
<< kMinimumPairwiseImprovementOverD2
<< ",\"maximumRegretRatio\":" << kMaximumRegretRatioToD2
<< "},\"d2RankingPassed\":"
<< (d2_ranking_passed ? "true" : "false")
<< ",\"residualRankingPassed\":"
<< (residual_ranking_passed ? "true" : "false") << "},\n"
" \"inference\":{\"d2\":{\"roots\":"
<< d2_throughput.roots << ",\"seconds\":" << d2_throughput.seconds
<< ",\"rootsPerSecond\":" << d2_throughput.roots_per_second
<< ",\"checksum\":" << d2_throughput.checksum
<< "},\"residual\":{\"roots\":" << residual_throughput.roots
<< ",\"seconds\":" << residual_throughput.seconds
<< ",\"rootsPerSecond\":"
<< residual_throughput.roots_per_second << ",\"checksum\":"
<< residual_throughput.checksum << "}},\n"
" \"rolloutSanity\":{\"teacher\":";
writeGameSummary(output, teacher_summary);
output << ",\"d2\":";
if (d2_summary == nullptr) {
output << "null";
} else {
writeGameSummary(output, *d2_summary);
}
output << ",\"d2Passed\":" << (d2_rollout_passed ? "true" : "false")
<< ",\"residual\":";
if (residual_summary == nullptr) {
output << "null";
} else {
writeGameSummary(output, *residual_summary);
}
output << ",\"residualPassed\":"
<< (residual_rollout_passed ? "true" : "false") << "},\n"
" \"teacherGames\":";
writeGames(output, heldout_games);
output << ",\n \"d2Games\":";
if (d2_games == nullptr) {
output << "null";
} else {
writeStudentGames(output, *d2_games);
}
output << ",\n \"residualGames\":";
if (residual_games == nullptr) {
output << "null";
} else {
writeStudentGames(output, *residual_games);
}
output << ",\n \"trainingTeacherGames\":";
writeGames(output, training_games);
output << ",\n \"totalWallSeconds\":" << total_wall_seconds
<< ",\n \"peakRssBytes\":" << peakRssBytes() << "\n}\n";
}
bool selfTest(const Options& options, std::ostream& output) {
const bool inherited = fair::selfTest(output);
const State fixture = fair::frozen::fixtureState(
fair::frozen::kTypeScriptFixtures[1]);
const fair::SearchDecision teacher = fair::chooseDepth4Action(fixture);
RootLabel label = behaviorLabel(fixture, teacher);
label.game = 0;
label.move_in_game = 0;
const PreparedRoot prepared = prepare(label);
std::vector<PreparedRoot> tiny(32, prepared);
const LinearModel first = trainLinear(tiny, 0.03);
const LinearModel repeat = trainLinear(tiny, 0.03);
const bool deterministic = fingerprint(first) == fingerprint(repeat);
writeCheckpoint(options.checkpoint, first);
const LinearModel restored = readCheckpoint(options.checkpoint);
const bool checkpoint = fingerprint(first) == fingerprint(restored) &&
fileBytes(options.checkpoint) <=
kMaximumCheckpointBytes;
const int action = studentAction(restored, fixture);
State reflected = fixture;
reflected.board = cfpi::detail::mirrorBoard(fixture.board);
const int reflected_action = studentAction(restored, reflected);
State metadata = fixture;
metadata.score = 9'000'000;
metadata.level = 80;
metadata.moves_played = 490;
const bool reflection = reflected_action == kBoardSize - 1 - action;
const bool metadata_blind = studentAction(restored, metadata) == action;
const bool legal = isLegal(fixture.board, action) &&
isLegal(fixture.board, d2Action(fixture));
LinearModel zero{};
const auto zero_scores = modelScores(zero, prepared);
int direct_d2 = -1;
for (const int candidate : kActionOrder) {
if (!label.legal[candidate]) continue;
if (direct_d2 < 0 ||
zero_scores[candidate] > zero_scores[direct_d2]) {
direct_d2 = candidate;
}
}
const bool anchor = d2Action(fixture) == direct_d2;
bool feature_bounds = true;
for (const auto& by_action : {prepared.direct, prepared.reflected}) {
for (const FeatureVector& features : by_action) {
for (const SparseFeature& feature : features) {
feature_bounds = feature_bounds && feature.index < kFeatureCount &&
std::isfinite(feature.value);
}
}
}
const bool resources = sizeof(LinearModel) ==
static_cast<std::size_t>(kFeatureCount) *
sizeof(double) &&
kFeatureCount == 1'647 &&
fileBytes(options.checkpoint) <= 32'768;
const bool protocol = kTrainingSeedStart == 0x3df2'0000u &&
kHeldoutSeedStart == 0x3df3'0000u &&
kTrainingGames == 24 && kHeldoutGames == 12 &&
kMinimumHeldoutTop1 == 0.55 &&
kMinimumHeldoutPairwise == 0.72 &&
kMinimumTop1ImprovementOverD2 == 0.01;
const bool passed = inherited && deterministic && checkpoint && reflection &&
metadata_blind && legal && anchor && feature_bounds &&
resources && protocol;
output << "SCALED_D4_DISTILL_SELF_TEST {\"passed\":"
<< (passed ? "true" : "false")
<< ",\"inheritedD4\":" << (inherited ? "true" : "false")
<< ",\"deterministicTraining\":"
<< (deterministic ? "true" : "false")
<< ",\"checkpoint\":" << (checkpoint ? "true" : "false")
<< ",\"reflection\":" << (reflection ? "true" : "false")
<< ",\"metadataBlind\":"
<< (metadata_blind ? "true" : "false")
<< ",\"legal\":" << (legal ? "true" : "false")
<< ",\"zeroResidualD2Anchor\":" << (anchor ? "true" : "false")
<< ",\"featureBounds\":" << (feature_bounds ? "true" : "false")
<< ",\"resources\":" << (resources ? "true" : "false")
<< ",\"protocol\":" << (protocol ? "true" : "false")
<< ",\"parameters\":" << kFeatureCount << "}\n";
return passed;
}
int pilotOnly(std::ostream& output) {
const CollectedGame pilot =
collectTeacherGame(kTrainingSeedStart, "pilot-only");
const double projected_wall =
pilot.game.elapsed_seconds *
std::ceil(static_cast<double>(kTrainingGames + kHeldoutGames) /
kParallelism) *
kProjectionSafetyFactor;
output << std::fixed << std::setprecision(4)
<< "SCALED_D4_DISTILL_PILOT {\"seed\":" << pilot.game.seed
<< ",\"score\":" << pilot.game.score
<< ",\"moves\":" << pilot.game.moves
<< ",\"censored\":" << (pilot.game.censored ? "true" : "false")
<< ",\"labels\":" << pilot.labels.size()
<< ",\"elapsedSeconds\":" << pilot.game.elapsed_seconds
<< ",\"projectedWallSeconds\":" << projected_wall
<< ",\"limitSeconds\":" << kMaximumProjectedWallSeconds
<< ",\"passed\":"
<< (projected_wall <= kMaximumProjectedWallSeconds ? "true" : "false")
<< ",\"nextSeedRead\":false}\n";
return 0;
}
int auditExisting(const Options& options, std::ostream& output) {
const auto started = std::chrono::steady_clock::now();
const std::vector<RootLabel> training_labels =
loadSplit(options.labels, "training");
const std::vector<RootLabel> heldout_labels =
loadSplit(options.labels, "heldout");
if (training_labels.size() != 1'885 || heldout_labels.size() != 926) {
throw std::runtime_error("unexpected frozen scaled-distill label counts");
}
const std::vector<PreparedRoot> prepared_training =
prepareAll(training_labels);
const std::vector<PreparedRoot> prepared_heldout = prepareAll(heldout_labels);
const LinearModel frozen = readCheckpoint(options.checkpoint);
const LinearModel repeated = trainLinear(prepared_training, 0.03);
if (fingerprint(frozen) != fingerprint(repeated)) {
throw std::runtime_error("frozen scaled-distill retrain mismatch");
}
const Ranking d2_all =
evaluateRange(nullptr, prepared_heldout, 0, kHeldoutGames);
const Ranking d2_first =
evaluateRange(nullptr, prepared_heldout, 0, kHeldoutGames / 2);
const Ranking d2_second = evaluateRange(
nullptr, prepared_heldout, kHeldoutGames / 2, kHeldoutGames);
const Ranking residual_all =
evaluateRange(&frozen, prepared_heldout, 0, kHeldoutGames);
const Ranking residual_first =
evaluateRange(&frozen, prepared_heldout, 0, kHeldoutGames / 2);
const Ranking residual_second = evaluateRange(
&frozen, prepared_heldout, kHeldoutGames / 2, kHeldoutGames);
const Throughput d2_throughput = benchmarkD2Inference(heldout_labels);
const Throughput residual_throughput =
benchmarkInference(frozen, heldout_labels);
const SearchCost search_cost = measureSearchCost(heldout_labels);
const bool d2_passed =
absoluteRankingGate(d2_all, d2_first, d2_second) &&
d2_throughput.roots_per_second >= kMinimumInferenceRootsPerSecond;
const bool residual_passed =
absoluteRankingGate(residual_all, residual_first, residual_second) &&
residualImprovesD2(residual_all, d2_all) &&
residualImprovesD2(residual_first, d2_first) &&
residualImprovesD2(residual_second, d2_second) &&
residual_throughput.roots_per_second >=
kMinimumInferenceRootsPerSecond &&
fileBytes(options.checkpoint) <= kMaximumCheckpointBytes;
const double elapsed = std::chrono::duration<double>(
std::chrono::steady_clock::now() - started)
.count();
std::ofstream artifact(options.output);
if (!artifact) throw std::runtime_error("could not write frozen audit");
artifact << std::setprecision(10)
<< "{\n \"experiment\":\"scaled-fair-d4-frozen-label-audit\",\n"
" \"status\":\"complete\",\n"
" \"sourceProtocol\":{\"trainingSeedStart\":"
<< kTrainingSeedStart << ",\"trainingGames\":" << kTrainingGames
<< ",\"heldoutSeedStart\":" << kHeldoutSeedStart
<< ",\"heldoutGames\":" << kHeldoutGames
<< ",\"trainingRecords\":" << training_labels.size()
<< ",\"heldoutRecords\":" << heldout_labels.size()
<< ",\"maximumMoves\":" << kCollectionMaximumMoves
<< ",\"pilotSeedUnique\":1,\"pilotSeedExecutions\":2,"
"\"pilotOnlyLabelsPersisted\":false,\"fresh3eSeedsRead\":0,"
"\"validation7dSeedsRead\":0,\"finalD7SeedsRead\":0},\n"
" \"protocolDeviation\":{\"kind\":\"inadvertent-D2-sanity-after-residual-rejection\","
"\"newSeedFamiliesOpened\":false,\"residualGameplayRan\":false,"
"\"diagnosticAccepted\":false,\"canonicalEvidence\":\"ranking-only\"},\n"
" \"architecture\":{\"anchor\":\"full-width public fair D2/five strata\","
"\"residual\":\"1647-weight action-relative reflection-averaged sparse linear NNUE\","
"\"checkpointBytes\":"
<< fileBytes(options.checkpoint)
<< ",\"fingerprintFnv1a64\":\"0x" << std::hex
<< fingerprint(frozen) << std::dec
<< "\",\"deterministicRetrain\":true},\n \"heldout\":{";
writeRanking(artifact, "d2All", d2_all);
artifact << ',';
writeRanking(artifact, "d2FirstSixGames", d2_first);
artifact << ',';
writeRanking(artifact, "d2SecondSixGames", d2_second);
artifact << ',';
writeRanking(artifact, "residualAll", residual_all);
artifact << ',';
writeRanking(artifact, "residualFirstSixGames", residual_first);
artifact << ',';
writeRanking(artifact, "residualSecondSixGames", residual_second);
artifact << "},\n \"gate\":{\"d2Passed\":"
<< (d2_passed ? "true" : "false")
<< ",\"residualPassed\":"
<< (residual_passed ? "true" : "false")
<< ",\"residualRequiredToBeatD2OnAllMetricsAndHalves\":true},\n"
" \"searchCost\":{\"roots\":"
<< search_cost.roots << ",\"d2WorkPerRoot\":"
<< search_cost.d2_work_per_root << ",\"d2NodesPerRoot\":"
<< search_cost.d2_nodes_per_root
<< ",\"d2CacheHitsPerRoot\":"
<< search_cost.d2_cache_hits_per_root
<< ",\"d1PlusD2WorkPerResidualRoot\":"
<< search_cost.d1_plus_d2_work_per_root
<< ",\"maximumD2Work\":" << search_cost.maximum_d2_work
<< ",\"maximumD2CacheEntries\":"
<< search_cost.maximum_d2_cache_entries << "},\n"
" \"throughput\":{\"d2RootsPerSecond\":"
<< d2_throughput.roots_per_second
<< ",\"d2BenchmarkSeconds\":" << d2_throughput.seconds
<< ",\"d2Checksum\":" << d2_throughput.checksum
<< ",\"residualRootsPerSecond\":"
<< residual_throughput.roots_per_second
<< ",\"residualBenchmarkSeconds\":"
<< residual_throughput.seconds << ",\"residualChecksum\":"
<< residual_throughput.checksum << "},\n"
" \"acceptedGameplayEvidence\":null,\n"
" \"conclusion\":\"exact public D2 is the accepted fast rollout primitive; learned residual rejected\",\n"
" \"auditWallSeconds\":"
<< elapsed << ",\n \"peakRssBytes\":" << peakRssBytes() << "\n}\n";
output << std::fixed << std::setprecision(6)
<< "SCALED_D4_FROZEN_AUDIT {\"d2Top1\":" << top1Rate(d2_all)
<< ",\"d2Pairwise\":" << pairwiseRate(d2_all)
<< ",\"d2Regret\":" << regret(d2_all)
<< ",\"residualTop1\":" << top1Rate(residual_all)
<< ",\"residualPairwise\":" << pairwiseRate(residual_all)
<< ",\"residualRegret\":" << regret(residual_all)
<< ",\"d2Passed\":" << (d2_passed ? "true" : "false")
<< ",\"residualPassed\":" << (residual_passed ? "true" : "false")
<< ",\"d2WorkPerRoot\":" << search_cost.d2_work_per_root
<< ",\"d2RootsPerSecond\":" << d2_throughput.roots_per_second
<< ",\"gameplayAccepted\":false,\"artifact\":\""
<< options.output << "\"}\n";
return 0;
}
int run(const Options& options, std::ostream& output) {
const auto started = std::chrono::steady_clock::now();
// Architecture, optimizer, split, and all gates above are constants. The
// first evaluation seed is used only for a wall-time projection, never selection.
CollectedGame pilot = collectTeacherGame(kTrainingSeedStart, "pilot-fit");
const double projected_wall =
pilot.game.elapsed_seconds *
std::ceil(static_cast<double>(kTrainingGames + kHeldoutGames) /
kParallelism) *
kProjectionSafetyFactor;
if (projected_wall > kMaximumProjectedWallSeconds) {
writePausedArtifact(options, pilot, projected_wall,
"first-seed useful-corpus projection exceeded limit");
output << "SCALED_D4_DISTILL_RESULT {\"status\":\"paused-runtime-gate\","
"\"trainingSeedsRead\":1,\"heldoutSeedsRead\":0,"
"\"projectedWallSeconds\":"
<< projected_wall << "}\n";
return 0;
}
std::vector<CollectedGame> training_games(kTrainingGames);
training_games[0] = std::move(pilot);
std::vector<CollectedGame> remaining = collectTeacherRange(
kTrainingSeedStart + 1, kTrainingGames - 1, "fitting");
for (int index = 1; index < kTrainingGames; ++index) {
training_games[index] = std::move(remaining[index - 1]);
}
const std::vector<RootLabel> training_labels = flattenLabels(training_games);
if (static_cast<int>(training_labels.size()) < kMinimumTrainingLabels) {
throw std::runtime_error("scaled-distill fitting corpus was too small");
}
const std::vector<PreparedRoot> prepared_training =
prepareAll(training_labels);
const LinearModel trained = trainLinear(prepared_training, 0.03);
writeCheckpoint(options.checkpoint, trained);
const LinearModel frozen = readCheckpoint(options.checkpoint);
const std::uint64_t model_fingerprint = fingerprint(frozen);
double training_cpu_seconds = 0.0;
for (const CollectedGame& game : training_games) {
training_cpu_seconds += game.game.elapsed_seconds;
}
const double heldout_projection =
training_cpu_seconds / kTrainingGames * kHeldoutGames / kParallelism *
kProjectionSafetyFactor;
const double elapsed_before_holdout = std::chrono::duration<double>(
std::chrono::steady_clock::now() -
started)
.count();
if (elapsed_before_holdout + heldout_projection >
kMaximumProjectedWallSeconds) {
std::ofstream paused(options.output);
if (!paused) throw std::runtime_error("could not write training pause");
paused << std::setprecision(10)
<< "{\"experiment\":\"scaled-fair-d4-distillation\","
"\"status\":\"paused-before-heldout-runtime-gate\","
"\"trainingSeedStart\":"
<< kTrainingSeedStart << ",\"trainingSeedsRead\":"
<< kTrainingGames << ",\"trainingRecords\":"
<< training_labels.size() << ",\"heldoutSeedsRead\":0,"
"\"projectedTotalWallSeconds\":"
<< elapsed_before_holdout + heldout_projection
<< ",\"limitSeconds\":" << kMaximumProjectedWallSeconds << "}\n";
output << "SCALED_D4_DISTILL_RESULT {\"status\":\"paused-before-heldout-runtime-gate\","
"\"trainingSeedsRead\":"
<< kTrainingGames << ",\"heldoutSeedsRead\":0}\n";
return 0;
}
// Read the heldout exactly once after locking the checkpoint.
const std::vector<CollectedGame> heldout_games = collectTeacherRange(
kHeldoutSeedStart, kHeldoutGames, "heldout");
const std::vector<RootLabel> heldout_labels = flattenLabels(heldout_games);
if (static_cast<int>(heldout_labels.size()) < kMinimumHeldoutLabels) {
throw std::runtime_error("scaled-distill heldout corpus was too small");
}
writeLabels(options.labels, training_labels, heldout_labels);
const std::vector<PreparedRoot> prepared_heldout = prepareAll(heldout_labels);
const Ranking training_d2 =
evaluateRange(nullptr, prepared_training, 0, kTrainingGames);
const Ranking training_residual =
evaluateRange(&frozen, prepared_training, 0, kTrainingGames);
const Ranking heldout_d2 =
evaluateRange(nullptr, prepared_heldout, 0, kHeldoutGames);
const Ranking heldout_d2_first = evaluateRange(
nullptr, prepared_heldout, 0, kHeldoutGames / 2);
const Ranking heldout_d2_second = evaluateRange(
nullptr, prepared_heldout, kHeldoutGames / 2, kHeldoutGames);
const Ranking heldout_residual =
evaluateRange(&frozen, prepared_heldout, 0, kHeldoutGames);
const Ranking heldout_residual_first = evaluateRange(
&frozen, prepared_heldout, 0, kHeldoutGames / 2);
const Ranking heldout_residual_second = evaluateRange(
&frozen, prepared_heldout, kHeldoutGames / 2, kHeldoutGames);
const Throughput d2_throughput = benchmarkD2Inference(heldout_labels);
const Throughput residual_throughput =
benchmarkInference(frozen, heldout_labels);
const std::uint64_t checkpoint_bytes = fileBytes(options.checkpoint);
const bool d2_ranking_passed =
absoluteRankingGate(heldout_d2, heldout_d2_first,
heldout_d2_second) &&
d2_throughput.roots_per_second >= kMinimumInferenceRootsPerSecond;
const bool residual_ranking_passed =
absoluteRankingGate(heldout_residual, heldout_residual_first,
heldout_residual_second) &&
residualImprovesD2(heldout_residual, heldout_d2) &&
residualImprovesD2(heldout_residual_first, heldout_d2_first) &&
residualImprovesD2(heldout_residual_second, heldout_d2_second) &&
residual_throughput.roots_per_second >=
kMinimumInferenceRootsPerSecond &&
checkpoint_bytes <= kMaximumCheckpointBytes;
const GameSummary teacher_summary = summarizeTeacher(heldout_games);
std::vector<GameResult> d2_games;
GameSummary d2_summary;
bool d2_rollout_passed = false;
// Gameplay is diagnostic only after the learned residual itself proves a
// material, split-stable improvement over exact D2. A D2-only label pass
// establishes a rollout primitive, not permission for another policy test.
if (residual_ranking_passed && d2_ranking_passed) {
d2_games = runStudentRange(kHeldoutSeedStart, kHeldoutGames,
LinearModel{});
d2_summary = summarize(d2_games);
d2_rollout_passed = rolloutGate(teacher_summary, d2_summary);
}
std::vector<GameResult> residual_games;
GameSummary residual_summary;
bool residual_rollout_passed = false;
if (residual_ranking_passed) {
residual_games =
runStudentRange(kHeldoutSeedStart, kHeldoutGames, frozen);
residual_summary = summarize(residual_games);
residual_rollout_passed = rolloutGate(teacher_summary, residual_summary);
}
const double total_wall_seconds = std::chrono::duration<double>(
std::chrono::steady_clock::now() -
started)
.count();
writeArtifact(
options, training_games, heldout_games, training_d2, training_residual,
heldout_d2, heldout_d2_first, heldout_d2_second, heldout_residual,
heldout_residual_first, heldout_residual_second, d2_throughput,
residual_throughput, teacher_summary,
residual_ranking_passed && d2_ranking_passed ? &d2_games : nullptr,
residual_ranking_passed && d2_ranking_passed ? &d2_summary : nullptr,
d2_ranking_passed,
d2_rollout_passed,
residual_ranking_passed ? &residual_games : nullptr,
residual_ranking_passed ? &residual_summary : nullptr,
residual_ranking_passed, residual_rollout_passed, checkpoint_bytes,
model_fingerprint, projected_wall, total_wall_seconds);
output << std::fixed << std::setprecision(4)
<< "SCALED_D4_DISTILL_RESULT {\"status\":\"complete\","
"\"trainingRecords\":"
<< training_labels.size() << ",\"heldoutRecords\":"
<< heldout_labels.size() << ",\"d2Top1\":"
<< top1Rate(heldout_d2) << ",\"d2Pairwise\":"
<< pairwiseRate(heldout_d2) << ",\"d2Regret\":"
<< regret(heldout_d2) << ",\"residualTop1\":"
<< top1Rate(heldout_residual) << ",\"residualPairwise\":"
<< pairwiseRate(heldout_residual) << ",\"residualRegret\":"
<< regret(heldout_residual) << ",\"d2RankingPassed\":"
<< (d2_ranking_passed ? "true" : "false")
<< ",\"d2RolloutPassed\":"
<< (d2_rollout_passed ? "true" : "false")
<< ",\"residualRankingPassed\":"
<< (residual_ranking_passed ? "true" : "false")
<< ",\"residualRolloutRan\":"
<< (residual_ranking_passed ? "true" : "false")
<< ",\"d2RootsPerSecond\":" << d2_throughput.roots_per_second
<< ",\"checkpointBytes\":" << checkpoint_bytes
<< ",\"totalWallSeconds\":" << total_wall_seconds
<< ",\"artifact\":\"" << options.output << "\"}\n";
return 0;
}
} // namespace drop7::scaled_d4_distill
#ifndef DROP7_SCALED_D4_DISTILL_LIBRARY
int main(int argc, char** argv) {
try {
if (argc >= 2 && std::string_view(argv[1]) == "--design-old") {
std::string labels = "/tmp/drop7-d4-public-root-labels.jsonl";
if (argc == 4 && std::string_view(argv[2]) == "--labels") {
labels = argv[3];
} else if (argc != 2) {
throw std::invalid_argument("invalid --design-old arguments");
}
return drop7::scaled_d4_distill::designOld(labels);
}
if (argc >= 2 && std::string_view(argv[1]) == "--self-test") {
const auto options =
drop7::scaled_d4_distill::parseOptions(argc, argv, 2);
return drop7::scaled_d4_distill::selfTest(options, std::cout)
? EXIT_SUCCESS
: EXIT_FAILURE;
}
if (argc == 2 && std::string_view(argv[1]) == "--pilot-only") {
return drop7::scaled_d4_distill::pilotOnly(std::cout);
}
if (argc >= 2 && std::string_view(argv[1]) == "--audit-existing") {
const auto options =
drop7::scaled_d4_distill::parseOptions(argc, argv, 2);
return drop7::scaled_d4_distill::auditExisting(options, std::cout);
}
if (argc >= 2 && std::string_view(argv[1]) == "--run") {
const auto options =
drop7::scaled_d4_distill::parseOptions(argc, argv, 2);
return drop7::scaled_d4_distill::run(options, std::cout);
}
std::cerr << "usage: drop7_scaled_d4_distill "
"--design-old [--labels PATH] | --self-test | "
"--pilot-only | --audit-existing | --run "
"[--output PATH --checkpoint PATH --labels PATH]\n";
return 2;
} catch (const std::exception& error) {
std::cerr << "drop7_scaled_d4_distill: " << error.what() << '\n';
return 1;
}
}
#endif