Warrior_EA/DirectML/WarriorDML.cpp

1319 lines
53 KiB
C++
Raw Permalink Normal View History

//+------------------------------------------------------------------+
//| Warrior_EA |
//| D3D12 compute fallback kernels, semantics mirror |
//| AI\Network.cl exactly (see that file for reference). |
//+------------------------------------------------------------------+
// Why float instead of double: DXGI_FORMAT/typed UAV double support is
// spotty across consumer GPUs (D3D12_FEATURE_DATA_D3D12_OPTIONS::
// DoublePrecisionFloatShaderOps is frequently false on gaming cards),
// while 32-bit float compute is universally supported on any DX12/D3D12
// capable device. This tier only exists for hardware that has neither
// OpenCL nor a "real" GPU compute story, so we trade a little precision
// for something that actually runs; the OpenCL and CPU paths remain
// full double precision. Conversion happens at the buffer boundary so
// MQL5 never has to know.
#include "WarriorDML.h"
#include <d3d12.h>
#include <dxgi1_4.h>
#include <d3dcompiler.h>
#include <wrl/client.h>
#include <vector>
#include <string>
#include <mutex>
#include <cmath>
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3dcompiler.lib")
using Microsoft::WRL::ComPtr;
namespace
{
struct GpuBuffer
{
ComPtr<ID3D12Resource> gpu; // default heap, UAV state, RWStructuredBuffer<float>
ComPtr<ID3D12Resource> upload; // upload heap staging buffer, sized to match
ComPtr<ID3D12Resource> readback; // readback heap staging buffer, sized to match
UINT count = 0;
bool inUse = false;
};
struct Kernel
{
ComPtr<ID3D12RootSignature> rootSig;
ComPtr<ID3D12PipelineState> pso;
};
//+------------------------------------------------------------------+
//| Everything one CDirectMLMy/CNet instance needs, heap-allocated by |
//| DML_Init() and handed back as an opaque DmlHandle. No part of |
//| this DLL keeps any global or static mutable state - every export |
//| below (besides DML_Init) operates only on the DmlContext its |
//| caller passes in, so a device-removal, a driver fault, or a |
//| wedged wait can only ever strand its own context (and only the |
//| one GPU device it owns), never another instance's, and the DLL |
//| can be loaded/used by any number of instances or threads in |
//| parallel with zero cross-talk - including each getting its own |
//| D3D12 device, which multiple instances in one process can hold |
//| concurrently without contention. |
//+------------------------------------------------------------------+
struct DmlContext
{
// Guards buffer-table bookkeeping and the command-list submit+wait sequence for THIS context's
// device only. Unlike WarriorCPU.cpp's pool (which can dispatch many chunks across many worker
// threads at once), this context has exactly one shared command allocator/list, so the whole
// submit+wait sequence must stay serialized end to end - narrowing the lock scope here would let
// a second call reset the command allocator while the GPU is still using it, which is undefined
// behavior at the driver level. A driver TDR normally recovers within ~2s and CheckDeviceLost()
// below would catch it anyway; WaitForGPU()'s bounded timeout only fires if the wait itself never
// returns (a genuinely wedged driver) - without it, MQL5's DLL-call watchdog could kill this
// thread mid-wait while `mutex` is held, leaving it locked forever. Because this mutex belongs to
// one context alone, that can only strand this one instance, never any other chart's calls.
std::mutex mutex;
ComPtr<ID3D12Device> device;
ComPtr<ID3D12CommandQueue> queue;
ComPtr<ID3D12CommandAllocator> allocator;
ComPtr<ID3D12GraphicsCommandList> cmdList;
ComPtr<ID3D12Fence> fence;
HANDLE fenceEvent = nullptr;
UINT64 fenceValue = 0;
// Set once GetDeviceRemovedReason() confirms the adapter is gone (driver crash/reset/TDR, GPU
// physically unplugged on a hot-swap rig, etc.). Every dispatch keeps trying a dead device
// otherwise - each call pays a full driver-call timeout before FAILED() finally trips, so a lost
// GPU silently turns training into a multi-second-per-call stall instead of a clean, fast,
// detectable failure. Latches until this context is torn down (DML_Shutdown) and a fresh one is
// created by another DML_Init() call - there is no in-place recovery.
bool deviceLost = false;
int lastError = 0;
std::vector<GpuBuffer> buffers;
Kernel kFeedForward, kOutputGradient, kHiddenGradient, kMomentum, kAdam;
Kernel kFeedForwardConv, kHiddenGradientConv, kMomentumConv, kAdamConv;
Kernel kLstmGates, kLstmState, kLstmGateGradient, kLstmWeightsGradient, kLstmInputsGradient, kLstmUpdateWeightsAdam, kLstmUpdateWeightsMomentum;
Kernel kFeedForwardProof, kCalcInputGradientProof;
~DmlContext()
{
if(fenceEvent)
CloseHandle(fenceEvent);
}
};
void CheckDeviceLost(DmlContext *ctx)
{
if(ctx->device && ctx->device->GetDeviceRemovedReason() != S_OK)
{
ctx->deviceLost = true;
ctx->lastError = 9;
}
}
//+------------------------------------------------------------------+
//| GPU sync helper - waits for all work submitted so far |
//+------------------------------------------------------------------+
bool WaitForGPU(DmlContext *ctx)
{
if(!ctx->queue || !ctx->fence)
return false;
ctx->fenceValue++;
if(FAILED(ctx->queue->Signal(ctx->fence.Get(), ctx->fenceValue)))
{
CheckDeviceLost(ctx);
return false;
}
if(ctx->fence->GetCompletedValue() < ctx->fenceValue)
{
if(FAILED(ctx->fence->SetEventOnCompletion(ctx->fenceValue, ctx->fenceEvent)))
{
CheckDeviceLost(ctx);
return false;
}
const DWORD FENCE_WAIT_TIMEOUT_MS = 15000;
if(WaitForSingleObject(ctx->fenceEvent, FENCE_WAIT_TIMEOUT_MS) != WAIT_OBJECT_0)
{
ctx->deviceLost = true; // wedged/unresponsive device - stop trusting it
ctx->lastError = 9;
return false;
}
}
CheckDeviceLost(ctx);
return !ctx->deviceLost;
}
bool ResetCommandList(DmlContext *ctx)
{
if(FAILED(ctx->allocator->Reset()))
return false;
if(FAILED(ctx->cmdList->Reset(ctx->allocator.Get(), nullptr)))
return false;
return true;
}
//+------------------------------------------------------------------+
//| Build a root signature: N root UAVs (u0..u(N-1)) + a root |
//| constants block (b0) holding constDwords 32-bit values. |
//+------------------------------------------------------------------+
bool CreateRootSignature(DmlContext *ctx, int numUAV, int constDwords, ComPtr<ID3D12RootSignature> &outSig)
{
std::vector<D3D12_ROOT_PARAMETER> params;
params.reserve(numUAV + 1);
for(int i = 0; i < numUAV; i++)
{
D3D12_ROOT_PARAMETER p = {};
p.ParameterType = D3D12_ROOT_PARAMETER_TYPE_UAV;
p.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
p.Descriptor.ShaderRegister = i;
p.Descriptor.RegisterSpace = 0;
params.push_back(p);
}
if(constDwords > 0)
{
D3D12_ROOT_PARAMETER p = {};
p.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
p.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
p.Constants.ShaderRegister = 0;
p.Constants.RegisterSpace = 0;
p.Constants.Num32BitValues = constDwords;
params.push_back(p);
}
D3D12_ROOT_SIGNATURE_DESC desc = {};
desc.NumParameters = (UINT)params.size();
desc.pParameters = params.data();
desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_NONE;
ComPtr<ID3DBlob> blob, error;
if(FAILED(D3D12SerializeRootSignature(&desc, D3D_ROOT_SIGNATURE_VERSION_1, &blob, &error)))
return false;
return SUCCEEDED(ctx->device->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(),
IID_PPV_ARGS(&outSig)));
}
bool CompileKernel(DmlContext *ctx, const char *hlsl, int numUAV, int constDwords, Kernel &out)
{
ComPtr<ID3DBlob> code, error;
HRESULT hr = D3DCompile(hlsl, strlen(hlsl), nullptr, nullptr, nullptr,
"main", "cs_5_0", 0, 0, &code, &error);
if(FAILED(hr))
return false; // error->GetBufferPointer() has the message if debugging is needed
if(!CreateRootSignature(ctx, numUAV, constDwords, out.rootSig))
return false;
D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.pRootSignature = out.rootSig.Get();
psoDesc.CS = {code->GetBufferPointer(), code->GetBufferSize()};
return SUCCEEDED(ctx->device->CreateComputePipelineState(&psoDesc, IID_PPV_ARGS(&out.pso)));
}
//+------------------------------------------------------------------+
//| HLSL sources - one-to-one with the OpenCL kernels in Network.cl. |
//| Scalar-per-thread (no double4 batching - GPU is wide enough that |
//| the manual SIMD packing in the OpenCL version buys little here, |
//| and it keeps this file auditable against the .cl reference). |
//+------------------------------------------------------------------+
const char *kHlslFeedForward = R"(
RWStructuredBuffer<float> matrix_w : register(u0);
RWStructuredBuffer<float> matrix_i : register(u1);
RWStructuredBuffer<float> matrix_o : register(u2);
cbuffer Consts : register(b0) { int inputs; int activation; }
[numthreads(64,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int i = (int)id.x;
int shift = (inputs + 1) * i;
float sum = 0.0;
for(int k = 0; k < inputs; k++)
sum += matrix_i[k] * matrix_w[shift + k];
sum += matrix_w[shift + inputs]; // bias
if(activation == 0)
sum = tanh(sum);
else if(activation == 1)
sum = 1.0 / (1.0 + exp(-clamp(sum, -50.0, 50.0)));
matrix_o[i] = sum;
}
)";
const char *kHlslOutputGradient = R"(
RWStructuredBuffer<float> matrix_t : register(u0);
RWStructuredBuffer<float> matrix_o : register(u1);
RWStructuredBuffer<float> matrix_ig : register(u2);
cbuffer Consts : register(b0) { int activation; }
[numthreads(64,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int i = (int)id.x;
float out_v = matrix_o[i];
float temp = 0.0;
if(activation == 0)
{
// Not multiplied by the tanh derivative (1-out^2) - see WarriorCPU.cpp's
// CPU_CalcOutputGradient for why (it vanishes exactly at the +-1
// targets a bounded output neuron needs to reach, e.g. a regression
// head's buy/sell extremes).
temp = clamp(matrix_t[i], -1.0, 1.0) - out_v;
}
else if(activation == 1)
{
// Also deliberately NOT multiplied by the sigmoid derivative out_v*(1-out_v) - see
// WarriorCPU.cpp's CPU_CalcOutputGradient for the full reasoning. This is the
// classification output layer (3 neurons, one-hot 0/1 targets); (target-out) unscaled
// is exactly the sigmoid+binary-cross-entropy gradient, whereas re-multiplying by
// out_v*(1-out_v) damps toward zero as out_v approaches 0 or 1 - exactly where a
// 0/1-target neuron needs to converge, and was observed in practice as all 3 output
// neurons converging to (and never escaping) an identical value.
temp = clamp(matrix_t[i], 0.0, 1.0) - out_v;
}
else
{
// NONE (raw logits - the 3-class softmax classification output layer) and PRELU (2, never
// actually used as an OUTPUT activation in this codebase) both fall here. Identity/NONE has
// derivative 1 everywhere, so the plain (target-out) error passes through unscaled -
// matching CNeuron::calcOutputGradients()'s unconditional formula on the MQL5-side
// un-accelerated fallback. Leaving this case unhandled left temp at its 0.0 initializer,
// i.e. every softmax-classification output neuron got a permanently zero gradient on this
// DLL - and since kHlslHiddenGradient propagates backward FROM the output layer's gradient,
// that silently froze the whole network (not just the output layer) at its random initial
// weights whenever the DirectML/GPU tier was active.
temp = matrix_t[i] - out_v;
}
matrix_ig[i] = temp;
}
)";
const char *kHlslHiddenGradient = R"(
RWStructuredBuffer<float> matrix_w : register(u0);
RWStructuredBuffer<float> matrix_g : register(u1);
RWStructuredBuffer<float> matrix_o : register(u2);
RWStructuredBuffer<float> matrix_ig : register(u3);
cbuffer Consts : register(b0) { int outputs; int activation; }
[numthreads(64,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int i = (int)id.x;
int shift = (outputs + 1) * i;
float out_v = matrix_o[i];
float sum = 0.0;
for(int k = 0; k < outputs; k++)
sum += matrix_g[k] * matrix_w[shift + k];
if(activation == 0)
{
sum = clamp(sum + out_v, -1.0, 1.0) - out_v;
sum = sum * max(1e-4, 1.0 - out_v * out_v);
}
else if(activation == 1)
{
sum = clamp(sum + out_v, 0.0, 1.0) - out_v;
sum = sum * max(1e-4, out_v * (1.0 - out_v));
}
else if(activation == 2 && out_v < 0.0)
{
// PReLU, param=0.01 - see WarriorCPU.cpp's matching CPU_CalcHiddenGradient comment for the
// full rationale. This branch was missing, so every hidden PReLU layer's backprop gradient
// was left completely unscaled (1.0 instead of 0.01) whenever out_v was negative, on this
// (DirectML GPU) tier.
sum = sum * 0.01;
}
matrix_ig[i] = sum;
}
)";
// Bias weight (j == inputs) is intentionally never touched here - this
// matches the existing OpenCL UpdateWeightsMomentum kernel's dispatch
// range (0..inputs-1) exactly, so SGD behaves identically on both backends.
// The +-100 clamp on every matrix_w write below (here and in kHlslAdam/kHlslMomentumConv/
// kHlslAdamConv/kHlslLstmUpdateWeightsAdam) mirrors AI\Network.cl's MAX_WEIGHT: without it a gradient
// spike can drive a weight to +-Infinity, and the next Adam step turns that into NaN (Inf/Inf) that
// propagates through every FeedForward sum touching it and never recovers. Tightened from 1e6 - see
// WarriorCPU.cpp's matching MAX_WEIGHT comment for why that ceiling was too loose to ever engage.
// Every Adam kernel below (kHlslAdam/kHlslAdamConv/kHlslLstmUpdateWeightsAdam) also applies a
// decoupled AdamW-style weight decay (the inline "- l * 0.001 * matrix_w[...]" term) - see
// WarriorCPU.cpp's WEIGHT_DECAY comment for the full rationale. HLSL string kernels can't share a
// C++ #define, so the 0.001 is inlined directly; keep it in sync with WarriorCPU.cpp/Network.cl's
// WEIGHT_DECAY if that ever changes. Same reasoning for the inline 1e-4 MIN_ACTIVATION_DERIVATIVE
// floor used in the tanh/sigmoid hidden-gradient and LSTM gate kernels below - keep in sync with
// WarriorCPU.cpp/Network.cl/Network.mqh's MIN_ACTIVATION_DERIVATIVE if that ever changes. The Adam
// kernels below apply their step unconditionally - the sign-agreement gate that used to sit on every
// matrix_w write was removed (see WarriorCPU.cpp's CPU_UpdateWeightsAdam comment for why: it
// rectified the one-hot softmax-CCE gradient stream into an all-Neutral collapse).
const char *kHlslMomentum = R"(
RWStructuredBuffer<float> matrix_w : register(u0);
RWStructuredBuffer<float> matrix_g : register(u1);
RWStructuredBuffer<float> matrix_i : register(u2);
RWStructuredBuffer<float> matrix_dw : register(u3);
cbuffer Consts : register(b0) { int inputs; float learning_rate; float momentum; }
[numthreads(8,8,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int i = (int)id.x;
int j = (int)id.y;
if(j >= inputs)
return;
int wi = i * (inputs + 1) + j;
float delta = learning_rate * matrix_g[i] * matrix_i[j] + momentum * matrix_dw[wi];
matrix_dw[wi] = delta;
matrix_w[wi] = clamp(matrix_w[wi] + delta, -100.0, 100.0);
}
)";
const char *kHlslAdam = R"(
RWStructuredBuffer<float> matrix_w : register(u0);
RWStructuredBuffer<float> matrix_g : register(u1);
RWStructuredBuffer<float> matrix_i : register(u2);
RWStructuredBuffer<float> matrix_m : register(u3);
RWStructuredBuffer<float> matrix_v : register(u4);
cbuffer Consts : register(b0) { int inputs; float l; float b1; float b2; }
[numthreads(8,8,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int i = (int)id.x;
int j = (int)id.y;
if(j > inputs)
return;
int wi = i * (inputs + 1) + j;
float inp = (j < inputs) ? matrix_i[j] : 1.0;
float g = matrix_g[i] * inp;
float mt = b1 * matrix_m[wi] + (1.0 - b1) * g;
float vt = sqrt(b2 * matrix_v[wi] + (1.0 - b2) * g * g);
float delta = clamp(l * mt / (vt > 0.0 ? vt : l * 10.0) - l * 0.001 * matrix_w[wi], -0.1, 0.1);
matrix_w[wi] = clamp(matrix_w[wi] + delta, -100.0, 100.0);
matrix_m[wi] = mt;
matrix_v[wi] = vt;
}
)";
//+------------------------------------------------------------------+
//| Max-pooling layer - no weights, mirrors AI\Network.cl's |
//| FeedForwardProof/CalcInputGradientProof. |
//+------------------------------------------------------------------+
const char *kHlslFeedForwardProof = R"(
RWStructuredBuffer<float> matrix_i : register(u0);
RWStructuredBuffer<float> matrix_o : register(u1);
cbuffer Consts : register(b0) { int inputs; int window; int step; }
[numthreads(64,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int i = (int)id.x;
int pos = i * step;
float result = matrix_i[pos];
for(int k = 1; k < window; k++)
{
int shift = k + pos;
if(shift >= inputs)
break;
result = max(result, matrix_i[shift]);
}
matrix_o[i] = result;
}
)";
const char *kHlslCalcInputGradientProof = R"(
RWStructuredBuffer<float> matrix_i : register(u0);
RWStructuredBuffer<float> matrix_g : register(u1);
RWStructuredBuffer<float> matrix_o : register(u2);
RWStructuredBuffer<float> matrix_ig : register(u3);
cbuffer Consts : register(b0) { int outputs; int window; int step; }
[numthreads(64,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int i = (int)id.x;
float value = matrix_i[i];
int start = i - window + step;
start = (start - start % step) / step;
int stop = (i - i % step) / step + 1;
float prevGrad = 0.0;
for(int out = max(0, start); out < min(outputs, stop); out++)
if(value == matrix_o[out])
prevGrad += matrix_g[out];
matrix_ig[i] = prevGrad;
}
)";
//+------------------------------------------------------------------+
//| Convolution kernels - ported alongside AI\Network.cl's Conv |
//| kernels. A single (windowIn+1)*windowOut weight block is shared |
//| across every sliding position. |
//+------------------------------------------------------------------+
const char *kHlslFeedForwardConv = R"(
RWStructuredBuffer<float> matrix_w : register(u0);
RWStructuredBuffer<float> matrix_i : register(u1);
RWStructuredBuffer<float> matrix_o : register(u2);
cbuffer Consts : register(b0) { int inputs; int step; int windowIn; int windowOut; int activation; }
[numthreads(64,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int i = (int)id.x;
int shiftOut = windowOut * i;
int shiftIn = step * i;
for(int out = 0; out < windowOut; out++)
{
int shift = (windowIn + 1) * out;
int stop = (windowIn <= (inputs - shiftIn)) ? windowIn : (inputs - shiftIn);
float sum = 0.0;
for(int k = 0; k < stop; k++)
sum += matrix_i[shiftIn + k] * matrix_w[shift + k];
sum += matrix_w[shift + windowIn];
if(activation == 0)
sum = tanh(sum);
else if(activation == 1)
sum = 1.0 / (1.0 + exp(-clamp(sum, -50.0, 50.0)));
else if(activation == 2 && sum < 0)
sum *= 0.01;
matrix_o[out + shiftOut] = sum;
}
}
)";
const char *kHlslHiddenGradientConv = R"(
RWStructuredBuffer<float> matrix_w : register(u0);
RWStructuredBuffer<float> matrix_g : register(u1);
RWStructuredBuffer<float> matrix_o : register(u2);
RWStructuredBuffer<float> matrix_ig : register(u3);
cbuffer Consts : register(b0) { int outputs; int step; int windowIn; int windowOut; int activation; }
[numthreads(64,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int i = (int)id.x;
float out_v = matrix_o[i];
int start = i - windowIn + step;
start = max((start - start % step) / step, 0);
int stop = (i - i % step) / step + 1;
if(stop > (outputs / windowOut))
stop = outputs / windowOut;
float sum = 0.0;
for(int h = 0; h < windowOut; h++)
{
for(int k = start; k < stop; k++)
{
int shiftW = (stop - k - 1) * step + i % step + h * (windowIn + 1);
int shiftG = k * windowOut + h;
if(shiftG >= outputs || shiftW >= (windowIn + 1) * windowOut)
break;
sum += matrix_g[shiftG] * matrix_w[shiftW];
}
}
if(activation == 0)
{
sum = clamp(sum + out_v, -1.0, 1.0) - out_v;
sum = sum * max(1e-4, 1.0 - out_v * out_v);
}
else if(activation == 1)
sum = (clamp(sum + out_v, 0.0, 1.0) - out_v) * max(1e-4, out_v * (1.0 - out_v));
else if(activation == 2 && out_v < 0)
sum *= 0.01;
matrix_ig[i] = sum;
}
)";
const char *kHlslMomentumConv = R"(
RWStructuredBuffer<float> matrix_w : register(u0);
RWStructuredBuffer<float> matrix_g : register(u1);
RWStructuredBuffer<float> matrix_i : register(u2);
RWStructuredBuffer<float> matrix_dw : register(u3);
cbuffer Consts : register(b0) { int inputs; float learningRate; float momentum; int windowIn; int windowOut; int step; }
[numthreads(64,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int i = (int)id.x;
if(i >= (windowIn + 1) * windowOut)
return;
int shift = i % (windowIn + 1);
int shiftOut = (i - shift) / (windowIn + 1);
int total = (inputs - windowIn) % step;
total = (inputs - windowIn - total) / step + (total > 0 ? 1 : 0);
float grad = 0.0;
for(int t = 0; t < total; t++)
{
if(shift != windowIn && (shift + t * step) >= inputs)
break;
grad += matrix_g[t * windowOut + shiftOut] * (shift == windowIn ? 1.0 : matrix_i[shift + t * step]);
}
float delta = learningRate * grad + momentum * matrix_dw[i];
matrix_dw[i] = delta;
matrix_w[i] = clamp(matrix_w[i] + delta, -100.0, 100.0);
}
)";
const char *kHlslAdamConv = R"(
RWStructuredBuffer<float> matrix_w : register(u0);
RWStructuredBuffer<float> matrix_g : register(u1);
RWStructuredBuffer<float> matrix_i : register(u2);
RWStructuredBuffer<float> matrix_m : register(u3);
RWStructuredBuffer<float> matrix_v : register(u4);
cbuffer Consts : register(b0) { int inputs; float l; float b1; float b2; int windowIn; int windowOut; int step; }
[numthreads(64,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int i = (int)id.x;
if(i > windowIn)
return;
int total = (inputs - (windowIn - step)) % step;
total = (inputs - (windowIn - step) - total) / step + (total > 0 ? 1 : 0);
for(int out = 0; out < windowOut; out++)
{
int shiftW = i + out * (windowIn + 1);
float grad = 0.0;
for(int t = 0; t < total; t++)
{
if(i != windowIn && (i + t * step) >= inputs)
break;
grad += matrix_g[t * windowOut + out] * (i == windowIn ? 1.0 : matrix_i[i + t * step]);
}
float mt = b1 * matrix_m[shiftW] + (1.0 - b1) * grad;
float vt = sqrt(b2 * matrix_v[shiftW] + (1.0 - b2) * grad * grad);
float delta = clamp(l * mt / (vt > 0.0 ? vt : l * 10.0) - l * 0.001 * matrix_w[shiftW], -0.1, 0.1);
matrix_w[shiftW] = clamp(matrix_w[shiftW] + delta, -100.0, 100.0);
matrix_m[shiftW] = mt;
matrix_v[shiftW] = vt;
}
}
)";
//+------------------------------------------------------------------+
//| LSTM kernels - mirror AI\Network.cl's LSTM_* kernels 1:1. See the |
//| comment above those kernels for the derivation notes (single- |
//| timestep-truncated BPTT, Adam-only, NOT ported from any reference).|
//+------------------------------------------------------------------+
const char *kHlslLstmGates = R"(
RWStructuredBuffer<float> matrix_w : register(u0);
RWStructuredBuffer<float> hidden_prev : register(u1);
RWStructuredBuffer<float> inputs : register(u2);
RWStructuredBuffer<float> concatenated : register(u3);
cbuffer Consts : register(b0) { int hiddenSize; int inputSize; }
[numthreads(8,4,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int hid = (int)id.x;
int gate = (int)id.y;
if(hid >= hiddenSize || gate >= 4)
return;
int perGate = hiddenSize * (hiddenSize + inputSize + 1);
int shift = gate * perGate + hid * (hiddenSize + inputSize + 1);
float sum = 0.0;
for(int k = 0; k < hiddenSize; k++)
sum += hidden_prev[k] * matrix_w[shift + k];
for(int k = 0; k < inputSize; k++)
sum += inputs[k] * matrix_w[shift + hiddenSize + k];
sum += matrix_w[shift + hiddenSize + inputSize];
float val = (gate < 3) ? (1.0 / (1.0 + exp(-sum))) : tanh(sum);
concatenated[gate * hiddenSize + hid] = val;
}
)";
const char *kHlslLstmState = R"(
RWStructuredBuffer<float> concatenated : register(u0);
RWStructuredBuffer<float> memory : register(u1);
RWStructuredBuffer<float> hidden_prev : register(u2);
RWStructuredBuffer<float> hidden_cache : register(u3);
RWStructuredBuffer<float> output : register(u4);
cbuffer Consts : register(b0) { int hiddenSize; }
[numthreads(64,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int i = (int)id.x;
if(i >= hiddenSize)
return;
float f = concatenated[i];
float ii = concatenated[hiddenSize + i];
float o = concatenated[2 * hiddenSize + i];
float g = concatenated[3 * hiddenSize + i];
float c_prev = memory[i];
memory[hiddenSize + i] = c_prev;
float c_t = f * c_prev + ii * g;
memory[i] = c_t;
hidden_cache[i] = hidden_prev[i];
output[i] = o * tanh(c_t);
}
)";
const char *kHlslLstmGateGradient = R"(
RWStructuredBuffer<float> gradient : register(u0);
RWStructuredBuffer<float> memory : register(u1);
RWStructuredBuffer<float> concatenated : register(u2);
RWStructuredBuffer<float> concatenated_gradient : register(u3);
cbuffer Consts : register(b0) { int hiddenSize; }
[numthreads(64,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int i = (int)id.x;
if(i >= hiddenSize)
return;
float c_t = memory[i];
float c_prev = memory[hiddenSize + i];
float f = concatenated[i];
float ii = concatenated[hiddenSize + i];
float o = concatenated[2 * hiddenSize + i];
float g = concatenated[3 * hiddenSize + i];
float t = tanh(c_t);
float dh = gradient[i];
float dc = dh * o * max(1e-4, 1.0 - t * t);
concatenated_gradient[2 * hiddenSize + i] = dh * t * max(1e-4, o * (1.0 - o));
concatenated_gradient[i] = dc * c_prev * max(1e-4, f * (1.0 - f));
concatenated_gradient[hiddenSize + i] = dc * g * max(1e-4, ii * (1.0 - ii));
concatenated_gradient[3 * hiddenSize + i] = dc * ii * max(1e-4, 1.0 - g * g);
}
)";
const char *kHlslLstmWeightsGradient = R"(
RWStructuredBuffer<float> concatenated_gradient : register(u0);
RWStructuredBuffer<float> hidden_cache : register(u1);
RWStructuredBuffer<float> inputs : register(u2);
RWStructuredBuffer<float> weights_gradient : register(u3);
cbuffer Consts : register(b0) { int hiddenSize; int inputSize; }
[numthreads(64,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int wi = (int)id.x;
int perGate = hiddenSize * (hiddenSize + inputSize + 1);
if(wi >= 4 * perGate)
return;
int gate = wi / perGate;
int rem = wi % perGate;
int hid = rem / (hiddenSize + inputSize + 1);
int k = rem % (hiddenSize + inputSize + 1);
float inp = (k < hiddenSize) ? hidden_cache[k] : ((k < hiddenSize + inputSize) ? inputs[k - hiddenSize] : 1.0);
weights_gradient[wi] = concatenated_gradient[gate * hiddenSize + hid] * inp;
}
)";
const char *kHlslLstmInputsGradient = R"(
RWStructuredBuffer<float> concatenated_gradient : register(u0);
RWStructuredBuffer<float> matrix_w : register(u1);
RWStructuredBuffer<float> inputs_gradient : register(u2);
cbuffer Consts : register(b0) { int hiddenSize; int inputSize; }
[numthreads(64,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int j = (int)id.x;
if(j >= inputSize)
return;
int perGate = hiddenSize * (hiddenSize + inputSize + 1);
float sum = 0.0;
for(int gate = 0; gate < 4; gate++)
for(int hid = 0; hid < hiddenSize; hid++)
sum += concatenated_gradient[gate * hiddenSize + hid] * matrix_w[gate * perGate + hid * (hiddenSize + inputSize + 1) + hiddenSize + j];
inputs_gradient[j] = sum;
}
)";
const char *kHlslLstmUpdateWeightsAdam = R"(
RWStructuredBuffer<float> matrix_w : register(u0);
RWStructuredBuffer<float> weights_gradient : register(u1);
RWStructuredBuffer<float> matrix_m : register(u2);
RWStructuredBuffer<float> matrix_v : register(u3);
cbuffer Consts : register(b0) { float l; float b1; float b2; int total; }
[numthreads(64,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int wi = (int)id.x;
if(wi >= total)
return;
float g = weights_gradient[wi];
float mt = b1 * matrix_m[wi] + (1.0 - b1) * g;
float vt = sqrt(b2 * matrix_v[wi] + (1.0 - b2) * g * g);
float delta = clamp(l * mt / (vt > 0.0 ? vt : l * 10.0) - l * 0.001 * matrix_w[wi], -0.1, 0.1);
matrix_w[wi] = clamp(matrix_w[wi] + delta, -100.0, 100.0);
matrix_m[wi] = mt;
matrix_v[wi] = vt;
}
)";
// SGD+momentum counterpart to kHlslLstmUpdateWeightsAdam above - same flat
// (already-elementwise) weights_gradient/matrix_dw layout as
// CPU_LSTMUpdateWeightsMomentum, just the classic heavy-ball update.
const char *kHlslLstmUpdateWeightsMomentum = R"(
RWStructuredBuffer<float> matrix_w : register(u0);
RWStructuredBuffer<float> weights_gradient : register(u1);
RWStructuredBuffer<float> matrix_dw : register(u2);
cbuffer Consts : register(b0) { float learning_rate; float momentum; int total; }
[numthreads(64,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
int wi = (int)id.x;
if(wi >= total)
return;
float delta = learning_rate * weights_gradient[wi] + momentum * matrix_dw[wi];
matrix_dw[wi] = delta;
matrix_w[wi] = clamp(matrix_w[wi] + delta, -100.0, 100.0);
}
)";
//+------------------------------------------------------------------+
//| Buffer helpers |
//+------------------------------------------------------------------+
int AllocBufferSlot(DmlContext *ctx)
{
for(size_t i = 0; i < ctx->buffers.size(); i++)
if(!ctx->buffers[i].inUse)
return (int)i;
ctx->buffers.push_back(GpuBuffer());
return (int)ctx->buffers.size() - 1;
}
bool CreateUploadBuffer(DmlContext *ctx, UINT64 size, ComPtr<ID3D12Resource> &out)
{
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = D3D12_HEAP_TYPE_UPLOAD;
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Width = size;
desc.Height = 1;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
return SUCCEEDED(ctx->device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc,
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&out)));
}
bool CreateReadbackBuffer(DmlContext *ctx, UINT64 size, ComPtr<ID3D12Resource> &out)
{
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = D3D12_HEAP_TYPE_READBACK;
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Width = size;
desc.Height = 1;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
return SUCCEEDED(ctx->device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc,
D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&out)));
}
bool CreateDefaultUAVBuffer(DmlContext *ctx, UINT64 size, ComPtr<ID3D12Resource> &out)
{
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = D3D12_HEAP_TYPE_DEFAULT;
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Width = size;
desc.Height = 1;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
return SUCCEEDED(ctx->device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS, nullptr, IID_PPV_ARGS(&out)));
}
} // namespace
//+------------------------------------------------------------------+
//| Exported API |
//+------------------------------------------------------------------+
WARRIORDML_API int __stdcall DML_GetLastError(DmlHandle handle)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
return ctx->lastError;
}
namespace
{
// Device creation and shader compilation run entirely inside the GPU vendor's
// driver, which is third-party, closed-source code this project has no
// control over - a buggy driver crashing here must not be allowed to take
// the whole terminal down with it (extracted to its own function, no locals
// with destructors, so it can hold a __try - see WarriorCPU.cpp's SehStartPool
// for the same MSVC C2712 constraint this works around).
bool DML_InitUnguarded(DmlContext *ctx);
bool DML_InitSeh(DmlContext *ctx)
{
__try
{
return DML_InitUnguarded(ctx);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
ctx->lastError = 9;
return false;
}
}
}
WARRIORDML_API DmlHandle __stdcall DML_Init(int *outError)
{
DmlContext *ctx = new DmlContext();
if(!DML_InitSeh(ctx))
{
if(outError)
*outError = ctx->lastError;
delete ctx;
return 0;
}
if(outError)
*outError = 0;
return reinterpret_cast<DmlHandle>(ctx);
}
namespace
{
bool DML_InitUnguarded(DmlContext *ctx)
{
ComPtr<IDXGIFactory4> factory;
if(FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&factory))))
{
ctx->lastError = 1;
return false;
}
ComPtr<IDXGIAdapter1> adapter;
ComPtr<ID3D12Device> device;
for(UINT i = 0; factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND; i++)
{
DXGI_ADAPTER_DESC1 desc;
adapter->GetDesc1(&desc);
if(desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
continue;
if(SUCCEEDED(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device))))
break;
device.Reset();
}
if(!device)
{
ctx->lastError = 2; // no usable DX12 hardware adapter - caller should fall back to CPU
return false;
}
ctx->device = device;
D3D12_COMMAND_QUEUE_DESC qDesc = {};
qDesc.Type = D3D12_COMMAND_LIST_TYPE_COMPUTE;
if(FAILED(ctx->device->CreateCommandQueue(&qDesc, IID_PPV_ARGS(&ctx->queue))))
{
ctx->lastError = 3;
return false;
}
if(FAILED(ctx->device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_COMPUTE, IID_PPV_ARGS(&ctx->allocator))))
{
ctx->lastError = 4;
return false;
}
if(FAILED(ctx->device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_COMPUTE, ctx->allocator.Get(), nullptr,
IID_PPV_ARGS(&ctx->cmdList))))
{
ctx->lastError = 5;
return false;
}
ctx->cmdList->Close();
if(FAILED(ctx->device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&ctx->fence))))
{
ctx->lastError = 6;
return false;
}
ctx->fenceValue = 0;
ctx->fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if(!ctx->fenceEvent)
{
ctx->lastError = 7;
return false;
}
if(!CompileKernel(ctx, kHlslFeedForward, 3, 2, ctx->kFeedForward) ||
!CompileKernel(ctx, kHlslOutputGradient, 3, 1, ctx->kOutputGradient) ||
!CompileKernel(ctx, kHlslHiddenGradient, 4, 2, ctx->kHiddenGradient) ||
!CompileKernel(ctx, kHlslMomentum, 4, 3, ctx->kMomentum) ||
!CompileKernel(ctx, kHlslAdam, 5, 4, ctx->kAdam) ||
!CompileKernel(ctx, kHlslFeedForwardConv, 3, 5, ctx->kFeedForwardConv) ||
!CompileKernel(ctx, kHlslHiddenGradientConv, 4, 5, ctx->kHiddenGradientConv) ||
!CompileKernel(ctx, kHlslMomentumConv, 4, 6, ctx->kMomentumConv) ||
!CompileKernel(ctx, kHlslAdamConv, 5, 7, ctx->kAdamConv) ||
!CompileKernel(ctx, kHlslLstmGates, 4, 2, ctx->kLstmGates) ||
!CompileKernel(ctx, kHlslLstmState, 5, 1, ctx->kLstmState) ||
!CompileKernel(ctx, kHlslLstmGateGradient, 4, 1, ctx->kLstmGateGradient) ||
!CompileKernel(ctx, kHlslLstmWeightsGradient, 4, 2, ctx->kLstmWeightsGradient) ||
!CompileKernel(ctx, kHlslLstmInputsGradient, 3, 2, ctx->kLstmInputsGradient) ||
!CompileKernel(ctx, kHlslLstmUpdateWeightsAdam, 4, 4, ctx->kLstmUpdateWeightsAdam) ||
!CompileKernel(ctx, kHlslLstmUpdateWeightsMomentum, 3, 3, ctx->kLstmUpdateWeightsMomentum) ||
!CompileKernel(ctx, kHlslFeedForwardProof, 2, 3, ctx->kFeedForwardProof) ||
!CompileKernel(ctx, kHlslCalcInputGradientProof, 4, 3, ctx->kCalcInputGradientProof))
{
ctx->lastError = 8;
return false;
}
return true;
}
} // namespace
WARRIORDML_API void __stdcall DML_Shutdown(DmlHandle handle)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return;
WaitForGPU(ctx);
delete ctx; // ComPtr members release the device/queue/kernels/buffers themselves
}
WARRIORDML_API int __stdcall DML_BufferCreate(DmlHandle handle, int elementCount)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return -1;
std::lock_guard<std::mutex> lock(ctx->mutex);
if(ctx->deviceLost || elementCount <= 0)
return -1;
int slot = AllocBufferSlot(ctx);
GpuBuffer &b = ctx->buffers[slot];
UINT64 size = (UINT64)elementCount * sizeof(float);
if(!CreateDefaultUAVBuffer(ctx, size, b.gpu) ||
!CreateUploadBuffer(ctx, size, b.upload) ||
!CreateReadbackBuffer(ctx, size, b.readback))
{
b = GpuBuffer();
return -1;
}
b.count = (UINT)elementCount;
b.inUse = true;
return slot;
}
WARRIORDML_API void __stdcall DML_BufferFree(DmlHandle handle, int bufHandle)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return;
std::lock_guard<std::mutex> lock(ctx->mutex);
if(bufHandle < 0 || (size_t)bufHandle >= ctx->buffers.size())
return;
ctx->buffers[bufHandle] = GpuBuffer();
}
WARRIORDML_API int __stdcall DML_BufferWrite(DmlHandle handle, int bufHandle, const double *data, int count)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
if(ctx->deviceLost || bufHandle < 0 || (size_t)bufHandle >= ctx->buffers.size())
return 0;
GpuBuffer &b = ctx->buffers[bufHandle];
if(!b.inUse || (UINT)count > b.count)
return 0;
// Same rationale as WarriorCPU.cpp's CPU_BufferWrite: a NaN/Inf weight or
// gradient doesn't crash the GPU path, it silently poisons every value it
// touches from then on with no error ever surfaced. Reject at the boundary.
for(int i = 0; i < count; i++)
if(!std::isfinite(data[i]))
return 0;
std::vector<float> tmp((size_t)count);
for(int i = 0; i < count; i++)
tmp[i] = (float)data[i];
void *mapped = nullptr;
D3D12_RANGE noRead = {0, 0};
if(FAILED(b.upload->Map(0, &noRead, &mapped)))
return 0;
memcpy(mapped, tmp.data(), tmp.size() * sizeof(float));
b.upload->Unmap(0, nullptr);
if(!ResetCommandList(ctx))
return 0;
ctx->cmdList->CopyBufferRegion(b.gpu.Get(), 0, b.upload.Get(), 0, tmp.size() * sizeof(float));
ctx->cmdList->Close();
ID3D12CommandList *lists[] = {ctx->cmdList.Get()};
ctx->queue->ExecuteCommandLists(1, lists);
return WaitForGPU(ctx) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_BufferRead(DmlHandle handle, int bufHandle, double *data, int count)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
if(ctx->deviceLost || bufHandle < 0 || (size_t)bufHandle >= ctx->buffers.size())
return 0;
GpuBuffer &b = ctx->buffers[bufHandle];
if(!b.inUse || (UINT)count > b.count)
return 0;
if(!ResetCommandList(ctx))
return 0;
ctx->cmdList->CopyBufferRegion(b.readback.Get(), 0, b.gpu.Get(), 0, (UINT64)count * sizeof(float));
ctx->cmdList->Close();
ID3D12CommandList *lists[] = {ctx->cmdList.Get()};
ctx->queue->ExecuteCommandLists(1, lists);
if(!WaitForGPU(ctx))
return 0;
void *mapped = nullptr;
D3D12_RANGE readRange = {0, (SIZE_T)count * sizeof(float)};
if(FAILED(b.readback->Map(0, &readRange, &mapped)))
return 0;
const float *src = (const float *)mapped;
for(int i = 0; i < count; i++)
data[i] = (double)src[i];
D3D12_RANGE noWrite = {0, 0};
b.readback->Unmap(0, &noWrite);
return 1;
}
namespace
{
bool DispatchKernelUnguarded(DmlContext *ctx, Kernel &k, const std::vector<int> &bufferHandles,
const void *constants, UINT constDwords, UINT gx, UINT gy)
{
if(ctx->deviceLost)
return false;
for(int h : bufferHandles)
if(h < 0 || (size_t)h >= ctx->buffers.size() || !ctx->buffers[h].inUse)
return false;
if(!ResetCommandList(ctx))
return false;
ctx->cmdList->SetPipelineState(k.pso.Get());
ctx->cmdList->SetComputeRootSignature(k.rootSig.Get());
for(size_t i = 0; i < bufferHandles.size(); i++)
ctx->cmdList->SetComputeRootUnorderedAccessView((UINT)i, ctx->buffers[bufferHandles[i]].gpu->GetGPUVirtualAddress());
if(constDwords > 0)
ctx->cmdList->SetComputeRoot32BitConstants((UINT)bufferHandles.size(), constDwords, constants, 0);
ctx->cmdList->Dispatch(gx, gy, 1);
ctx->cmdList->Close();
ID3D12CommandList *lists[] = {ctx->cmdList.Get()};
ctx->queue->ExecuteCommandLists(1, lists);
return WaitForGPU(ctx);
}
// Fault isolation boundary, same rationale as WarriorCPU.cpp's SehCallFn: a
// driver bug or a genuinely bad COM call here must degrade to "this dispatch
// failed" rather than an access violation that takes the whole terminal down
// with it. Every one of the 16 kernel dispatch functions below routes through
// this single choke point.
bool DispatchKernel(DmlContext *ctx, Kernel &k, const std::vector<int> &bufferHandles,
const void *constants, UINT constDwords, UINT gx, UINT gy)
{
__try
{
return DispatchKernelUnguarded(ctx, k, bufferHandles, constants, constDwords, gx, gy);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
ctx->deviceLost = true; // unknown-state GPU context - don't trust it further
ctx->lastError = 9;
return false;
}
}
UINT CeilDiv(int a, int b) { return (UINT)((a + b - 1) / b); }
}
WARRIORDML_API int __stdcall DML_FeedForward(DmlHandle handle, int wHandle, int iHandle, int oHandle, int inputs, int activation)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int inputs; int activation; } consts = {inputs, activation};
// oHandle is caller-supplied and unchecked at this point - indexing ctx->buffers[oHandle]
// directly (as this used to) is an out-of-bounds vector access for a bad handle,
// crashing before DispatchKernel ever gets to run its own bounds check below.
bool valid = oHandle >= 0 && (size_t)oHandle < ctx->buffers.size() && ctx->buffers[oHandle].inUse;
int neurons = valid ? (int)ctx->buffers[oHandle].count : 0;
return DispatchKernel(ctx, ctx->kFeedForward, {wHandle, iHandle, oHandle}, &consts, 2, CeilDiv(neurons, 64), 1) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_CalcOutputGradient(DmlHandle handle, int tHandle, int oHandle, int igHandle, int activation, int count)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int activation; } consts = {activation};
return DispatchKernel(ctx, ctx->kOutputGradient, {tHandle, oHandle, igHandle}, &consts, 1, CeilDiv(count, 64), 1) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_CalcHiddenGradient(DmlHandle handle, int wHandle, int gHandle, int oHandle, int igHandle,
int outputs, int activation, int count)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int outputs; int activation; } consts = {outputs, activation};
return DispatchKernel(ctx, ctx->kHiddenGradient, {wHandle, gHandle, oHandle, igHandle}, &consts, 2, CeilDiv(count, 64), 1) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_UpdateWeightsMomentum(DmlHandle handle, int wHandle, int gHandle, int iHandle, int dwHandle,
int inputs, double learningRate, double momentum, int neurons)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int inputs; float learningRate; float momentum; } consts = {inputs, (float)learningRate, (float)momentum};
return DispatchKernel(ctx, ctx->kMomentum, {wHandle, gHandle, iHandle, dwHandle}, &consts, 3,
CeilDiv(neurons, 8), CeilDiv(inputs, 8)) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_UpdateWeightsAdam(DmlHandle handle, int wHandle, int gHandle, int iHandle, int mHandle, int vHandle,
int inputs, double lt, double b1, double b2, int neurons)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int inputs; float l; float b1; float b2; } consts = {inputs, (float)lt, (float)b1, (float)b2};
return DispatchKernel(ctx, ctx->kAdam, {wHandle, gHandle, iHandle, mHandle, vHandle}, &consts, 4,
CeilDiv(neurons, 8), CeilDiv(inputs + 1, 8)) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_FeedForwardConv(DmlHandle handle, int wHandle, int iHandle, int oHandle,
int inputs, int step, int windowIn, int windowOut, int activation, int positions)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int inputs; int step; int windowIn; int windowOut; int activation; } consts =
{inputs, step, windowIn, windowOut, activation};
return DispatchKernel(ctx, ctx->kFeedForwardConv, {wHandle, iHandle, oHandle}, &consts, 5, CeilDiv(positions, 64), 1) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_CalcHiddenGradientConv(DmlHandle handle, int wHandle, int gHandle, int oHandle, int igHandle,
int outputs, int step, int windowIn, int windowOut, int activation, int inputCount)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int outputs; int step; int windowIn; int windowOut; int activation; } consts =
{outputs, step, windowIn, windowOut, activation};
return DispatchKernel(ctx, ctx->kHiddenGradientConv, {wHandle, gHandle, oHandle, igHandle}, &consts, 5, CeilDiv(inputCount, 64), 1) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_UpdateWeightsConvMomentum(DmlHandle handle, int wHandle, int gHandle, int iHandle, int dwHandle,
int inputs, double learningRate, double momentum, int windowIn, int windowOut, int step)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int inputs; float learningRate; float momentum; int windowIn; int windowOut; int step; } consts =
{inputs, (float)learningRate, (float)momentum, windowIn, windowOut, step};
int total = (windowIn + 1) * windowOut;
return DispatchKernel(ctx, ctx->kMomentumConv, {wHandle, gHandle, iHandle, dwHandle}, &consts, 6, CeilDiv(total, 64), 1) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_UpdateWeightsConvAdam(DmlHandle handle, int wHandle, int gHandle, int iHandle, int mHandle, int vHandle,
int inputs, double lt, double b1, double b2, int windowIn, int windowOut, int step)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int inputs; float l; float b1; float b2; int windowIn; int windowOut; int step; } consts =
{inputs, (float)lt, (float)b1, (float)b2, windowIn, windowOut, step};
return DispatchKernel(ctx, ctx->kAdamConv, {wHandle, gHandle, iHandle, mHandle, vHandle}, &consts, 7, CeilDiv(windowIn + 1, 64), 1) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_LSTMGates(DmlHandle handle, int wHandle, int hiddenPrevHandle, int inputsHandle,
int concatenatedHandle, int hiddenSize, int inputSize)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int hiddenSize; int inputSize; } consts = {hiddenSize, inputSize};
return DispatchKernel(ctx, ctx->kLstmGates, {wHandle, hiddenPrevHandle, inputsHandle, concatenatedHandle}, &consts, 2,
CeilDiv(hiddenSize, 8), CeilDiv(4, 4)) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_LSTMState(DmlHandle handle, int concatenatedHandle, int memoryHandle, int hiddenPrevHandle,
int hiddenCacheHandle, int outputHandle, int hiddenSize)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int hiddenSize; } consts = {hiddenSize};
return DispatchKernel(ctx, ctx->kLstmState, {concatenatedHandle, memoryHandle, hiddenPrevHandle, hiddenCacheHandle, outputHandle}, &consts, 1,
CeilDiv(hiddenSize, 64), 1) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_LSTMGateGradient(DmlHandle handle, int gradientHandle, int memoryHandle, int concatenatedHandle,
int concatenatedGradientHandle, int hiddenSize)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int hiddenSize; } consts = {hiddenSize};
return DispatchKernel(ctx, ctx->kLstmGateGradient, {gradientHandle, memoryHandle, concatenatedHandle, concatenatedGradientHandle}, &consts, 1,
CeilDiv(hiddenSize, 64), 1) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_LSTMWeightsGradient(DmlHandle handle, int concatenatedGradientHandle, int hiddenCacheHandle,
int inputsHandle, int weightsGradientHandle, int hiddenSize, int inputSize)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int hiddenSize; int inputSize; } consts = {hiddenSize, inputSize};
int total = 4 * hiddenSize * (hiddenSize + inputSize + 1);
return DispatchKernel(ctx, ctx->kLstmWeightsGradient, {concatenatedGradientHandle, hiddenCacheHandle, inputsHandle, weightsGradientHandle}, &consts, 2,
CeilDiv(total, 64), 1) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_LSTMInputsGradient(DmlHandle handle, int concatenatedGradientHandle, int wHandle,
int inputsGradientHandle, int hiddenSize, int inputSize)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int hiddenSize; int inputSize; } consts = {hiddenSize, inputSize};
return DispatchKernel(ctx, ctx->kLstmInputsGradient, {concatenatedGradientHandle, wHandle, inputsGradientHandle}, &consts, 2,
CeilDiv(inputSize, 64), 1) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_LSTMUpdateWeightsAdam(DmlHandle handle, int wHandle, int weightsGradientHandle,
int mHandle, int vHandle, double l, double b1, double b2, int total)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { float l; float b1; float b2; int total; } consts = {(float)l, (float)b1, (float)b2, total};
return DispatchKernel(ctx, ctx->kLstmUpdateWeightsAdam, {wHandle, weightsGradientHandle, mHandle, vHandle}, &consts, 4,
CeilDiv(total, 64), 1) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_LSTMUpdateWeightsMomentum(DmlHandle handle, int wHandle, int weightsGradientHandle,
int dwHandle, double learningRate, double momentum, int total)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { float learningRate; float momentum; int total; } consts = {(float)learningRate, (float)momentum, total};
return DispatchKernel(ctx, ctx->kLstmUpdateWeightsMomentum, {wHandle, weightsGradientHandle, dwHandle}, &consts, 3,
CeilDiv(total, 64), 1) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_FeedForwardProof(DmlHandle handle, int iHandle, int oHandle, int inputs, int window, int step, int outputs)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int inputs; int window; int step; } consts = {inputs, window, step};
return DispatchKernel(ctx, ctx->kFeedForwardProof, {iHandle, oHandle}, &consts, 3, CeilDiv(outputs, 64), 1) ? 1 : 0;
}
WARRIORDML_API int __stdcall DML_CalcInputGradientProof(DmlHandle handle, int iHandle, int gHandle, int oHandle, int igHandle,
int outputs, int window, int step, int inputs)
{
DmlContext *ctx = reinterpret_cast<DmlContext*>(handle);
if(!ctx)
return 0;
std::lock_guard<std::mutex> lock(ctx->mutex);
struct { int outputs; int window; int step; } consts = {outputs, window, step};
return DispatchKernel(ctx, ctx->kCalcInputGradientProof, {iHandle, gHandle, oHandle, igHandle}, &consts, 3, CeilDiv(inputs, 64), 1) ? 1 : 0;
}