// Backend interface: batched numerical primitives over CSR panels. Every primitive has a CPU FP64 reference; // the Metal backend implements the same contract in FP32 (+ compensated partial sums combined in FP64 on the CPU). #pragma once #include #include #include #include #include "raid/core/types.hpp" #include "raid/core/plan.hpp" namespace raid::backend { /// Batched input: `values` concatenated, `offsets` (n_series+1). All kernels are segment-wise over series. struct Batch { std::span values; std::span offsets; usize n_series() const { return offsets.empty() ? 0 : offsets.size() - 1; } usize size(usize i) const { return static_cast(offsets[i + 1] - offsets[i]); } std::span series(usize i) const { return values.subspan(static_cast(offsets[i]), size(i)); } }; /// Timing / provenance of one primitive call (filled by every backend). struct KernelStats { std::string kernel; Backend backend{Backend::cpu}; Precision precision{Precision::fp64}; f64 wall_seconds{0}; f64 device_seconds{0}; // GPU time (Metal GPUStartTime/EndTime) or 0 usize bytes_in{0}, bytes_out{0}; usize buffer_allocations{0}; bool compiled_now{false}; // first shader compilation happened in this call bool fell_back{false}; // Metal → CPU fallback occurred std::string note; }; class Backend { public: virtual ~Backend() = default; virtual std::string name() const = 0; virtual raid::Backend kind() const = 0; virtual Device device() const = 0; virtual bool available() const = 0; /// Σ x² per series (realized variance when x are returns). virtual std::vector sum_squares(const Batch& b, KernelStats* st = nullptr) = 0; /// Σ |x_i| |x_{i-1}| per series (bipower core, without constants). virtual std::vector sum_abs_adjacent(const Batch& b, KernelStats* st = nullptr) = 0; /// Σ x² 1{x<0}, Σ x² 1{x>0} per series (semivariances). virtual void semivariances(const Batch& b, std::vector& neg, std::vector& pos, KernelStats* st = nullptr) = 0; /// Σ x⁴ per series (realized quarticity core). virtual std::vector sum_fourth(const Batch& b, KernelStats* st = nullptr) = 0; /// Rolling sum of squares with window w per series; output same layout as input (NaN for incomplete windows). virtual std::vector rolling_sum_squares(const Batch& b, usize window, KernelStats* st = nullptr) = 0; /// Batched sufficient statistics for regressions with k regressors: for each series i, X_i is (n_i × k) row-major /// stored in `x` (concatenated), y_i in `y`; outputs xtx (k×k per series, row-major), xty (k per series), yty. virtual void regression_stats(std::span x, std::span y, std::span offsets, usize k, std::vector& xtx, std::vector& xty, std::vector& yty, KernelStats* st = nullptr) = 0; /// Batched GARCH(1,1)-family negative log-likelihood for a grid of parameter vectors on each series. /// params: n_series × n_params × p (row-major); returns nll (n_series × n_params). Model codes in models/garch.hpp. virtual std::vector garch_nll_batch(const Batch& returns, std::span params, usize n_params, usize p, int model_code, f64 nu_fixed, KernelStats* st = nullptr) = 0; }; std::shared_ptr make_cpu_backend(unsigned threads = 0); #ifdef RAID_ENABLE_METAL std::shared_ptr make_metal_backend(const PrecisionPolicy& policy = {}); #endif /// Resolve a plan to a concrete backend (auto → Metal if available and allowed, else CPU). Never throws for auto. std::shared_ptr resolve(const ExecutionPlan& plan, std::string* reason = nullptr); std::vector list_devices(); } // namespace raid::backend namespace raid::backend { #ifdef RAID_ENABLE_METAL /// Metal-only fused realized kernel (one pass for Σx², Σ|x||x₋₁|, Σx²1{x<0}, Σx²1{x>0}, Σx⁴). Returns false if `be` is not Metal. bool realized_fused(Backend& be, const Batch& b, std::vector& s2, std::vector& adj, std::vector& neg, std::vector& pos, std::vector& s4, KernelStats* st = nullptr); /// Seconds spent compiling/loading the shader library (0 if not Metal or not yet loaded). f64 metal_compile_seconds(Backend& be); #endif }