Validation
September 21, 2026 · View on GitHub
Keywords: model validation monte carlo, SR 11-7 reproducibility, deterministic monte carlo java, independent implementation check
Every NablaTensor backend replays the same recorded tape. The scalar cpu
engine is the reference: it walks the tape node by node in plain Java and shares
the Philox random stream with every other backend path-for-path. So an
accelerated result can be checked against it at an equal seed with no
statistical allowance — any difference is arithmetic reordering, not noise.
The com.nablatensor.validate harness (in nablatensor-examples) turns that into one call:
Report report = ModelValidation.of(Products.asianCall())
.market(EquityMarket.atmOneYear()).steps(252)
.scenarios(2_000_000).seed(42L)
.fp64().tolerance(1e-6)
.run();
System.out.println(report); // the evidence pack below
assert report.passed();
It does two things:
- BackendEnum reproduction. Replays on every engine this machine can run and
diffs price and each gradient against the oracle (relative, divided by
1 + |oracle|). - Adjoint cross-check. Central bump-and-revalue on the oracle, run with common random numbers, so the difference against the adjoint gradient is the bump's own discretisation error.
Reproduce
mvn -o -q install
MAVEN_OPTS="--add-modules jdk.incubator.vector" mvn -o -q -pl nablatensor-examples exec:java \
-Dexec.mainClass=com.nablatensor.validate.EvidenceMain -Dscenarios=2000000 -Dsteps=252
Evidence pack
NablaTensor — model-validation evidence pack
============================================
product : Asian CALL
market : S0=100.0000 K=100.0000 sigma=0.2000 r=0.0300 T=1.0000
discretisation : 252 steps
scenarios : 1,000,000
seed : 0x000000000000002A
precision : fp64
tolerance : 1.00e-06 (relative)
machine : JDK 25.0.1+8-LTS · Linux amd64 · 16 processors
scalar CPU oracle (reference)
-----------------------------
price +5.3058639800e+00 (1,000,000 scenarios in 1.145 s)
delta +5.6198939096e-01
vega +2.2412432861e+01
rho +2.3603384920e+01
dV/dK -5.0893075116e-01
dV/dT +2.9493448337e+00
backend reproduction vs oracle (equal seed, equal scenarios)
-----------------------------------------------------------
engine result price relΔ grad relΔ detail
rocm PASS 7.042e-16 3.804e-14 rocm
simd PASS 7.042e-15 4.150e-14 simd
cpu-jit PASS 0.000e+00 0.000e+00 cpu-jit
adjoint gradient vs central bump-and-revalue on the oracle
---------------------------------------------------------
bump size : 5.00e-03 (relative, common random numbers)
greek adjoint bump absΔ
delta +5.61989391e-01 +5.61922391e-01 6.70e-05
dV/dK -5.08930751e-01 -5.08905459e-01 2.53e-05
vega +2.24124329e+01 +2.24122737e+01 1.59e-04
rho +2.36033849e+01 +2.36010273e+01 2.36e-03
dV/dT +2.94934483e+00 +2.94935170e+00 6.87e-06
RESULT: PASS — every backend reproduces the oracle within tolerance.
cpu-jit matches the oracle bit-for-bit; rocm (a HIPRTC-compiled GPU kernel)
and simd differ only by reduction/rounding order. vulkan and
cuda are fp32-only — pass .fp32() to ModelValidation to include them; on
this box vulkan reproduces the oracle's price and delta to five decimal places
(see the Asian backend matrix). cuda needs an
NVIDIA device, absent here, so it is skipped rather than failed — but a separate
Colab Tesla T4 run (notebooks/engine-benchmark.ipynb)
reproduces the fp64 oracle's price and delta to ~1e-5 at fp32, the same order
as vulkan.
The adjoint gradient agrees with the bump to the bump's own O(h²) error —
rho is the loosest because the payoff's rate dependence is the most nonlinear
over a 0.5 % shock.
This is generated automatically from a run and is the shape a model-validation function (SR 11-7 / TRIM / SS1/23) expects: reproducible inputs, an independent implementation, seed-for-seed agreement. Widening it into a full submission pack is Phase 2.