#include "aner/gen.hpp"
#include "aner/gpu.hpp"
#include "aner/nn.hpp"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <memory>
#include <numeric>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

namespace gen = aner::gen;
namespace nn = aner::nn;
namespace token = aner::token;
using IDs = std::vector<std::vector<std::int64_t>>;
using Clock = std::chrono::steady_clock;

namespace {
void require(bool value, const std::string &message) {
    if (!value) throw std::runtime_error(message);
}
template<class T> void array(const std::vector<T> &values) {
    std::cout << '[';
    for (std::size_t i = 0; i < values.size(); ++i) std::cout << (i ? "," : "") << values[i];
    std::cout << ']';
}
void nested(const IDs &rows) {
    std::cout << '[';
    for (std::size_t i = 0; i < rows.size(); ++i) { if (i) std::cout << ','; array(rows[i]); }
    std::cout << ']';
}
struct Options {
    std::string device = "mps", dtype = "Float32", phase = "natural", name = "tiny";
    std::int64_t batch = 8, width = 32, layers = 2;
    int repetitions = 3, warmups = 2;
    std::size_t chunk_steps = 8;
    double temperature = 0, top_p = 1;
    std::int64_t top_k = 0;
    bool native_plan = true;
};
struct Work {
    IDs prompts;
    std::vector<std::int64_t> lengths, budgets, capacities, seeds;
    std::int64_t eos_position = -1;
};
Work work(const Options &options) {
    Work result;
    if (options.name == "ragged") {
        require(options.batch == 8, "ragged workload requires eight logical rows");
        result.lengths = {1, 3, 7, 31, 2, 5, 13, 47};
        result.budgets = {32, 1, 16, 2, 24, 3, 8, 4};
    } else if (options.name == "mixed_eos") {
        require(options.batch == 8, "mixed EOS workload requires eight rows");
        result.lengths = {1, 57, 58, 59, 60, 61, 63, 65};
        result.budgets = {64, 31, 31, 31, 31, 31, 31, 31};
        result.eos_position = 64;
    } else if (options.name == "zero_mixed") {
        require(options.batch == 8, "mixed zero budgets require eight rows");
        result.lengths = {0, 1, 8, 3, 8, 0, 31, 5};
        result.budgets = {0, 1, 0, 8, 64, 1, 0, 16};
    } else if (options.name == "all_zero" || options.name == "one") {
        result.lengths.assign(static_cast<std::size_t>(options.batch), 0);
        result.budgets.assign(static_cast<std::size_t>(options.batch), options.name == "one" ? 1 : 0);
    } else if (options.name.starts_with("eos")) {
        const auto count = std::stoll(options.name.substr(3));
        require(count >= 1 && count <= 10, "unsupported EOS boundary");
        result.lengths.assign(static_cast<std::size_t>(options.batch), 8);
        result.budgets.assign(static_cast<std::size_t>(options.batch), 64);
        result.eos_position = 8 + count - 2;
    } else {
        require(options.name == "tiny" || options.name == "long", "unknown workload");
        result.lengths.assign(static_cast<std::size_t>(options.batch), 8);
        result.budgets.assign(static_cast<std::size_t>(options.batch), options.name == "tiny" ? 8 : 64);
    }
    for (std::size_t row = 0; row < result.lengths.size(); ++row) {
        std::vector<std::int64_t> prompt;
        if (result.lengths[row] > 0) prompt.push_back(token::bos_id);
        for (std::int64_t step = 1; step < result.lengths[row]; ++step)
            prompt.push_back(97 + (37 * step + 11 * static_cast<std::int64_t>(row) + 5) % 26);
        result.prompts.push_back(std::move(prompt));
        result.capacities.push_back(std::max(std::int64_t(1), std::max(std::int64_t(1), result.lengths[row]) + result.budgets[row] - 1));
        result.seeds.push_back(7301 + 101 * static_cast<std::int64_t>(row));
    }
    return result;
}
IDs normalized_prompts(const Work &workload) {
    auto result = workload.prompts;
    for (auto &row : result) if (row.empty()) row = {token::bos_id};
    return result;
}
gen::Model fixture_model(const Options &options, const Work &workload) {
    auto result = gen::decoder(token::byte_tokenizer(), {96, options.width, 4, options.layers, 20261015, 0});
    if (workload.eos_position < 0) return result;
    // With zero transformer projections, the residual is token + position.
    // Ordinary token 65 has direction u; EOS has -u. Positions switch from
    // +4u to -4u at a declared absolute position, yielding a robust EOS margin.
    for (const auto &name : gen::parameter_names(result)) {
        const auto parameter = gen::parameter(result, name);
        std::vector<double> values(static_cast<std::size_t>(parameter.rows() * parameter.cols()), 0);
        if (name.ends_with(".gain")) std::fill(values.begin(), values.end(), 1);
        if (name == "embedding.token") {
            values[65 * static_cast<std::size_t>(options.width)] = 1;
            values[65 * static_cast<std::size_t>(options.width) + 1] = -1;
            values[static_cast<std::size_t>(token::eos_id * options.width)] = -1;
            values[static_cast<std::size_t>(token::eos_id * options.width) + 1] = 1;
        }
        if (name == "embedding.position")
            for (std::int64_t position = 0; position < 96; ++position) {
                const auto sign = position < workload.eos_position ? 4. : -4.;
                values[static_cast<std::size_t>(position * options.width)] = sign;
                values[static_cast<std::size_t>(position * options.width + 1)] = -sign;
            }
        result = gen::with_parameter(result, name, nn::tensor(parameter.rows(), parameter.cols(), std::move(values)));
    }
    return result;
}
gen::Model cpu_reference_model(const Options &options, const Work &workload) {
    // Match the actual placed dtype first, then widen those exact parameter
    // values for an independent Float64 mathematical reference.
    return gen::to(gen::to(fixture_model(options, workload), "cpu", options.dtype), "cpu", "Float64");
}
void verify_analytic(const IDs &output, const Work &workload) {
    if (workload.eos_position < 0) return;
    for (std::size_t row = 0; row < output.size(); ++row) {
        const auto eos_at = std::max(std::int64_t(1), workload.eos_position - std::max(std::int64_t(1), workload.lengths[row]) + 2);
        const auto count = std::min(workload.budgets[row], eos_at);
        std::vector<std::int64_t> expected(static_cast<std::size_t>(count), 65);
        if (count > 0 && count == eos_at) expected.back() = token::eos_id;
        require(output[row] == expected, "position-driven EOS fixture differs from its analytic sequence");
    }
}
IDs forced_inputs(const Work &workload, std::int64_t step) {
    IDs result(workload.prompts.size());
    for (std::size_t row = 0; row < result.size(); ++row)
        if (step < workload.budgets[row] - 1)
            result[row] = {97 + (7 * step + 13 * static_cast<std::int64_t>(row) + 3) % 26};
    return result;
}
template<class Cache = gen::detail::GenerationCache>
Cache single_cache(const gen::Model &model, std::int64_t capacity, bool native) {
    if constexpr (std::is_constructible_v<Cache, const gen::Model &, bool, std::int64_t, bool>)
        return Cache(model, true, capacity, native);
    else {
        require(native, "baseline does not expose the primitive-plan testing selector");
        return Cache(model, true, capacity);
    }
}
template<class Cache = gen::detail::GenerationBatchCache>
Cache batch_cache(const gen::Model &model, const Work &workload, bool native) {
    if constexpr (std::is_constructible_v<Cache, const gen::Model &, const std::vector<std::int64_t> &, bool, bool, bool>)
        return Cache(model, workload.capacities, true, false, native);
    else {
        require(native, "baseline does not expose the primitive-plan testing selector");
        return Cache(model, workload.capacities, true, false);
    }
}
struct Statistics {
    bool available = false;
    std::uint64_t forwards = 0, buffers = 0, reads = 0, gemms = 0, encoders = 0, packed_rows = 0;
    bool greedy_available = false;
    std::uint64_t chunks = 0, active_reads = 0, result_reads = 0, id_bytes = 0, emitted = 0;
};
template<class Cache> Statistics statistics(const Cache &cache) {
    if constexpr (requires { cache.statistics(); }) {
        const auto source = cache.statistics();
        Statistics result{source.available, source.forward_calls, source.command_buffers, source.status_reads,
                          source.gemm_calls, source.compute_encoders, source.packed_output_rows};
        if constexpr (requires { source.greedy_chunks; }) {
            result.greedy_available = true; result.chunks = source.greedy_chunks;
            result.active_reads = source.greedy_active_reads; result.result_reads = source.greedy_result_readbacks;
            result.id_bytes = source.greedy_id_bytes; result.emitted = source.greedy_emitted_tokens;
        }
        return result;
    } else return {};
}
void statistics_json(const Statistics &stats) {
    std::cout << "{\"available\":" << (stats.available ? "true" : "false")
              << ",\"forward_calls\":" << stats.forwards << ",\"command_buffers\":" << stats.buffers
              << ",\"status_reads\":" << stats.reads << ",\"gemm_calls\":" << stats.gemms
              << ",\"compute_encoders\":" << stats.encoders << ",\"packed_output_rows\":" << stats.packed_rows
              << ",\"greedy_available\":" << (stats.greedy_available ? "true" : "false")
              << ",\"greedy_chunks\":" << stats.chunks << ",\"greedy_active_reads\":" << stats.active_reads
              << ",\"greedy_result_readbacks\":" << stats.result_reads << ",\"greedy_id_bytes\":" << stats.id_bytes
              << ",\"greedy_emitted_tokens\":" << stats.emitted << '}';
}
void prefix(const Options &options, const Work &workload) {
    std::cout << "{\"schema\":\"aner.metal-greedy-benchmark/1\",\"phase\":\"" << options.phase
              << "\",\"device\":\"" << options.device << "\",\"dtype\":\"" << options.dtype
              << "\",\"case\":\"" << options.name << "\",\"batch_size\":" << options.batch
              << ",\"width\":" << options.width << ",\"layers\":" << options.layers
              << ",\"heads\":4,\"context\":96,\"vocabulary\":259,\"model_seed\":20261015,\"dropout\":0"
              << ",\"temperature\":" << options.temperature << ",\"top_k\":" << options.top_k << ",\"top_p\":" << options.top_p
              << ",\"eos_position\":" << workload.eos_position << ",\"chunk_steps\":" << options.chunk_steps
              << ",\"prompt_lengths\":";
    array(workload.lengths); std::cout << ",\"budgets\":"; array(workload.budgets);
    std::cout << ",\"row_seeds\":"; array(workload.seeds);
}
void memory_json(const nn::MemoryStats &before, const nn::MemoryStats &after) {
    require(after.all_buffers.current_bytes == before.all_buffers.current_bytes,
            "generation retained tracked numeric buffers after release");
    std::cout << ",\"memory\":{\"retained_increment_bytes\":0"
              << ",\"all_peak_increment_bytes\":" << after.all_buffers.peak_bytes - before.all_buffers.current_bytes
              << ",\"device_peak_increment_bytes\":" << after.device_buffers.peak_bytes - before.device_buffers.current_bytes
              << ",\"host_peak_increment_bytes\":" << after.host_buffers.peak_bytes - before.host_buffers.current_bytes
              << ",\"readback_peak_increment_bytes\":" << after.host_readback_cache.peak_bytes - before.host_readback_cache.current_bytes
              << '}';
}
IDs public_request(const gen::Model &model, const Work &workload, const Options &options) {
    if (options.batch == 1)
        return {gen::generate(model, workload.prompts[0], workload.budgets[0], options.temperature, options.top_k, options.top_p, workload.seeds[0], true)};
    return gen::generate_batch(model, workload.prompts, workload.budgets, options.temperature, options.top_k, options.top_p, workload.seeds, true, false);
}
void reference_request(const gen::Model &model, const Work &workload, const Options &options) {
    const auto output = public_request(model, workload, options);
    if (options.temperature == 0) verify_analytic(output, workload);
    prefix(options, workload); std::cout << ",\"output_ids\":"; nested(output); std::cout << "}\n";
}
void natural(const gen::Model &model, const Work &workload, const Options &options) {
    IDs expected;
    for (int iteration = -options.warmups; iteration < options.repetitions; ++iteration) {
        nn::synchronize(options.device);
        const auto before = nn::memory_stats(); nn::reset_memory_peaks();
        const auto started = Clock::now();
        const auto output = public_request(model, workload, options);
        nn::synchronize(options.device);
        const auto seconds = std::chrono::duration<double>(Clock::now() - started).count();
        const auto after = nn::memory_stats();
        require(output.size() == workload.prompts.size(), "natural generation changed row inventory");
        for (std::size_t row = 0; row < output.size(); ++row)
            require((workload.budgets[row] == 0 ? output[row].empty() : !output[row].empty()) && output[row].size() <= static_cast<std::size_t>(workload.budgets[row]),
                    "natural generation violated an output budget");
        if (options.temperature == 0) verify_analytic(output, workload);
        if (expected.empty()) expected = output;
        else require(output == expected, "one runtime produced inconsistent repeated greedy IDs");
        if (iteration < 0) continue;
        prefix(options, workload);
        std::cout << ",\"iteration\":" << iteration << ",\"seconds\":" << seconds << ",\"output_ids\":";
        nested(output);
        std::vector<std::int64_t> counts, ended;
        for (const auto &row : output) { counts.push_back(static_cast<std::int64_t>(row.size())); ended.push_back(!row.empty() && row.back() == token::eos_id); }
        std::cout << ",\"actual_new_tokens_per_row\":"; array(counts);
        std::cout << ",\"ended_eos_per_row\":"; array(ended);
        std::cout << ",\"actual_total_new_tokens\":" << std::accumulate(counts.begin(), counts.end(), std::int64_t(0));
        memory_json(before, after);
        std::cout << ",\"statistics\":{\"available\":false}}\n";
    }
}
template<class Cache> bool resident_available(const Cache &cache) {
    if constexpr (requires { cache.supports_resident_greedy(); }) return cache.supports_resident_greedy();
    else return false;
}
template<class Cache> IDs resident_call(Cache &cache, const Work &workload, std::size_t chunk) {
    if constexpr (requires { cache.generate_greedy(workload.prompts, workload.budgets, chunk); })
        return cache.generate_greedy(workload.prompts, workload.budgets, chunk);
    else throw std::runtime_error("resident greedy testing seam unavailable in this runtime");
}
std::vector<std::int64_t> expected_positions(const IDs &output, const Work &workload) {
    std::vector<std::int64_t> result;
    for (std::size_t row = 0; row < output.size(); ++row)
        result.push_back(output[row].empty() ? 0 : std::max(std::int64_t(1), workload.lengths[row]) + static_cast<std::int64_t>(output[row].size()) - 1);
    return result;
}
void resident(const gen::Model &model, const Work &workload, const Options &options) {
    for (int iteration = -options.warmups; iteration < options.repetitions; ++iteration) {
        nn::synchronize(options.device); const auto before = nn::memory_stats(); nn::reset_memory_peaks();
        const auto started = Clock::now(); IDs output; Statistics stats; std::vector<std::int64_t> positions;
        {
            auto cache = batch_cache(model, workload, true);
            require(resident_available(cache), "explicit resident request unsupported; no fallback");
            output = resident_call(cache, workload, options.chunk_steps);
            stats = statistics(cache); positions = cache.positions();
            require(positions == expected_positions(output, workload), "resident committed EOS/final token or masked tail to KV");
        }
        nn::synchronize(options.device);
        const auto seconds = std::chrono::duration<double>(Clock::now() - started).count();
        const auto after = nn::memory_stats();
        verify_analytic(output, workload);
        if (iteration < 0) continue;
        prefix(options, workload); std::cout << ",\"iteration\":" << iteration << ",\"seconds\":" << seconds << ",\"output_ids\":";
        nested(output); std::cout << ",\"positions\":"; array(positions);
        memory_json(before, after); std::cout << ",\"statistics\":"; statistics_json(stats); std::cout << "}\n";
    }
}
void kv_oracle(const gen::Model &model, Work workload, const Options &options) {
    for (std::size_t row = 0; row < workload.capacities.size(); ++row)
        workload.capacities[row] = std::max(std::int64_t(1), workload.lengths[row]) + workload.budgets[row];
    auto cache = batch_cache(model, workload, true);
    require(resident_available(cache), "resident KV oracle unsupported; no fallback");
    const auto output = resident_call(cache, workload, options.chunk_steps);
    verify_analytic(output, workload);
    const auto positions = cache.positions();
    require(positions == expected_positions(output, workload), "resident cache position differs before probe");
    const auto stats = statistics(cache);
    const auto reference_model = cpu_reference_model(options, workload);
    std::vector<double> expected;
    const auto normalized = normalized_prompts(workload);
    for (std::size_t row = 0; row < output.size(); ++row) {
        std::vector<std::int64_t> history;
        if (!output[row].empty()) {
            history = normalized[row];
            history.insert(history.end(), output[row].begin(), output[row].end() - 1);
        }
        history.push_back(97);
        gen::detail::GenerationCache reference(reference_model, true, workload.capacities[row]);
        const auto logits = reference.append(history);
        const auto &values = logits.values(); expected.insert(expected.end(), values.begin(), values.end());
    }
    const auto actual = cache.append(IDs(output.size(), {97})).values();
    require(actual.size() == expected.size(), "resident KV probe shape differs");
    double maximum = 0;
    for (std::size_t i = 0; i < actual.size(); ++i) {
        require(std::isfinite(actual[i]) && std::isfinite(expected[i]), "nonfinite resident KV probe");
        maximum = std::max(maximum, std::abs(actual[i] - expected[i]) / (1 + std::abs(expected[i])));
    }
    require(maximum <= 5e-4, "resident KV state differs from independently reconstructed CPU Float64 history");
    prefix(options, workload); std::cout << ",\"output_ids\":"; nested(output);
    std::cout << ",\"positions_before_probe\":"; array(positions);
    std::cout << ",\"max_scaled_logit_error\":" << maximum << ",\"scaled_tolerance\":0.0005,\"actual_logits\":"; array(actual);
    std::cout << ",\"reference_logits\":"; array(expected); std::cout << ",\"statistics\":"; statistics_json(stats); std::cout << "}\n";
}
void forced(const gen::Model &model, const Work &workload, const Options &options) {
    const auto steps = *std::max_element(workload.budgets.begin(), workload.budgets.end()) - 1;
    std::vector<IDs> inputs;
    for (std::int64_t step = 0; step < steps; ++step) inputs.push_back(forced_inputs(workload, step));
    for (int iteration = -options.warmups; iteration < options.repetitions; ++iteration) {
        nn::synchronize(options.device);
        const auto before = nn::memory_stats(); nn::reset_memory_peaks();
        const auto started = Clock::now();
        Statistics stats;
        std::uint64_t bytes = 0, elements = 0, allocations = 0;
        if (options.batch == 1) {
            auto cache = single_cache(model, workload.capacities[0], options.native_plan);
            auto output = cache.append(workload.prompts[0]);
            for (const auto &input : inputs) { output = nn::Tensor(); output = cache.append(input[0]); }
            require(cache.position() == workload.capacities[0], "forced single final position differs");
            stats = statistics(cache); bytes = cache.key_value_bytes(); elements = cache.key_value_elements(); allocations = cache.key_value_allocations();
        } else {
            auto cache = batch_cache(model, workload, options.native_plan);
            auto output = cache.append(workload.prompts);
            if (!inputs.empty()) cache.prepare_decode(static_cast<std::int64_t>(std::count_if(inputs[0].begin(), inputs[0].end(), [](const auto &x) { return !x.empty(); })));
            for (const auto &input : inputs) { output = nn::Tensor(); output = cache.append(input); }
            require(cache.positions() == workload.capacities, "forced batch final positions differ");
            stats = statistics(cache); bytes = cache.key_value_bytes(); elements = cache.key_value_elements(); allocations = cache.key_value_allocations();
        }
        nn::synchronize(options.device);
        const auto seconds = std::chrono::duration<double>(Clock::now() - started).count();
        const auto after = nn::memory_stats();
        if (iteration < 0) continue;
        prefix(options, workload);
        std::cout << ",\"iteration\":" << iteration << ",\"seconds\":" << seconds
                  << ",\"timed_logit_readback\":false,\"forced_next_logit_rows\":"
                  << std::accumulate(workload.budgets.begin(), workload.budgets.end(), std::int64_t(0))
                  << ",\"kv_bytes\":" << bytes << ",\"kv_elements\":" << elements
                  << ",\"kv_allocations\":" << allocations;
        memory_json(before, after);
        std::cout << ",\"statistics\":"; statistics_json(stats); std::cout << "}\n";
    }
}
void oracle(const gen::Model &model, const Work &workload, const Options &options) {
    const auto reference_model = cpu_reference_model(options, workload);
    std::vector<std::unique_ptr<gen::detail::GenerationCache>> references;
    for (auto capacity : workload.capacities)
        references.push_back(std::make_unique<gen::detail::GenerationCache>(reference_model, true, capacity));
    std::unique_ptr<gen::detail::GenerationCache> single;
    std::unique_ptr<gen::detail::GenerationBatchCache> packed;
    if (options.batch == 1)
        single = std::make_unique<gen::detail::GenerationCache>(single_cache(model, workload.capacities[0], options.native_plan));
    else
        packed = std::make_unique<gen::detail::GenerationBatchCache>(batch_cache(model, workload, options.native_plan));
    const auto steps = *std::max_element(workload.budgets.begin(), workload.budgets.end()) - 1;
    const double tolerance = options.dtype == "Float64" ? 1e-10 : 5e-4;
    for (std::int64_t step = -1; step < steps; ++step) {
        auto input = step < 0 ? normalized_prompts(workload) : forced_inputs(workload, step);
        if (step < 0) for (std::size_t row = 0; row < input.size(); ++row) if (workload.budgets[row] == 0) input[row].clear();
        const auto output = options.batch == 1 ? single->append(input[0]) : packed->append(input);
        const auto actual = output.values();
        std::vector<double> expected; std::vector<std::int64_t> active;
        double maximum = 0;
        for (std::size_t row = 0; row < input.size(); ++row) if (!input[row].empty()) {
            const auto next = references[row]->append(input[row]);
            const auto &values = next.values();
            expected.insert(expected.end(), values.begin(), values.end()); active.push_back(static_cast<std::int64_t>(row));
        }
        require(actual.size() == expected.size(), "oracle shape differs");
        for (std::size_t i = 0; i < actual.size(); ++i) {
            require(std::isfinite(actual[i]) && std::isfinite(expected[i]), "oracle observed nonfinite logit");
            maximum = std::max(maximum, std::abs(actual[i] - expected[i]) / (1 + std::abs(expected[i])));
        }
        require(maximum <= tolerance, "forced logits exceed the existing CPU Float64 reference tolerance");
        prefix(options, workload); std::cout << ",\"step\":" << step << ",\"active_rows\":"; array(active);
        std::cout << ",\"max_scaled_logit_error\":" << maximum << ",\"scaled_tolerance\":" << tolerance << ",\"actual_logits\":"; array(actual);
        std::cout << ",\"reference_logits\":"; array(expected); std::cout << "}\n";
    }
}
void weights(const gen::Model &model) {
    auto names = gen::parameter_names(model); std::sort(names.begin(), names.end());
    auto write64 = [](std::uint64_t value) { std::cout.write(reinterpret_cast<const char *>(&value), sizeof(value)); };
    std::cout << "aner.parameters.widened-f64/1\n" << gen::dtype(model) << '\n'; write64(names.size());
    for (const auto &name : names) {
        const auto parameter = gen::parameter(model, name); const auto &values = parameter.values();
        write64(name.size()); std::cout.write(name.data(), static_cast<std::streamsize>(name.size()));
        write64(parameter.rows()); write64(parameter.cols()); write64(values.size());
        std::cout.write(reinterpret_cast<const char *>(values.data()), static_cast<std::streamsize>(values.size() * sizeof(double)));
    }
}
} // namespace

int main(int argc, char **argv) {
    try {
        Options options;
        for (int i = 1; i < argc; i += 2) {
            require(i + 1 < argc, "option needs value");
            const std::string flag = argv[i], value = argv[i + 1];
            if (flag == "--device") options.device = value;
            else if (flag == "--dtype") options.dtype = value;
            else if (flag == "--phase") options.phase = value;
            else if (flag == "--case") options.name = value;
            else if (flag == "--batch-size") options.batch = std::stoll(value);
            else if (flag == "--width") options.width = std::stoll(value);
            else if (flag == "--layers") options.layers = std::stoll(value);
            else if (flag == "--repetitions") options.repetitions = std::stoi(value);
            else if (flag == "--warmups") options.warmups = std::stoi(value);
            else if (flag == "--chunk-steps") options.chunk_steps = static_cast<std::size_t>(std::stoull(value));
            else if (flag == "--temperature") options.temperature = std::stod(value);
            else if (flag == "--top-k") options.top_k = std::stoll(value);
            else if (flag == "--top-p") options.top_p = std::stod(value);
            else if (flag == "--native-plan") { require(value == "true" || value == "false", "native-plan requires true/false"); options.native_plan = value == "true"; }
            else throw std::runtime_error("unknown option");
        }
        require(options.batch == 1 || options.batch == 8, "batch must be one or eight");
        require((options.width == 32 && options.layers == 2) || (options.width == 128 && options.layers == 3), "unsupported bounded model");
        require(options.repetitions >= 2 && options.repetitions <= 100 && options.warmups >= 1 && options.warmups <= 4, "invalid repeat protocol");
        require(gen::device_supported(options.device, options.dtype), "explicit device/precision unavailable; no fallback");
        const auto workload = work(options);
        const auto model = gen::to(fixture_model(options, workload), options.device, options.dtype);
        std::cout << std::setprecision(17);
        if (options.phase == "natural") natural(model, workload, options);
        else if (options.phase == "reference") reference_request(model, workload, options);
        else if (options.phase == "forced") forced(model, workload, options);
        else if (options.phase == "oracle") oracle(model, workload, options);
        else if (options.phase == "resident") resident(model, workload, options);
        else if (options.phase == "kv_oracle") kv_oracle(model, workload, options);
        else if (options.phase == "weights") weights(model);
        else throw std::runtime_error("unknown phase");
        return 0;
    } catch (const std::exception &error) {
        std::cerr << error.what() << '\n'; return 1;
    }
}
