Warrior_EA/AI/ComputeDll.mqh
AnimateDread ccfe5563e3 perf(tester,bn): no sub-second timer in the tester + BN kernels on the DLL tier
THE OPTIMIZER ("0.1% an hour per agent", 0 of 39 passes in 78 min,
12 agents): the tester fires OnTimer on SIMULATED time, so the live
chart's 500ms EventSetMillisecondTimer over a 2016-2026 pass is ~600
MILLION OnTimer calls - each walking 4x PollTraining, the vote
readout's string build, the overlay advance and the deployed census.
None of it serves an inference-only pass: training never runs, per-bar
inference is driven by OnTickHandler off the tick stream, the risk
budget re-checks in OnTick, and there is no chart to keep fresh.
StepSetTimer now arms EventSetTimer(3600) in tester/optimizer/forward
(~2,600 calls per pass) and keeps the 500ms timer for live charts.

Plus a TESTER PASS SELF-PROFILE: per-tick buckets (pre / Expert.OnTick
/ journal) and the timer total, printed once at the pass's OnDeinit -
so if a pass is still slow it names its own consumer instead of being
diagnosed from outside.

OFFLOAD (operator: "as much calculation as possible to DLL/OpenCL"):
batch norm was the ONE stage still host-side on the DLL tier - the
device path was OpenCL-only, so every sample crossed the bus twice per
BN layer and normalized in interpreted MQL5 (and every model runs
batchnorm ON). Four new exports mirror AI\Network.cl's BatchNorm*
kernels 1:1 in DOUBLE precision (closer to the host reference than
the float OpenCL kernels): forward with running stats + frozen flag,
hidden gradient with the clamp derivative, gamma/beta accumulate, and
the batch-mean apply (no weight decay, moments-before-skip ordering,
sqrt-stored v). BnDeviceEligible/EnsureBnDeviceBuffers/all four
Dispatch* now route by backend; the EXISTING in-situ self-checks
(host-vs-device on the first real sample, latch-off + host fallback on
mismatch) verify the DLL kernels exactly as they verified OpenCL ones.

batch_accum_check regression: ALL CHECKS PASSED on the rebuilt DLL.
Same deployment coupling as bd46374: the .ex5 imports the new exports
- copy DirectML\WarriorCPU.dll into MQL5\Libraries (terminal closed)
together with the new .ex5, and re-copy it to the tester agents (or
just run DirectML\build_cpu.bat once with everything closed - it
deploys to every discovered Libraries folder).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 20:44:18 -04:00

221 lines
20 KiB
MQL5

//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| ComputeDll.mqh |
//| AnimateDread |
//| https://www.mql5.com |
//| CComputeDll + the WarriorCPU.dll #import block it wraps - the |
//| multithreaded-CPU compute tier used when OpenCL is unavailable. |
//| Self-contained - nothing outside this class touches CPU_* import |
//| functions directly. |
//| Formerly NeuronDirectML.mqh / CDirectMLMy: the D3D12/DirectML GPU |
//| tier (WarriorDML.dll) it also used to front was removed entirely |
//| (operator directive 2026-08-23) - three backends left: OpenCL, |
//| this CPU DLL, pure MQL5. See project_solid_campaign_plan S1.5. |
//+------------------------------------------------------------------+
//| CPU thread-pool fallback tier - used when OpenCL is unavailable |
//| (e.g. a VM with no GPU passthrough). Full double precision, work |
//| spread across a configurable pool of worker threads instead of a |
//| device. Backed by DirectML\WarriorCPU.dll (build it with |
//| DirectML\build_cpu.bat). |
//+------------------------------------------------------------------+
// Every function below (besides CPU_Init/CPU_GetHardwareConcurrency) takes `ctx`, the opaque
// per-instance handle CPU_Init() returns - see DirectML\WarriorCPU.h's comment for why: the DLL
// keeps no global state of its own, so each CComputeDll instance owns an independent thread pool
// and the DLL can be loaded/used by any number of instances or threads in parallel with zero
// cross-talk between them.
#import "WarriorCPU.dll"
long CPU_Init(int threads);
int CPU_GetLastError(long ctx);
int CPU_GetThreadCount(long ctx);
int CPU_GetHardwareConcurrency();
void CPU_Shutdown(long ctx);
int CPU_BufferCreate(long ctx, int elementCount);
int CPU_BufferWrite(long ctx, int handle, const double &data[], int count);
int CPU_BufferRead(long ctx, int handle, double &data[], int count);
void CPU_BufferFree(long ctx, int handle);
int CPU_FeedForward(long ctx, int wHandle, int iHandle, int oHandle, int inputs, int activation);
int CPU_CalcOutputGradient(long ctx, int tHandle, int oHandle, int igHandle, int activation, int count);
int CPU_CalcHiddenGradient(long ctx, int wHandle, int gHandle, int oHandle, int igHandle, int outputs, int activation, int count);
int CPU_UpdateWeightsMomentum(long ctx, int wHandle, int gHandle, int iHandle, int dwHandle, int inputs, double learningRate, double momentumRate, int neurons, int optimizer);
int CPU_UpdateWeightsAdam(long ctx, int wHandle, int gHandle, int iHandle, int mHandle, int vHandle, int inputs, double lt, double b1v, double b2v, int neurons);
int CPU_FeedForwardConv(long ctx, int wHandle, int iHandle, int oHandle, int inputs, int step, int windowIn, int windowOut, int activation, int positions);
int CPU_CalcHiddenGradientConv(long ctx, int wHandle, int gHandle, int oHandle, int igHandle, int outputs, int step, int windowIn, int windowOut, int activation, int inputCount);
int CPU_UpdateWeightsConvMomentum(long ctx, int wHandle, int gHandle, int iHandle, int dwHandle, int inputs, double learningRate, double momentumRate, int windowIn, int windowOut, int step, int optimizer);
int CPU_UpdateWeightsConvAdam(long ctx, int wHandle, int gHandle, int iHandle, int mHandle, int vHandle, int inputs, double lt, double b1v, double b2v, int windowIn, int windowOut, int step);
int CPU_LSTMGates(long ctx, int wHandle, int hiddenPrevHandle, int inputsHandle, int concatenatedHandle, int hiddenSize, int inputSize);
int CPU_LSTMState(long ctx, int concatenatedHandle, int memoryHandle, int hiddenPrevHandle, int hiddenCacheHandle, int outputHandle, int hiddenSize);
int CPU_LSTMGateGradient(long ctx, int gradientHandle, int memoryHandle, int concatenatedHandle, int concatenatedGradientHandle, int hiddenSize);
int CPU_LSTMWeightsGradient(long ctx, int concatenatedGradientHandle, int hiddenCacheHandle, int inputsHandle, int weightsGradientHandle, int hiddenSize, int inputSize);
int CPU_LSTMInputsGradient(long ctx, int concatenatedGradientHandle, int wHandle, int inputsGradientHandle, int hiddenSize, int inputSize);
int CPU_LSTMSeqForward(long ctx, int wHandle, int inputsHandle, int cacheGatesHandle, int cacheCellHandle, int cacheHiddenHandle, int outputHandle, int hiddenSize, int stepInputs, int steps);
int CPU_LSTMSeqBackward(long ctx, int wHandle, int inputsHandle, int cacheGatesHandle, int cacheCellHandle, int cacheHiddenHandle, int outGradientHandle, int weightsGradientHandle, int inputsGradientHandle, int hiddenSize, int stepInputs, int steps);
int CPU_LSTMUpdateWeightsAdam(long ctx, int wHandle, int weightsGradientHandle, int mHandle, int vHandle, double l, double b1v, double b2v, int total);
int CPU_LSTMUpdateWeightsMomentum(long ctx, int wHandle, int weightsGradientHandle, int dwHandle, double learningRate, double momentumRate, int total, int optimizer);
int CPU_FeedForwardProof(long ctx, int iHandle, int oHandle, int inputs, int window, int step, int outputs);
int CPU_CalcInputGradientProof(long ctx, int iHandle, int gHandle, int oHandle, int igHandle, int outputs, int window, int step, int inputs);
int CPU_AccumulateWeightGrad(long ctx, int accHandle, int gHandle, int iHandle, int inputs, int neurons);
int CPU_AccumulateWeightGradConv(long ctx, int accHandle, int gHandle, int iHandle, int inputs, int windowIn, int windowOut, int step);
int CPU_AccumulateBufferInto(long ctx, int dstHandle, int srcHandle, int count);
int CPU_ApplyAccumAdam(long ctx, int wHandle, int accHandle, int mHandle, int vHandle, int total, double scale, double lt, double b1v, double b2v);
int CPU_ApplyAccumMomentum(long ctx, int wHandle, int accHandle, int dwHandle, int total, double scale, double learningRate, double momentumRate);
int CPU_BatchNormForward(long ctx, int iHandle, int oHandle, int optHandle, double w, int frozen, int count);
int CPU_BatchNormHiddenGrad(long ctx, int gHandle, int prevOHandle, int prevGHandle, int optHandle, int activation, int count);
int CPU_BatchNormAccumGammaBeta(long ctx, int gHandle, int optHandle, int accHandle, int count);
int CPU_BatchNormApplyGammaBeta(long ctx, int optHandle, int accHandle, double scale, double lt, double b1v, double b2v, double lr, double momentumRate, int optimizer, int count);
#import
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
enum ENUM_COMPUTE_TIER
{
COMPUTE_TIER_NONE = 0,
COMPUTE_TIER_CPU = 2 // multithreaded fallback - WarriorCPU.dll
};
//+------------------------------------------------------------------+
//| CPU DLL fallback wrapper. Used when OpenCL is unavailable (no HW |
//| accel, e.g. a VM), so callers just get "multithreaded acceleration,|
//| or NULL" without caring about the backend's internals. |
//+------------------------------------------------------------------+
class CComputeDll
{
private:
ENUM_COMPUTE_TIER m_tier;
int m_cpuLoadPercent; // 100 = auto/all cores; 10-90 scales down from the detected count
//--- opaque per-instance context handle for the CPU DLL tier (0 when m_tier==NONE). WarriorCPU.dll
//--- keeps no global state of its own - each CPU_Init() call heap-allocates an independent context
//--- (own thread pool, own buffer table) and this handle is the caller's only reference to it,
//--- threaded through every single call below. This is what makes the DLL safe to load/reload and
//--- use from any number of CNet instances at once: a fault or a watchdog-killed call against one
//--- instance's context can never poison another chart's or another CNet's calls the way a
//--- shared/global singleton could.
long m_ctx;
//--- CPU_Init()'s failure reason, captured immediately since a failed Init() hands back no context
//--- to read it from afterward - LastError() falls back to this when m_tier is NONE.
int m_initError;
public:
CComputeDll(void) : m_tier(COMPUTE_TIER_NONE), m_cpuLoadPercent(100), m_ctx(0), m_initError(0) {};
~CComputeDll(void)
{
if(m_tier == COMPUTE_TIER_CPU)
CPU_Shutdown(m_ctx);
}
//--- pct <= 0 or >= 100 means "let WarriorCPU.dll pick hardware_concurrency() (all cores)";
//--- 10-90 targets that percentage of the auto-detected core count instead.
void SetCpuLoadPercent(int pct) { m_cpuLoadPercent = pct; }
ENUM_COMPUTE_TIER Tier(void) { return m_tier; }
//--- Which DLL is actually behind this object, for diagnostics.
string BackendName(void)
{
switch(m_tier)
{
case COMPUTE_TIER_CPU:
return "CPU-DLL";
default:
return "no-backend";
}
}
int CpuThreadsUsed(void) { return (m_tier == COMPUTE_TIER_CPU) ? CPU_GetThreadCount(m_ctx) : 0; }
bool Initialize(void)
{
int threads = 0; // 0 = auto (hardware_concurrency, i.e. all cores)
if(m_cpuLoadPercent > 0 && m_cpuLoadPercent < 100)
{
// CPU_GetHardwareConcurrency() is a stateless OS query, independent of any other
// instance's pool size - each CNet gets its own CPU_Init() context now, so there is no
// shared pool for this to be scaled down by the way there used to be.
int detected = CPU_GetHardwareConcurrency();
threads = (int)MathMax(1, MathRound(detected * m_cpuLoadPercent / 100.0));
}
long cpuCtx = CPU_Init(threads);
if(cpuCtx != 0)
{
m_ctx = cpuCtx;
m_tier = COMPUTE_TIER_CPU;
return true;
}
// CPU-DLL init failed - every layer now falls through to the slow plain-MQL5 CPU path with no
// diagnostic trail explaining the resulting behavior/performance change unless this is logged here.
m_initError = -1;
Print(__FUNCTION__ + ": CPU-DLL init failed (CPU_Init returned no context) - falling back to the plain-MQL5 CPU tier.");
m_tier = COMPUTE_TIER_NONE;
return false;
}
int LastError(void) { return (m_tier == COMPUTE_TIER_NONE) ? m_initError : CPU_GetLastError(m_ctx); }
int BufferCreate(int count)
{ return CPU_BufferCreate(m_ctx, count); }
bool BufferWrite(int handle, double &data[], int count)
{ return CPU_BufferWrite(m_ctx, handle, data, count) != 0; }
bool BufferRead(int handle, double &data[], int count)
{ return CPU_BufferRead(m_ctx, handle, data, count) != 0; }
void BufferFree(int handle)
{ CPU_BufferFree(m_ctx, handle); }
bool FeedForward(int wHandle, int iHandle, int oHandle, int inputs, int activation)
{ return CPU_FeedForward(m_ctx, wHandle, iHandle, oHandle, inputs, activation) != 0; }
bool CalcOutputGradient(int tHandle, int oHandle, int igHandle, int activation, int count)
{ return CPU_CalcOutputGradient(m_ctx, tHandle, oHandle, igHandle, activation, count) != 0; }
bool CalcHiddenGradient(int wHandle, int gHandle, int oHandle, int igHandle, int outputs, int activation, int count)
{ return CPU_CalcHiddenGradient(m_ctx, wHandle, gHandle, oHandle, igHandle, outputs, activation, count) != 0; }
bool UpdateWeightsMomentum(int wHandle, int gHandle, int iHandle, int dwHandle, int inputs, double learningRate, double momentumRate, int neurons, int optimizer)
{ return CPU_UpdateWeightsMomentum(m_ctx, wHandle, gHandle, iHandle, dwHandle, inputs, learningRate, momentumRate, neurons, optimizer) != 0; }
bool UpdateWeightsAdam(int wHandle, int gHandle, int iHandle, int mHandle, int vHandle, int inputs, double lt, double b1v, double b2v, int neurons)
{ return CPU_UpdateWeightsAdam(m_ctx, wHandle, gHandle, iHandle, mHandle, vHandle, inputs, lt, b1v, b2v, neurons) != 0; }
bool FeedForwardConv(int wHandle, int iHandle, int oHandle, int inputs, int step, int windowIn, int windowOut, int activation, int positions)
{ return CPU_FeedForwardConv(m_ctx, wHandle, iHandle, oHandle, inputs, step, windowIn, windowOut, activation, positions) != 0; }
bool CalcHiddenGradientConv(int wHandle, int gHandle, int oHandle, int igHandle, int outputs, int step, int windowIn, int windowOut, int activation, int inputCount)
{ return CPU_CalcHiddenGradientConv(m_ctx, wHandle, gHandle, oHandle, igHandle, outputs, step, windowIn, windowOut, activation, inputCount) != 0; }
bool UpdateWeightsConvMomentum(int wHandle, int gHandle, int iHandle, int dwHandle, int inputs, double learningRate, double momentumRate, int windowIn, int windowOut, int step, int optimizer)
{ return CPU_UpdateWeightsConvMomentum(m_ctx, wHandle, gHandle, iHandle, dwHandle, inputs, learningRate, momentumRate, windowIn, windowOut, step, optimizer) != 0; }
bool UpdateWeightsConvAdam(int wHandle, int gHandle, int iHandle, int mHandle, int vHandle, int inputs, double lt, double b1v, double b2v, int windowIn, int windowOut, int step)
{ return CPU_UpdateWeightsConvAdam(m_ctx, wHandle, gHandle, iHandle, mHandle, vHandle, inputs, lt, b1v, b2v, windowIn, windowOut, step) != 0; }
//--- Mini-batch gradient accumulation (2026-08-09 audit, F4). Accumulation only - the optimizer step
//--- is host-side MQL5, so there is no matching Apply* here; see CNeuronBaseOCL::ApplyAccumulatedGradients.
bool AccumulateWeightGrad(int accHandle, int gHandle, int iHandle, int inputs, int neurons)
{ return CPU_AccumulateWeightGrad(m_ctx, accHandle, gHandle, iHandle, inputs, neurons) != 0; }
bool AccumulateWeightGradConv(int accHandle, int gHandle, int iHandle, int inputs, int windowIn, int windowOut, int step)
{ return CPU_AccumulateWeightGradConv(m_ctx, accHandle, gHandle, iHandle, inputs, windowIn, windowOut, step) != 0; }
bool AccumulateBufferInto(int dstHandle, int srcHandle, int count)
{ return CPU_AccumulateBufferInto(m_ctx, dstHandle, srcHandle, count) != 0; }
//--- Mini-batch apply (2026-08-25): the optimizer step on the batch mean, device-side. See
//--- CPU_ApplyAccumAdam in WarriorCPU.h - without these the step ran as a per-weight MQL5 loop.
bool ApplyAccumAdam(int wHandle, int accHandle, int mHandle, int vHandle, int total, double scale, double lt, double b1v, double b2v)
{ return CPU_ApplyAccumAdam(m_ctx, wHandle, accHandle, mHandle, vHandle, total, scale, lt, b1v, b2v) != 0; }
bool ApplyAccumMomentum(int wHandle, int accHandle, int dwHandle, int total, double scale, double learningRate, double momentumRate)
{ return CPU_ApplyAccumMomentum(m_ctx, wHandle, accHandle, dwHandle, total, scale, learningRate, momentumRate) != 0; }
//--- Batch norm (2026-08-25) - see CPU_BatchNormForward in WarriorCPU.h. These are what let a BN
//--- layer run device-side on the DLL tier; the OpenCL kernels remain the device path there.
bool BatchNormForward(int iHandle, int oHandle, int optHandle, double w, int frozen, int count)
{ return CPU_BatchNormForward(m_ctx, iHandle, oHandle, optHandle, w, frozen, count) != 0; }
bool BatchNormHiddenGrad(int gHandle, int prevOHandle, int prevGHandle, int optHandle, int activation, int count)
{ return CPU_BatchNormHiddenGrad(m_ctx, gHandle, prevOHandle, prevGHandle, optHandle, activation, count) != 0; }
bool BatchNormAccumGammaBeta(int gHandle, int optHandle, int accHandle, int count)
{ return CPU_BatchNormAccumGammaBeta(m_ctx, gHandle, optHandle, accHandle, count) != 0; }
bool BatchNormApplyGammaBeta(int optHandle, int accHandle, double scale, double lt, double b1v, double b2v, double lr, double momentumRate, int optimizer, int count)
{ return CPU_BatchNormApplyGammaBeta(m_ctx, optHandle, accHandle, scale, lt, b1v, b2v, lr, momentumRate, optimizer, count) != 0; }
//--- Fused unrolled sequence LSTM. These are what a sequence model uses; the per-step LSTMGates/
//--- LSTMState/LSTM*Gradient below remain only for the legacy single-timestep layer.
bool LSTMSeqForward(int wHandle, int inputsHandle, int cacheGatesHandle, int cacheCellHandle, int cacheHiddenHandle, int outputHandle, int hiddenSize, int stepInputs, int steps)
{ return CPU_LSTMSeqForward(m_ctx, wHandle, inputsHandle, cacheGatesHandle, cacheCellHandle, cacheHiddenHandle, outputHandle, hiddenSize, stepInputs, steps) != 0; }
bool LSTMSeqBackward(int wHandle, int inputsHandle, int cacheGatesHandle, int cacheCellHandle, int cacheHiddenHandle, int outGradientHandle, int weightsGradientHandle, int inputsGradientHandle, int hiddenSize, int stepInputs, int steps)
{ return CPU_LSTMSeqBackward(m_ctx, wHandle, inputsHandle, cacheGatesHandle, cacheCellHandle, cacheHiddenHandle, outGradientHandle, weightsGradientHandle, inputsGradientHandle, hiddenSize, stepInputs, steps) != 0; }
bool LSTMGates(int wHandle, int hiddenPrevHandle, int inputsHandle, int concatenatedHandle, int hiddenSize, int inputSize)
{ return CPU_LSTMGates(m_ctx, wHandle, hiddenPrevHandle, inputsHandle, concatenatedHandle, hiddenSize, inputSize) != 0; }
bool LSTMState(int concatenatedHandle, int memoryHandle, int hiddenPrevHandle, int hiddenCacheHandle, int outputHandle, int hiddenSize)
{ return CPU_LSTMState(m_ctx, concatenatedHandle, memoryHandle, hiddenPrevHandle, hiddenCacheHandle, outputHandle, hiddenSize) != 0; }
bool LSTMGateGradient(int gradientHandle, int memoryHandle, int concatenatedHandle, int concatenatedGradientHandle, int hiddenSize)
{ return CPU_LSTMGateGradient(m_ctx, gradientHandle, memoryHandle, concatenatedHandle, concatenatedGradientHandle, hiddenSize) != 0; }
bool LSTMWeightsGradient(int concatenatedGradientHandle, int hiddenCacheHandle, int inputsHandle, int weightsGradientHandle, int hiddenSize, int inputSize)
{ return CPU_LSTMWeightsGradient(m_ctx, concatenatedGradientHandle, hiddenCacheHandle, inputsHandle, weightsGradientHandle, hiddenSize, inputSize) != 0; }
bool LSTMInputsGradient(int concatenatedGradientHandle, int wHandle, int inputsGradientHandle, int hiddenSize, int inputSize)
{ return CPU_LSTMInputsGradient(m_ctx, concatenatedGradientHandle, wHandle, inputsGradientHandle, hiddenSize, inputSize) != 0; }
bool LSTMUpdateWeightsAdam(int wHandle, int weightsGradientHandle, int mHandle, int vHandle, double l, double b1v, double b2v, int total)
{ return CPU_LSTMUpdateWeightsAdam(m_ctx, wHandle, weightsGradientHandle, mHandle, vHandle, l, b1v, b2v, total) != 0; }
bool LSTMUpdateWeightsMomentum(int wHandle, int weightsGradientHandle, int dwHandle, double learningRate, double momentumRate, int total, int optimizer)
{ return CPU_LSTMUpdateWeightsMomentum(m_ctx, wHandle, weightsGradientHandle, dwHandle, learningRate, momentumRate, total, optimizer) != 0; }
bool FeedForwardProof(int iHandle, int oHandle, int inputs, int window, int step, int outputs)
{ return CPU_FeedForwardProof(m_ctx, iHandle, oHandle, inputs, window, step, outputs) != 0; }
bool CalcInputGradientProof(int iHandle, int gHandle, int oHandle, int igHandle, int outputs, int window, int step, int inputs)
{ return CPU_CalcInputGradientProof(m_ctx, iHandle, gHandle, oHandle, igHandle, outputs, window, step, inputs) != 0; }
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+