Warrior_EA/DirectML/WarriorDML.h
AnimateDread 0c01dc279b feat: mini-batch gradient accumulation (F4), front-end-aware capacity budget (F6), split Wyckoff categoricals (N1)
Completes the 2026-08-09 training audit. FORCES A RETRAIN of every
Wyckoff-enabled config (N1 re-keys the fingerprint), and BOTH DLLs must be
redeployed alongside the .ex5 - they carry new exports.

F4 - mini-batch accumulation, TRAIN_BATCH_SIZE=32. Training was pure online
SGD (one weight update per bar), which is the mechanical source of the
era-to-era whipsaw every downstream guard was built to cope with. The O(n^2)
outer product is native - AccumulateWeightGrad / AccumulateWeightGradConv /
AccumulateBufferInto in Network.cl, WarriorCPU and WarriorDML - while the
optimizer step is host-side MQL5 shared by all tiers (ApplyAccumToBlock), so
there is one Adam/SGD implementation instead of four that can drift.
  - the LSTM needs no outer-product kernel (WeightsGradient already holds the
    sample's full dW) but could NOT simply be left un-zeroed between samples:
    CPU_LSTMSeqBackward/DML_LSTMSeqBackward memset it on entry. Hence a
    separate accumulator plus an elementwise add.
  - batch-norm gamma/beta accumulate in host arrays, not new BatchOptions
    slots - BN_OPT_STRIDE is baked into every persisted .nnw.
  - scoped to pass 2; online learning keeps immediate updates. Every save /
    checkpoint / scoring boundary flushes, scaling by the real sample count.
  - degrades to per-sample updates (one log line) on a tier that cannot
    accumulate, so old devices and DLL-free builds are unaffected.
  - verified offline: DirectML/batch_accum_check.cpp drives the real exports
    against an independent reference; at B=1 the accumulator matches the
    shipped unbatched kernel's own gradient to 1.1e-16. Math only - the
    in-situ check remains the per-layer dW/W report on a real era.

F6 - ComputeFirstLayerWidth budgeted against the RAW input width even where a
conv/LSTM front end had already reduced it, so an LSTM's dense stack was
charged for 1,280 inputs when it receives 64. Confirmed from the deployed
.cfg files: CONV, LSTM and HYBRID were all pinned at the 16-unit floor. Now
budgeted against the front-end output and capped at it (never fan out), with
the derivation reordered so both stages settle first.

N1 - EventCode/EventPhase/StructuralPhase are signed categoricals packing
direction and Wyckoff stage into one scalar across a sign discontinuity. Split
into direction + [0,1] magnitude, the same convention the base OHLC block uses.
Information-preserving; 13 readings now occupy 16 inputs.

Compiled clean (0 errors, 0 warnings); both DLLs rebuilt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 11:48:03 -04:00

147 lines
9 KiB
C

//+------------------------------------------------------------------+
//| Warrior_EA |
//| D3D12 compute fallback for GPUs|
//| without OpenCL (DirectX12/D3D)|
//+------------------------------------------------------------------+
// Flat C ABI so MQL5 can #import this DLL directly. Every buffer is a
// plain array of 64-bit doubles on the MQL5 side; internally the GPU
// works in 32-bit floats (see WarriorDML.cpp for why) and this layer
// does the conversion, so callers never see the difference.
#pragma once
#define WARRIORDML_API extern "C" __declspec(dllexport)
// Every exported function (besides DML_Init) takes a DmlHandle as its first
// argument - an opaque pointer to a heap-allocated, self-contained context
// (its own D3D12 device/queue/command list/fence, compiled kernels and
// buffer table) that DML_Init() allocates and the caller (CDirectMLMy on the
// MQL5 side) is responsible for remembering and passing back on every
// subsequent call, then releasing via DML_Shutdown(). No state is shared
// between contexts and this DLL keeps no global/static mutable state of its
// own, so it can be loaded any number of times and driven by any number of
// instances/threads in parallel - a driver fault, a device-removal, or a
// wedged call against one context can never poison another context's calls,
// unlike a process-wide singleton device would.
// A plain `long long` (not C++ `long`, which is only 32 bits on Windows) so
// it round-trips exactly through MQL5's 64-bit `long` on the #import side.
typedef long long DmlHandle;
// Lifecycle. DML_Init() returns a non-zero handle on success (a usable D3D12
// device was created), 0 otherwise - callers should fall back to CPU on 0. A
// failed attempt allocates no context to hand back (there is nothing to pass
// to DML_Shutdown() or DML_GetLastError()), so the failure stage is instead
// written directly to *outError (may be NULL if the caller doesn't care) so
// the MQL5 log can show *why* (e.g. "no DX12 adapter" vs "kernel compile
// failed") without this DLL needing to keep any state alive past the failure.
// See DML_GetLastError() below for the error codes.
WARRIORDML_API DmlHandle __stdcall DML_Init(int *outError);
WARRIORDML_API void __stdcall DML_Shutdown(DmlHandle ctx);
// 0 = no error / not yet run
// 1 = CreateDXGIFactory1 failed
// 2 = no hardware D3D_FEATURE_LEVEL_11_0 adapter found (only software/none enumerated)
// 3 = command queue creation failed
// 4 = command allocator creation failed
// 5 = command list creation failed
// 6 = fence creation failed
// 7 = fence event creation failed
// 8 = one or more compute kernels failed to compile/create (HLSL/PSO)
// 9 = device lost (TDR/removal/wedge) after a successful Init, or a fault
// during a kernel dispatch
// Reads the error off a live context (e.g. after a DML_* call returns 0) -
// see DML_Init()'s outError parameter for reading an Init() failure itself.
WARRIORDML_API int __stdcall DML_GetLastError(DmlHandle ctx);
// Buffer management. Handles are small non-negative integers scoped to ctx;
// -1 means failure. Buffers are GPU-resident; Write uploads, Read reads back.
WARRIORDML_API int __stdcall DML_BufferCreate(DmlHandle ctx, int elementCount);
WARRIORDML_API int __stdcall DML_BufferWrite(DmlHandle ctx, int handle, const double *data, int count);
WARRIORDML_API int __stdcall DML_BufferRead(DmlHandle ctx, int handle, double *data, int count);
WARRIORDML_API void __stdcall DML_BufferFree(DmlHandle ctx, int handle);
// Compute kernels - argument order/semantics mirror AI\Network.cl 1:1.
// activation: 0 = TANH, 1 = SIGMOID.
WARRIORDML_API int __stdcall DML_FeedForward(DmlHandle ctx, int wHandle, int iHandle, int oHandle,
int inputs, int activation);
WARRIORDML_API int __stdcall DML_CalcOutputGradient(DmlHandle ctx, int tHandle, int oHandle, int igHandle,
int activation, int count);
WARRIORDML_API int __stdcall DML_CalcHiddenGradient(DmlHandle ctx, int wHandle, int gHandle, int oHandle, int igHandle,
int outputs, int activation, int count);
WARRIORDML_API int __stdcall DML_UpdateWeightsMomentum(DmlHandle ctx, int wHandle, int gHandle, int iHandle, int dwHandle,
int inputs, double learningRate, double momentumRate, int neurons, int optimizer);
WARRIORDML_API int __stdcall DML_UpdateWeightsAdam(DmlHandle ctx, int wHandle, int gHandle, int iHandle,
int mHandle, int vHandle,
int inputs, double lt, double b1, double b2, int neurons);
// Convolution layer kernels (CNeuronConvOCL) - mirror AI\Network.cl's
// FeedForwardConv/CalcHiddenGradientConv/UpdateWeightsConv* 1:1. A single
// (windowIn+1)*windowOut weight block is shared across every sliding position.
// activation: 0 = TANH, 1 = SIGMOID, 2 = PReLU (fixed param 0.01).
WARRIORDML_API int __stdcall DML_FeedForwardConv(DmlHandle ctx, int wHandle, int iHandle, int oHandle,
int inputs, int step, int windowIn, int windowOut, int activation, int positions);
WARRIORDML_API int __stdcall DML_CalcHiddenGradientConv(DmlHandle ctx, int wHandle, int gHandle, int oHandle, int igHandle,
int outputs, int step, int windowIn, int windowOut, int activation, int inputCount);
WARRIORDML_API int __stdcall DML_UpdateWeightsConvMomentum(DmlHandle ctx, int wHandle, int gHandle, int iHandle, int dwHandle,
int inputs, double learningRate, double momentumRate, int windowIn, int windowOut, int step, int optimizer);
// Mini-batch gradient accumulation (2026-08-09 audit, F4) - see WarriorCPU.h's matching pair. Adds one
// sample's per-weight gradient into an accumulator; the optimizer step is host-side MQL5 once per batch.
WARRIORDML_API int __stdcall DML_AccumulateWeightGrad(DmlHandle ctx, int accHandle, int gHandle, int iHandle,
int inputs, int neurons);
WARRIORDML_API int __stdcall DML_AccumulateWeightGradConv(DmlHandle ctx, int accHandle, int gHandle, int iHandle,
int inputs, int windowIn, int windowOut, int step);
WARRIORDML_API int __stdcall DML_AccumulateBufferInto(DmlHandle ctx, int dstHandle, int srcHandle, int count);
WARRIORDML_API int __stdcall DML_UpdateWeightsConvAdam(DmlHandle ctx, int wHandle, int gHandle, int iHandle,
int mHandle, int vHandle,
int inputs, double lt, double b1, double b2, int windowIn, int windowOut, int step);
// LSTM layer kernels (CNeuronLSTMOCL) - single-timestep-truncated BPTT,
// Adam-only. Derived from the standard LSTM equations, NOT ported from any
// reference (see AI\Network.cl for why). Gate order: forget, input, output,
// candidate; weight layout per gate is a H*(H+I+1) block (H weights on
// h_prev, I weights on the current input, 1 bias), 4 such blocks total.
WARRIORDML_API int __stdcall DML_LSTMGates(DmlHandle ctx, int wHandle, int hiddenPrevHandle, int inputsHandle,
int concatenatedHandle, int hiddenSize, int inputSize);
WARRIORDML_API int __stdcall DML_LSTMState(DmlHandle ctx, int concatenatedHandle, int memoryHandle, int hiddenPrevHandle,
int hiddenCacheHandle, int outputHandle, int hiddenSize);
WARRIORDML_API int __stdcall DML_LSTMGateGradient(DmlHandle ctx, int gradientHandle, int memoryHandle, int concatenatedHandle,
int concatenatedGradientHandle, int hiddenSize);
WARRIORDML_API int __stdcall DML_LSTMWeightsGradient(DmlHandle ctx, int concatenatedGradientHandle, int hiddenCacheHandle,
int inputsHandle, int weightsGradientHandle, int hiddenSize, int inputSize);
WARRIORDML_API int __stdcall DML_LSTMInputsGradient(DmlHandle ctx, int concatenatedGradientHandle, int wHandle,
int inputsGradientHandle, int hiddenSize, int inputSize);
// Fused unrolled sequence LSTM - see the block comments in WarriorDML.cpp (host-side by design) and
// WarriorCPU.cpp (the authoritative contract).
WARRIORDML_API int __stdcall DML_LSTMSeqForward(DmlHandle ctx, int wHandle, int inputsHandle,
int cacheGatesHandle, int cacheCellHandle, int cacheHiddenHandle, int outputHandle,
int hiddenSize, int stepInputs, int steps);
WARRIORDML_API int __stdcall DML_LSTMSeqBackward(DmlHandle ctx, int wHandle, int inputsHandle,
int cacheGatesHandle, int cacheCellHandle, int cacheHiddenHandle, int outGradientHandle,
int weightsGradientHandle, int inputsGradientHandle, int hiddenSize, int stepInputs, int steps);
WARRIORDML_API int __stdcall DML_LSTMUpdateWeightsAdam(DmlHandle ctx, int wHandle, int weightsGradientHandle,
int mHandle, int vHandle, double l, double b1, double b2, int total);
WARRIORDML_API int __stdcall DML_LSTMUpdateWeightsMomentum(DmlHandle ctx, int wHandle, int weightsGradientHandle,
int dwHandle, double learningRate, double momentumRate, int total, int optimizer);
// Max-pooling layer kernels (CNeuronPoolOCL) - no weights.
WARRIORDML_API int __stdcall DML_FeedForwardProof(DmlHandle ctx, int iHandle, int oHandle, int inputs, int window, int step, int outputs);
WARRIORDML_API int __stdcall DML_CalcInputGradientProof(DmlHandle ctx, int iHandle, int gHandle, int oHandle, int igHandle,
int outputs, int window, int step, int inputs);