2026-08-01 11:27:28 -04:00
//+------------------------------------------------------------------+
//| NetBuild.mqh |
//| |
//| CNet lifecycle: statics, topology construction from |
//| CLayerDescription, OpenCL/DirectML init, destructor. |
//| |
//| Included from AI\Network.mqh AFTER every class declaration - |
//| bodies only, no declarations. Relocation is behaviour-neutral by |
//| construction: nothing here is reachable until Network.mqh ends. |
//+------------------------------------------------------------------+
# ifndef WARRIOR_AI_IMPL_NETBUILD_MQH
# define WARRIOR_AI_IMPL_NETBUILD_MQH
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double CNet : : recentAverageSmoothingFactor = 10000.0 ; // Number of training samples to average over
bool CNet : : s_openclUnavailable = false ;
bool CNet : : s_computeTierLogged = false ;
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Element width that a conv or pool stage actually slides over. |
//| |
//| Reads the REAL output width of the layer already built below it, |
//| rather than the running position cursor. This matters the moment |
//| two window stages are stacked: a conv's own output is |
//| units_count * window_out (CNeuronConvOCL::Init), but the cursor |
//| tracks sliding POSITIONS only, so it under-reports a conv's width |
//| by a factor of window_out. A pool sized off that cursor would |
//| reduce the wrong number of elements and silently build a layer of |
//| the wrong shape - the failure would look like bad accuracy, not a |
//| crash. Same technique, and the same reason, as the batch-norm |
//| branch in the constructor below. |
//+------------------------------------------------------------------+
int ConvChainInputWidth ( CArrayLayer * builtLayers , CLayerDescription * prev , CLayerDescription * desc , int cursor )
{
if ( CheckPointer ( builtLayers ) ! = POINTER_INVALID & & builtLayers . Total ( ) > 0 )
{
CLayer * below = builtLayers . At ( builtLayers . Total ( ) - 1 ) ;
if ( CheckPointer ( below ) ! = POINTER_INVALID & & below . Total ( ) > 0 )
{
CNeuronBaseOCL * belowNeuron = below . At ( 0 ) ;
if ( CheckPointer ( belowNeuron ) ! = POINTER_INVALID & & belowNeuron . Neurons ( ) > 0 )
return ( int ) belowNeuron . Neurons ( ) ;
}
}
//--- Nothing built yet (a window stage at index 0) - fall back to the description arithmetic.
if ( CheckPointer ( prev ) = = POINTER_INVALID )
return ( CheckPointer ( desc ) = = POINTER_INVALID ? cursor : ( int ) desc . count ) ;
if ( prev . type = = defNeuron | | prev . type = = defNeuronBaseOCL )
return ( int ) prev . count ;
return cursor ;
}
//+------------------------------------------------------------------+
CNet : : CNet ( CArrayObj * Description )
{
//--- Before the early returns below: CNet(NULL) is a legitimate construction (see
//--- InitNeuralNetwork) and must still leave the adjustment cleanly disabled.
bLogitAdjust = false ;
ArrayInitialize ( dLogitAdjust , 0.0 ) ;
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
//--- Same rule for the mini-batch state - a net that returns early here must still read as
//--- "per-sample updates, nothing accumulated" rather than as uninitialized memory.
m_batchSizeRequested = 1 ;
m_batchCount = 0 ;
m_batchKernelsOk = false ;
perf: mini-batch apply becomes a kernel - 8 weight-matrix transfers per batch become 0
Market builds cannot import a DLL, so OpenCL is the tier paying clients run.
It was several times slower than the CPU DLL, and the dominant reason was a
host-side optimizer step I shipped with the mini-batch work in 274630f.
ApplyAccumToBlock read the weights, the accumulator and both Adam moments back
over the bus, stepped them in MQL5, and wrote four buffers out - eight full
weight-matrix transfers per batch PER WEIGHT BLOCK, each a blocking sync. At
TRAIN_BATCH_SIZE 8 that is roughly one entire weight matrix crossing the bus
per training sample. It was host-side for a good reason (one optimizer
implementation shared by all four tiers instead of four that can drift), and
that reason turned out to cost the product's own compute tier.
- ApplyAccumAdam / ApplyAccumMomentum in Network.cl: flat, elementwise, and
they zero the accumulator themselves so there is no separate clear dispatch
and no way to leave it dirty via an early return
- ApplyAccumOnDevice dispatches them; the host step stays as the reference and
as the implementation for DirectML, the CPU DLL and pure-MQL5
- failure latches OFF process-wide with one warning rather than a failed
Execute per batch, since a kernel that did not build will not build later
- m_applyKernelsOk is tracked SEPARATELY from m_batchKernelsOk: without the
accumulation kernels a device cannot batch and must drop to per-sample
updates, whereas without these it batches normally and merely pays the
transfers. Conflating them would turn a missing optimisation into a changed
optimizer
The kernel runs fp32 where the host step ran fp64. That is the OpenCL tier
becoming self-consistent, not a regression: its device buffers are already
fp32 (CBufferDouble::m_data_f) and its unbatched optimizer already ran in
fp32, so the batched path was the odd one out. DLL tiers keep fp64 throughout.
Validated: no OpenCL platform exists on this box, so the kernel source is
syntax/type checked as C against a shim and driven for 4000 steps. It clears
the accumulator, and it is scale-invariant - displacement 1.199336 at |g|=1
versus 1.199333 at |g|=1e-4, matching the figure DirectML/batch_accum_check
produced for the fixed CPU_UpdateWeightsAdam. It reproduces the corrected Adam,
not the pre-371f8aa one.
Both build variants compile 0 errors, 0 warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 17:09:52 -04:00
m_applyKernelsOk = false ;
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
m_batchBegun = false ;
m_batchWarned = false ;
2026-08-01 11:27:28 -04:00
if ( CheckPointer ( Description ) = = POINTER_INVALID )
return ;
//---
int total = Description . Total ( ) ;
if ( total < = 0 )
return ;
//---
layers = new CArrayLayer ( ) ;
if ( CheckPointer ( layers ) = = POINTER_INVALID )
return ;
//---
CLayer * temp ;
CLayerDescription * desc = NULL , * next = NULL , * prev = NULL ;
CNeuronBase * neuron = NULL ;
CNeuronPool * neuron_p = NULL ;
int output_count = 0 ;
int temp_count = 0 ;
//---
next = Description . At ( 1 ) ;
if ( CheckPointer ( next ) ! = POINTER_INVALID & &
( next . type = = defNeuron | | next . type = = defNeuronBaseOCL | | next . type = = defNeuronConv | | next . type = = defNeuronConvOCL | |
next . type = = defNeuronLSTM | | next . type = = defNeuronBatchNorm | | next . type = = defNeuronBatchNormOCL ) )
{
//--- OpenCL first, DirectML/D3D12 next, plain CPU as the final fallback
if ( ! InitOpenCL ( ) )
InitDirectML ( ) ;
}
//--- Batch normalization exists only in the OCL neuron model (AI\NeuronBatchNorm.mqh); the legacy
//--- scalar CNeuron path below has no counterpart. Rather than silently build a DIFFERENT network
//--- than the topology asked for - which is precisely the class of bug that had models training
//--- against a stale output head for a day - refuse, loudly, and leave an empty net behind. Callers
//--- already treat a 0-layer net as a hard failure. Reaching here means OpenCL, DirectML AND the
//--- CPU DLL all failed to initialize, which this project does not support for training anyway.
if ( CheckPointer ( opencl ) = = POINTER_INVALID & & CheckPointer ( directml ) = = POINTER_INVALID )
{
for ( int i = 0 ; i < total ; i + + )
{
CLayerDescription * probe = Description . At ( i ) ;
if ( CheckPointer ( probe ) ! = POINTER_INVALID & &
( probe . type = = defNeuronBatchNorm | | probe . type = = defNeuronBatchNormOCL ) )
{
Print ( __FUNCTION__ + " : REFUSED - topology requests a batch-normalization layer but no compute "
" backend initialized (no OpenCL, no DirectML, no CPU DLL). Batch norm has no scalar-CPU "
" implementation; building without it would silently train a different architecture. " ) ;
return ;
}
}
}
//---
for ( int i = 0 ; i < total ; i + + )
{
prev = desc ;
desc = Description . At ( i ) ;
if ( ( i + 1 ) < total )
{
next = Description . At ( i + 1 ) ;
if ( CheckPointer ( next ) = = POINTER_INVALID )
return ;
}
else
next = NULL ;
//--- How many outgoing weights this layer carries. The convention in this engine is that the
//--- weight matrix feeding layer L is stored ON layer L-1, so only a DENSE successor claims one:
//--- conv/pool/LSTM own their weights internally, and batch norm is elementwise and has none at
//--- all (just gamma/beta, which live in its own parameter block). Getting this wrong for the new
//--- type would allocate a full dense matrix that nothing ever reads or trains.
int outputs = ( next = = NULL | | ( next . type ! = defNeuron & & next . type ! = defNeuronBaseOCL ) ? 0 : next . count ) ;
temp = new CLayer ( outputs ) ;
int neurons = ( desc . count + ( desc . type = = defNeuron | | desc . type = = defNeuronBaseOCL ? 1 : 0 ) ) ;
if ( CheckPointer ( opencl ) ! = POINTER_INVALID | | CheckPointer ( directml ) ! = POINTER_INVALID )
{
CNeuronBaseOCL * neuron_ocl = NULL ;
switch ( desc . type )
{
case defNeuron :
case defNeuronBaseOCL :
neuron_ocl = new CNeuronBaseOCL ( ) ;
if ( CheckPointer ( neuron_ocl ) = = POINTER_INVALID )
{
delete temp ;
return ;
}
if ( CheckPointer ( opencl ) ! = POINTER_INVALID
? ! neuron_ocl .Init ( outputs , 0 , opencl , desc . count , desc . optimization )
: ! neuron_ocl .Init ( outputs , 0 , directml , desc . count , desc . optimization ) )
{
delete temp ;
return ;
}
neuron_ocl . SetActivationFunction ( desc . activation ) ;
if ( ! temp . Add ( neuron_ocl ) )
{
delete neuron_ocl ;
delete temp ;
return ;
}
neuron_ocl = NULL ;
break ;
case defNeuronBatchNorm :
case defNeuronBatchNormOCL :
{
CNeuronBatchNormOCL * neuron_bn = new CNeuronBatchNormOCL ( ) ;
if ( CheckPointer ( neuron_bn ) = = POINTER_INVALID )
{
delete temp ;
return ;
}
//--- Elementwise, so this layer is exactly as wide as the one below it. Take that width
//--- from the layer already built rather than from desc.count: a conv or pool stage's
//--- output size is derived HERE (the sliding-window arithmetic above), so the topology
//--- builder in ExpertSignalAIBase.mqh has no way to know it and cannot state it. Falls
//--- back to desc.count only for the impossible case of a batch-norm layer at index 0.
int bnUnits = desc . count ;
if ( layers . Total ( ) > 0 )
{
CLayer * below = layers . At ( layers . Total ( ) - 1 ) ;
if ( CheckPointer ( below ) ! = POINTER_INVALID & & below . Total ( ) > 0 )
{
CNeuronBaseOCL * belowNeuron = below . At ( 0 ) ;
if ( CheckPointer ( belowNeuron ) ! = POINTER_INVALID & & belowNeuron . Neurons ( ) > 0 )
bnUnits = belowNeuron . Neurons ( ) ;
}
}
bool bnInit = ( CheckPointer ( opencl ) ! = POINTER_INVALID
? neuron_bn .Init ( outputs , 0 , opencl , bnUnits , desc . batch , desc . optimization )
: neuron_bn .Init ( outputs , 0 , directml , bnUnits , desc . batch , desc . optimization ) ) ;
if ( ! bnInit )
{
delete neuron_bn ;
delete temp ;
return ;
}
if ( ! temp . Add ( neuron_bn ) )
{
delete neuron_bn ;
delete temp ;
return ;
}
neuron_bn = NULL ;
//--- Keep the running conv/pool sizing cursor pointing at this layer's real width, so a
//--- conv or pool stage placed ABOVE a batch-norm layer still sizes correctly.
output_count = bnUnits ;
break ;
}
case defNeuronConv :
case defNeuronConvOCL :
{
CNeuronConvOCL * neuron_conv = new CNeuronConvOCL ( ) ;
if ( CheckPointer ( neuron_conv ) = = POINTER_INVALID )
{
delete temp ;
return ;
}
//--- number of sliding positions - same formula the CPU CNeuronConv path uses.
//--- output_count keeps meaning POSITIONS (it is this layer's units_count); the width
//--- being slid over comes from the built layer below - see ConvChainInputWidth.
int convIn = ConvChainInputWidth ( layers , prev , desc , output_count ) ;
temp_count = ( convIn - desc . window ) % desc . step ;
output_count = ( convIn - desc . window - temp_count ) / desc . step + ( temp_count = = 0 ? 1 : 2 ) ;
bool convInit = ( CheckPointer ( opencl ) ! = POINTER_INVALID
? neuron_conv .Init ( outputs , 0 , opencl , desc . window , desc . step , desc . count , output_count , desc . optimization )
: neuron_conv .Init ( outputs , 0 , directml , desc . window , desc . step , desc . count , output_count , desc . optimization ) ) ;
if ( ! convInit )
{
delete neuron_conv ;
delete temp ;
return ;
}
neuron_conv . SetActivationFunction ( desc . activation ) ;
if ( ! temp . Add ( neuron_conv ) )
{
delete neuron_conv ;
delete temp ;
return ;
}
neuron_conv = NULL ;
break ;
}
case defNeuronPool :
case defNeuronPoolOCL :
{
CNeuronPoolOCL * neuron_pool = new CNeuronPoolOCL ( ) ;
if ( CheckPointer ( neuron_pool ) = = POINTER_INVALID )
{
delete temp ;
return ;
}
//--- number of sliding positions - same formula the CPU CNeuronPool path uses.
//--- Critically this must slide over the conv's FULL output (positions * filters), which
//--- is what ConvChainInputWidth returns; the position cursor alone would be window_out
//--- times too small and pool the wrong element count entirely.
int poolIn = ConvChainInputWidth ( layers , prev , desc , output_count ) ;
temp_count = ( poolIn - desc . window ) % desc . step ;
output_count = ( poolIn - desc . window - temp_count ) / desc . step + ( temp_count = = 0 ? 1 : 2 ) ;
bool poolInit = ( CheckPointer ( opencl ) ! = POINTER_INVALID
? neuron_pool .Init ( outputs , 0 , opencl , desc . window , desc . step , output_count , desc . optimization )
: neuron_pool .Init ( outputs , 0 , directml , desc . window , desc . step , output_count , desc . optimization ) ) ;
if ( ! poolInit )
{
delete neuron_pool ;
delete temp ;
return ;
}
if ( ! temp . Add ( neuron_pool ) )
{
delete neuron_pool ;
delete temp ;
return ;
}
neuron_pool = NULL ;
break ;
}
case defNeuronLSTM :
case defNeuronLSTMOCL :
{
CNeuronLSTMOCL * neuron_lstm = new CNeuronLSTMOCL ( ) ;
if ( CheckPointer ( neuron_lstm ) = = POINTER_INVALID )
{
delete temp ;
return ;
}
bool lstmInit = ( CheckPointer ( opencl ) ! = POINTER_INVALID
? neuron_lstm .Init ( outputs , 0 , opencl , desc . count , desc . optimization )
: neuron_lstm .Init ( outputs , 0 , directml , desc . count , desc . optimization ) ) ;
if ( ! lstmInit )
{
delete neuron_lstm ;
delete temp ;
return ;
}
//--- desc.window carries the PER-TIMESTEP input width (see AddLstmStage) - the per-bar
//--- feature count reaching this layer. It used to be dead metadata; it is now what
//--- turns this into a recurrence over bars instead of one giant gated projection.
neuron_lstm . SetStepWidth ( desc . window ) ;
if ( ! temp . Add ( neuron_lstm ) )
{
delete neuron_lstm ;
delete temp ;
return ;
}
neuron_lstm = NULL ;
break ;
}
default :
return ;
break ;
}
}
else
for ( int n = 0 ; n < neurons ; n + + )
{
switch ( desc . type )
{
case defNeuron :
neuron = new CNeuron ( ) ;
if ( CheckPointer ( neuron ) = = POINTER_INVALID )
{
delete temp ;
delete layers ;
return ;
}
//--- He-scaled init, matching CNeuronBaseOCL::Init's rationale - fan-in is this
//--- layer's own neuron count (bias already included via the `neurons` count above).
neuron .Init ( outputs , n , desc . optimization , MathSqrt ( 2.0 / ( double ) neurons ) ) ;
neuron . SetActivationFunction ( desc . activation ) ;
break ;
case defNeuronConv :
neuron_p = new CNeuronConv ( ) ;
if ( CheckPointer ( neuron_p ) = = POINTER_INVALID )
{
delete temp ;
delete layers ;
return ;
}
if ( CheckPointer ( prev ) ! = POINTER_INVALID )
{
if ( prev . type = = defNeuron )
{
temp_count = ( int ) ( ( prev . count - desc . window ) % desc . step ) ;
output_count = ( int ) ( ( prev . count - desc . window - temp_count ) / desc . step + ( temp_count = = 0 ? 1 : 2 ) ) ;
}
else
if ( n = = 0 )
{
temp_count = ( int ) ( ( output_count - desc . window ) % desc . step ) ;
output_count = ( int ) ( ( output_count - desc . window - temp_count ) / desc . step + ( temp_count = = 0 ? 1 : 2 ) ) ;
}
}
if ( neuron_p .Init ( outputs , n , desc . window , desc . step , output_count , desc . optimization ) )
neuron = neuron_p ;
break ;
case defNeuronPool :
neuron_p = new CNeuronPool ( ) ;
if ( CheckPointer ( neuron_p ) = = POINTER_INVALID )
{
delete temp ;
delete layers ;
return ;
}
if ( CheckPointer ( prev ) ! = POINTER_INVALID )
{
if ( prev . type = = defNeuron )
{
temp_count = ( int ) ( ( prev . count - desc . window ) % desc . step ) ;
output_count = ( int ) ( ( prev . count - desc . window - temp_count ) / desc . step + ( temp_count = = 0 ? 1 : 2 ) ) ;
}
else
if ( n = = 0 )
{
temp_count = ( int ) ( ( output_count - desc . window ) % desc . step ) ;
output_count = ( int ) ( ( output_count - desc . window - temp_count ) / desc . step + ( temp_count = = 0 ? 1 : 2 ) ) ;
}
}
if ( neuron_p .Init ( outputs , n , desc . window , desc . step , output_count , desc . optimization ) )
neuron = neuron_p ;
break ;
case defNeuronLSTM :
neuron_p = new CNeuronLSTM ( ) ;
if ( CheckPointer ( neuron_p ) = = POINTER_INVALID )
{
delete temp ;
delete layers ;
return ;
}
output_count = ( next ! = NULL ? next . window : desc . step ) ;
if ( neuron_p .Init ( outputs , n , desc . window , 1 , output_count , desc . optimization ) )
neuron = neuron_p ;
break ;
}
if ( ! temp . Add ( neuron ) )
{
delete temp ;
delete layers ;
return ;
}
neuron = NULL ;
}
if ( ! layers . Add ( temp ) )
{
delete temp ;
delete layers ;
return ;
}
}
//---
}
//+------------------------------------------------------------------+
//| Tries to initialize OpenCL; on any failure (no GPU, driver |
//| missing, kernel build error) frees it and leaves opencl==NULL |
//| so the rest of CNet transparently runs its CPU code path. |
//+------------------------------------------------------------------+
bool CNet : : InitOpenCL ( void )
{
//--- pure-MQL5 inference: deliberately refuse a backend so the OCL neurons compute host-side.
if ( m_cpuInference )
return false ;
if ( CheckPointer ( opencl ) ! = POINTER_INVALID )
return true ;
//--- already established (by an earlier CNet in this process) that this host has no OpenCL - skip the
//--- probe rather than re-run a known failure and reprint the stdlib's banner. See s_openclUnavailable.
if ( s_openclUnavailable )
return false ;
//---
opencl = new COpenCLMy ( ) ;
if ( CheckPointer ( opencl ) = = POINTER_INVALID | | ! opencl . Initialize ( cl_program , true ) )
{
if ( CheckPointer ( opencl ) ! = POINTER_INVALID )
delete opencl ;
opencl = NULL ;
s_openclUnavailable = true ;
PrintFormat ( " %s: OpenCL unavailable, falling back to CPU " , __FUNCTION__ ) ;
return false ;
}
//--- create kernels
fix: live trades now use the geometry the gate certifies; perf: BN kernels
Three changes, one theme: the trade placed, the trade graded, and the trade
computed are now the same trade.
1) GEOMETRY WIRE (correctness, the ranked #1 open issue). The measured barrier
pair reached the LABELS only - OpenParams still placed orders at the enum
geometry (2*ATR/6*ATR), so the deploy gate certified "reaches 1.62*ATR before
3.33*ATR above break-even" about trades the EA never placed. Published via
g_DerivedSlAtrMult/g_DerivedTpAtrMult (ConfidenceBridge, same same-tick
contract as the confidence globals, because OpenParams runs on the root signal
which has no pointer to the AI filter). Two writers: DeriveBarrierGeometry at
era 0, and the .cfg adoption a deployed model takes. Overrides both legs and
both Intelligent modes - the certificate is exact or it is nothing. TP is
ATR-anchored like the label, NOT risk-relative, so a floor-widened stop cannot
reshape the certified target.
2) BATCH NORM RUNS DEVICE-SIDE ON OPENCL. Four kernels in Network.cl -
forward, hidden gradient, gamma/beta accumulate, gamma/beta apply - each a
line-for-line transcription of the host implementation (NormalizeHost /
HiddenGradHost / StepGammaBeta) including every NaN guard, clamp, and the
exact moment-write ordering. The host copies remain the runtime for the DLL
and pure-MQL5 tiers and the reference the kernels must match.
Because this box has no OpenCL platform, the safety story is layered:
- shim validation: kernels compiled as C and driven against a fp64 host
transcription over NaN-poisoned stats, NaN gamma, over-clamp inputs, the
frozen path, both optimizers, 3 batches - ALL PASS, worst normalized diff
0.132 vs tolerance 1.0
- in-situ self-check: each kernel is compared against its host twin ON FIRST
USE on the real device (SelfCheckBn*), covering what the shim cannot - arg
indices and buffer bindings. Any disagreement resyncs from the good copy,
latches all BN kernels off process-wide, and training continues host-side.
A transcription bug costs a warning and some speed, never a poisoned .nnw.
- sync discipline: BatchOptions is now a CBufferDouble with explicit
authority tracking (m_bnDeviceAuthoritative). Checkpoints/saves pull
read-only; restores/loads/resets push; a mid-batch handover drains the
device gamma/beta accumulator into the host arrays so no sample is lost.
3) SMALL FIXES. Apply-kernel build failure now latches the dispatch path at
init (one warning instead of warning + failed Execute). Build tag bumped to
win-scoring-gpu-v1 - first tag change since expectancy-stop-v1 despite five
binary-changing commits.
Both build variants compile 0 errors, 0 warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 17:51:40 -04:00
opencl . SetKernelsCount ( 31 ) ;
2026-08-01 11:27:28 -04:00
opencl . KernelCreate ( def_k_FeedForward , " FeedForward " ) ;
opencl . KernelCreate ( def_k_CaclOutputGradient , " CaclOutputGradient " ) ;
opencl . KernelCreate ( def_k_CaclHiddenGradient , " CaclHiddenGradient " ) ;
opencl . KernelCreate ( def_k_UpdateWeightsMomentum , " UpdateWeightsMomentum " ) ;
opencl . KernelCreate ( def_k_UpdateWeightsAdam , " UpdateWeightsAdam " ) ;
opencl . KernelCreate ( def_k_FeedForwardConv , " FeedForwardConv " ) ;
opencl . KernelCreate ( def_k_CalcHiddenGradientConv , " CalcHiddenGradientConv " ) ;
opencl . KernelCreate ( def_k_UpdateWeightsConvMomentum , " UpdateWeightsConvMomentum " ) ;
opencl . KernelCreate ( def_k_UpdateWeightsConvAdam , " UpdateWeightsConvAdam " ) ;
opencl . KernelCreate ( def_k_LSTM_Gates , " LSTM_Gates " ) ;
opencl . KernelCreate ( def_k_LSTM_State , " LSTM_State " ) ;
opencl . KernelCreate ( def_k_LSTM_GateGradient , " LSTM_GateGradient " ) ;
opencl . KernelCreate ( def_k_LSTM_WeightsGradient , " LSTM_WeightsGradient " ) ;
opencl . KernelCreate ( def_k_LSTM_InputsGradient , " LSTM_InputsGradient " ) ;
opencl . KernelCreate ( def_k_LSTM_UpdateWeightsAdam , " LSTM_UpdateWeightsAdam " ) ;
opencl . KernelCreate ( def_k_LSTM_UpdateWeightsMomentum , " LSTM_UpdateWeightsMomentum " ) ;
opencl . KernelCreate ( def_k_FeedForwardProof , " FeedForwardProof " ) ;
opencl . KernelCreate ( def_k_CalcInputGradientProof , " CalcInputGradientProof " ) ;
//--- Return values CHECKED here, unlike the calls above. A kernel that fails to build otherwise
//--- surfaces only as an Execute() failure deep inside training on a customer's machine, and these
//--- four are the newest and least-exercised code in the program. Reported, not fatal: a device
//--- without them can still run every non-recurrent topology.
bool seqOk = opencl . KernelCreate ( def_k_LSTM_SeqStepForward , " LSTM_SeqStepForward " ) ;
seqOk = opencl . KernelCreate ( def_k_LSTM_SeqStepGateGrad , " LSTM_SeqStepGateGrad " ) & & seqOk ;
seqOk = opencl . KernelCreate ( def_k_LSTM_SeqStepWeightGrad , " LSTM_SeqStepWeightGrad " ) & & seqOk ;
seqOk = opencl . KernelCreate ( def_k_LSTM_SeqStepInputGrad , " LSTM_SeqStepInputGrad " ) & & seqOk ;
if ( ! seqOk )
Print ( " CNet::InitOpenCL: WARNING - the sequence-LSTM kernels failed to build on this OpenCL device. LSTM and HYBRID will not train here; MLP and CONV are unaffected. Run those topologies on the CPU/DirectML tier instead. " ) ;
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
//--- Mini-batch accumulation (see AccumulateWeightGrad in Network.cl). Checked and reported for the
//--- same reason as the sequence kernels, and non-fatal in the same way: CNet::BatchSize() falls the
//--- whole net back to per-sample updates when these are missing, so an old device trains exactly as
//--- it did before rather than not at all.
m_batchKernelsOk = opencl . KernelCreate ( def_k_AccumulateWeightGrad , " AccumulateWeightGrad " ) ;
m_batchKernelsOk = opencl . KernelCreate ( def_k_AccumulateWeightGradConv , " AccumulateWeightGradConv " ) & & m_batchKernelsOk ;
m_batchKernelsOk = opencl . KernelCreate ( def_k_AccumulateBufferInto , " AccumulateBufferInto " ) & & m_batchKernelsOk ;
if ( ! m_batchKernelsOk )
Print ( " CNet::InitOpenCL: WARNING - the mini-batch accumulation kernels failed to build on this OpenCL device. Training falls back to one weight update per sample (the pre-2026-08-09 behaviour), which is noisier but correct. " ) ;
perf: mini-batch apply becomes a kernel - 8 weight-matrix transfers per batch become 0
Market builds cannot import a DLL, so OpenCL is the tier paying clients run.
It was several times slower than the CPU DLL, and the dominant reason was a
host-side optimizer step I shipped with the mini-batch work in 274630f.
ApplyAccumToBlock read the weights, the accumulator and both Adam moments back
over the bus, stepped them in MQL5, and wrote four buffers out - eight full
weight-matrix transfers per batch PER WEIGHT BLOCK, each a blocking sync. At
TRAIN_BATCH_SIZE 8 that is roughly one entire weight matrix crossing the bus
per training sample. It was host-side for a good reason (one optimizer
implementation shared by all four tiers instead of four that can drift), and
that reason turned out to cost the product's own compute tier.
- ApplyAccumAdam / ApplyAccumMomentum in Network.cl: flat, elementwise, and
they zero the accumulator themselves so there is no separate clear dispatch
and no way to leave it dirty via an early return
- ApplyAccumOnDevice dispatches them; the host step stays as the reference and
as the implementation for DirectML, the CPU DLL and pure-MQL5
- failure latches OFF process-wide with one warning rather than a failed
Execute per batch, since a kernel that did not build will not build later
- m_applyKernelsOk is tracked SEPARATELY from m_batchKernelsOk: without the
accumulation kernels a device cannot batch and must drop to per-sample
updates, whereas without these it batches normally and merely pays the
transfers. Conflating them would turn a missing optimisation into a changed
optimizer
The kernel runs fp32 where the host step ran fp64. That is the OpenCL tier
becoming self-consistent, not a regression: its device buffers are already
fp32 (CBufferDouble::m_data_f) and its unbatched optimizer already ran in
fp32, so the batched path was the odd one out. DLL tiers keep fp64 throughout.
Validated: no OpenCL platform exists on this box, so the kernel source is
syntax/type checked as C against a shim and driven for 4000 steps. It clears
the accumulator, and it is scale-invariant - displacement 1.199336 at |g|=1
versus 1.199333 at |g|=1e-4, matching the figure DirectML/batch_accum_check
produced for the fixed CPU_UpdateWeightsAdam. It reproduces the corrected Adam,
not the pre-371f8aa one.
Both build variants compile 0 errors, 0 warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 17:09:52 -04:00
//--- Device-side mini-batch APPLY. Tracked SEPARATELY from m_batchKernelsOk and degraded separately:
//--- these are a pure speed optimisation over a host-side step that still works and still produces
//--- the same answer, so a device that cannot build them must keep BATCHING (correctness, and the
//--- LR/patience compensation is tuned for it) and merely pay the transfers. Folding them into
//--- m_batchKernelsOk would silently drop such a device to batch size 1, changing the optimizer
//--- rather than the speed.
m_applyKernelsOk = opencl . KernelCreate ( def_k_ApplyAccumAdam , " ApplyAccumAdam " ) ;
m_applyKernelsOk = opencl . KernelCreate ( def_k_ApplyAccumMomentum , " ApplyAccumMomentum " ) & & m_applyKernelsOk ;
if ( ! m_applyKernelsOk )
fix: live trades now use the geometry the gate certifies; perf: BN kernels
Three changes, one theme: the trade placed, the trade graded, and the trade
computed are now the same trade.
1) GEOMETRY WIRE (correctness, the ranked #1 open issue). The measured barrier
pair reached the LABELS only - OpenParams still placed orders at the enum
geometry (2*ATR/6*ATR), so the deploy gate certified "reaches 1.62*ATR before
3.33*ATR above break-even" about trades the EA never placed. Published via
g_DerivedSlAtrMult/g_DerivedTpAtrMult (ConfidenceBridge, same same-tick
contract as the confidence globals, because OpenParams runs on the root signal
which has no pointer to the AI filter). Two writers: DeriveBarrierGeometry at
era 0, and the .cfg adoption a deployed model takes. Overrides both legs and
both Intelligent modes - the certificate is exact or it is nothing. TP is
ATR-anchored like the label, NOT risk-relative, so a floor-widened stop cannot
reshape the certified target.
2) BATCH NORM RUNS DEVICE-SIDE ON OPENCL. Four kernels in Network.cl -
forward, hidden gradient, gamma/beta accumulate, gamma/beta apply - each a
line-for-line transcription of the host implementation (NormalizeHost /
HiddenGradHost / StepGammaBeta) including every NaN guard, clamp, and the
exact moment-write ordering. The host copies remain the runtime for the DLL
and pure-MQL5 tiers and the reference the kernels must match.
Because this box has no OpenCL platform, the safety story is layered:
- shim validation: kernels compiled as C and driven against a fp64 host
transcription over NaN-poisoned stats, NaN gamma, over-clamp inputs, the
frozen path, both optimizers, 3 batches - ALL PASS, worst normalized diff
0.132 vs tolerance 1.0
- in-situ self-check: each kernel is compared against its host twin ON FIRST
USE on the real device (SelfCheckBn*), covering what the shim cannot - arg
indices and buffer bindings. Any disagreement resyncs from the good copy,
latches all BN kernels off process-wide, and training continues host-side.
A transcription bug costs a warning and some speed, never a poisoned .nnw.
- sync discipline: BatchOptions is now a CBufferDouble with explicit
authority tracking (m_bnDeviceAuthoritative). Checkpoints/saves pull
read-only; restores/loads/resets push; a mid-batch handover drains the
device gamma/beta accumulator into the host arrays so no sample is lost.
3) SMALL FIXES. Apply-kernel build failure now latches the dispatch path at
init (one warning instead of warning + failed Execute). Build tag bumped to
win-scoring-gpu-v1 - first tag change since expectancy-stop-v1 despite five
binary-changing commits.
Both build variants compile 0 errors, 0 warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 17:51:40 -04:00
{
//--- Latch the dispatch-side fast path off HERE as well, so a device that could not build the
//--- kernels gets exactly one warning at init instead of also paying (and logging) one failed
//--- Execute on the first batch before the runtime latch caught it.
g_applyAccumKernelUsable = false ;
perf: mini-batch apply becomes a kernel - 8 weight-matrix transfers per batch become 0
Market builds cannot import a DLL, so OpenCL is the tier paying clients run.
It was several times slower than the CPU DLL, and the dominant reason was a
host-side optimizer step I shipped with the mini-batch work in 274630f.
ApplyAccumToBlock read the weights, the accumulator and both Adam moments back
over the bus, stepped them in MQL5, and wrote four buffers out - eight full
weight-matrix transfers per batch PER WEIGHT BLOCK, each a blocking sync. At
TRAIN_BATCH_SIZE 8 that is roughly one entire weight matrix crossing the bus
per training sample. It was host-side for a good reason (one optimizer
implementation shared by all four tiers instead of four that can drift), and
that reason turned out to cost the product's own compute tier.
- ApplyAccumAdam / ApplyAccumMomentum in Network.cl: flat, elementwise, and
they zero the accumulator themselves so there is no separate clear dispatch
and no way to leave it dirty via an early return
- ApplyAccumOnDevice dispatches them; the host step stays as the reference and
as the implementation for DirectML, the CPU DLL and pure-MQL5
- failure latches OFF process-wide with one warning rather than a failed
Execute per batch, since a kernel that did not build will not build later
- m_applyKernelsOk is tracked SEPARATELY from m_batchKernelsOk: without the
accumulation kernels a device cannot batch and must drop to per-sample
updates, whereas without these it batches normally and merely pays the
transfers. Conflating them would turn a missing optimisation into a changed
optimizer
The kernel runs fp32 where the host step ran fp64. That is the OpenCL tier
becoming self-consistent, not a regression: its device buffers are already
fp32 (CBufferDouble::m_data_f) and its unbatched optimizer already ran in
fp32, so the batched path was the odd one out. DLL tiers keep fp64 throughout.
Validated: no OpenCL platform exists on this box, so the kernel source is
syntax/type checked as C against a shim and driven for 4000 steps. It clears
the accumulator, and it is scale-invariant - displacement 1.199336 at |g|=1
versus 1.199333 at |g|=1e-4, matching the figure DirectML/batch_accum_check
produced for the fixed CPU_UpdateWeightsAdam. It reproduces the corrected Adam,
not the pre-371f8aa one.
Both build variants compile 0 errors, 0 warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 17:09:52 -04:00
Print ( " CNet::InitOpenCL: WARNING - the device-side mini-batch apply kernels failed to build on this OpenCL device. Training still batches and is still correct, but each batch reads the weights back to the host and writes them out again - expect it to be several times slower. " ) ;
fix: live trades now use the geometry the gate certifies; perf: BN kernels
Three changes, one theme: the trade placed, the trade graded, and the trade
computed are now the same trade.
1) GEOMETRY WIRE (correctness, the ranked #1 open issue). The measured barrier
pair reached the LABELS only - OpenParams still placed orders at the enum
geometry (2*ATR/6*ATR), so the deploy gate certified "reaches 1.62*ATR before
3.33*ATR above break-even" about trades the EA never placed. Published via
g_DerivedSlAtrMult/g_DerivedTpAtrMult (ConfidenceBridge, same same-tick
contract as the confidence globals, because OpenParams runs on the root signal
which has no pointer to the AI filter). Two writers: DeriveBarrierGeometry at
era 0, and the .cfg adoption a deployed model takes. Overrides both legs and
both Intelligent modes - the certificate is exact or it is nothing. TP is
ATR-anchored like the label, NOT risk-relative, so a floor-widened stop cannot
reshape the certified target.
2) BATCH NORM RUNS DEVICE-SIDE ON OPENCL. Four kernels in Network.cl -
forward, hidden gradient, gamma/beta accumulate, gamma/beta apply - each a
line-for-line transcription of the host implementation (NormalizeHost /
HiddenGradHost / StepGammaBeta) including every NaN guard, clamp, and the
exact moment-write ordering. The host copies remain the runtime for the DLL
and pure-MQL5 tiers and the reference the kernels must match.
Because this box has no OpenCL platform, the safety story is layered:
- shim validation: kernels compiled as C and driven against a fp64 host
transcription over NaN-poisoned stats, NaN gamma, over-clamp inputs, the
frozen path, both optimizers, 3 batches - ALL PASS, worst normalized diff
0.132 vs tolerance 1.0
- in-situ self-check: each kernel is compared against its host twin ON FIRST
USE on the real device (SelfCheckBn*), covering what the shim cannot - arg
indices and buffer bindings. Any disagreement resyncs from the good copy,
latches all BN kernels off process-wide, and training continues host-side.
A transcription bug costs a warning and some speed, never a poisoned .nnw.
- sync discipline: BatchOptions is now a CBufferDouble with explicit
authority tracking (m_bnDeviceAuthoritative). Checkpoints/saves pull
read-only; restores/loads/resets push; a mid-batch handover drains the
device gamma/beta accumulator into the host arrays so no sample is lost.
3) SMALL FIXES. Apply-kernel build failure now latches the dispatch path at
init (one warning instead of warning + failed Execute). Build tag bumped to
win-scoring-gpu-v1 - first tag change since expectancy-stop-v1 despite five
binary-changing commits.
Both build variants compile 0 errors, 0 warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 17:51:40 -04:00
}
//--- Batch-norm kernels (2026-08-09). Same speed-only degradation contract as the apply kernels
//--- above: without them batch norm computes host-side exactly as it always did, paying its
//--- transfers, and nothing about the numbers changes.
bool bnOk = opencl . KernelCreate ( def_k_BatchNormForward , " BatchNormForward " ) ;
bnOk = opencl . KernelCreate ( def_k_BatchNormHiddenGrad , " BatchNormHiddenGrad " ) & & bnOk ;
bnOk = opencl . KernelCreate ( def_k_BatchNormAccumGammaBeta , " BatchNormAccumGammaBeta " ) & & bnOk ;
bnOk = opencl . KernelCreate ( def_k_BatchNormApplyGammaBeta , " BatchNormApplyGammaBeta " ) & & bnOk ;
if ( ! bnOk )
{
g_bnKernelUsable = false ;
Print ( " CNet::InitOpenCL: WARNING - the batch-norm kernels failed to build on this OpenCL device. Batch norm runs host-side (the pre-2026-08-09 behaviour): correct, but every batch-norm layer pays a device round-trip per sample. " ) ;
}
2026-08-01 11:27:28 -04:00
return true ;
}
//+------------------------------------------------------------------+
//| Second-tier GPU fallback: only tried when OpenCL init failed. |
//| Requires DirectML\WarriorDML.dll in the terminal's Libraries |
//| folder (build it with DirectML\build.bat); on any failure frees |
//| itself and leaves directml==NULL so CNet falls through to CPU. |
//+------------------------------------------------------------------+
bool CNet : : InitDirectML ( void )
{
//--- pure-MQL5 inference: deliberately refuse a backend so the OCL neurons compute host-side.
if ( m_cpuInference )
return false ;
//--- Tester/optimization/forward runs must not touch WarriorDML/WarriorCPU DLL imports.
//--- This avoids agent-side file-lock/synchronization failures on rapid stop/restart cycles.
if ( MQLInfoInteger ( MQL_TESTER ) | | MQLInfoInteger ( MQL_OPTIMIZATION ) | | MQLInfoInteger ( MQL_FORWARD ) )
return false ;
if ( CheckPointer ( directml ) ! = POINTER_INVALID )
return true ;
//---
directml = new CDirectMLMy ( ) ;
if ( CheckPointer ( directml ) = = POINTER_INVALID )
return false ;
directml . SetCpuLoadPercent ( EffectiveCpuLoadPercent ( ) ) ;
if ( ! directml . Initialize ( ) )
{
int err = directml . LastError ( ) ;
delete directml ;
directml = NULL ;
string reason ;
switch ( err )
{
case 1 : reason = " CreateDXGIFactory1 failed " ; break ;
case 2 : reason = " no DX12 hardware adapter found (feature level 11_0) " ; break ;
case 3 : reason = " compute command queue creation failed " ; break ;
case 4 : reason = " command allocator creation failed " ; break ;
case 5 : reason = " command list creation failed " ; break ;
case 6 : reason = " fence creation failed " ; break ;
case 7 : reason = " fence event creation failed " ; break ;
case 8 : reason = " HLSL kernel compile/PSO creation failed " ; break ;
default : reason = " neither WarriorDML.dll nor WarriorCPU.dll loaded (check Libraries folder / \" Allow DLL imports \" ) " ;
}
PrintFormat ( " %s: DirectML/D3D12 and CPU DLL both unavailable (%s), falling back to slow per-object CPU path " , __FUNCTION__ , reason ) ;
return false ;
}
//--- Announce the tier ONCE per process: it describes the host, not this particular net, and a run builds
//--- several nets (main + EMA shadow + sim/self-check clones). See s_computeTierLogged.
if ( ! s_computeTierLogged )
{
s_computeTierLogged = true ;
if ( directml . Tier ( ) = = COMPUTE_TIER_CPU )
{
//--- Report the SPLIT, not just the result. When several charts train at once this is the
//--- number that explains their speed, and it is the one that was silently wrong before.
int share = EffectiveCpuLoadPercent ( ) ;
PrintFormat ( " %s: DirectML/D3D12 unavailable, using multithreaded CPU DLL fallback (%d threads per network, target %d; %d%% of %d detected cores - fixed, independent of how many charts run) " ,
__FUNCTION__ , directml . CpuThreadsUsed ( ) , CPU_THREADS_PER_NETWORK , share , ( int ) TerminalInfoInteger ( TERMINAL_CPU_CORES ) ) ;
}
else
PrintFormat ( " %s: DirectML/D3D12 GPU tier active " , __FUNCTION__ ) ;
}
return true ;
}
CNet : : ~ CNet ( void )
{
if ( CheckPointer ( layers ) ! = POINTER_INVALID )
delete layers ;
if ( CheckPointer ( m_weightSnapshot ) ! = POINTER_INVALID )
delete m_weightSnapshot ; // CArrayObj (FreeMode) deletes its per-neuron CArrayDouble elements
if ( CheckPointer ( m_prevLayerWeights ) ! = POINTER_INVALID )
delete m_prevLayerWeights ; // same - FreeMode owns the per-layer CArrayDouble elements
if ( CheckPointer ( opencl ) ! = POINTER_INVALID )
{
opencl . Shutdown ( ) ;
delete opencl ;
}
if ( CheckPointer ( directml ) ! = POINTER_INVALID )
delete directml ;
}
# endif