Files
chains/evm/cevm/TESTING_CUDA.md
zeekayandHanzo Dev acd68484d7 The cevm binding reads ABI 7, and declines a library of another
ABIVersion is 7, the go_bridge.h ABI in which ok says whether the result is
the block's. The binding does not compile against a header that names
another, and init reads the loaded library's gpu_abi_version() instead of
panicking on it: against a library of another ABI, ExecuteBlock calls
nothing and declines every block, and a result of another ABI is declined
too, so the caller runs them on its own EVM.

The native tests hold the library to that contract. A block of funded plain
transfers runs on a GPU lane to TxOK at 21000 gas each, or is declined there
when the host has no device, and a CPU lane declines it; a batch with code
or calldata is declined on every lane. The opcode, bytecode and gas-parity
tests ran code through the entry, which runs none, and are gone; the
block-context and sustained-Metal tests run transfers. A test that needs a
device skips on a GPU lane that declines. The tests that pin the build with
no library linked moved to cevm_nolib_test.go, outside the native build,
where they failed.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 11:27:54 -07:00

7.3 KiB

CUDA box validation checklist

What this PR set ships and what to verify on a Linux + NVIDIA host. Everything below is locally validated on darwin/arm64; only the CUDA-specific bullets require the NVIDIA host.

0. Prereqs on the CUDA box

  • Ubuntu 22.04+ or RHEL 9+
  • NVIDIA driver ≥ 535 (nvidia-smi works)
  • CUDA Toolkit ≥ 12.0 (nvcc --version)
  • Go ≥ 1.26.3
  • CMake ≥ 3.20
  • pkg-config
  • ~/work/lux and ~/work/lx and ~/work/luxcpp checked out at the same refs you build from

Set:

export LUXCPP_PREFIX=$HOME/work/luxcpp/install
export PKG_CONFIG_PATH=$LUXCPP_PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH
export LD_LIBRARY_PATH=$LUXCPP_PREFIX/lib:$LD_LIBRARY_PATH

1. Build the new luxcpp CUDA targets

cd ~/work/luxcpp/dex
cmake -S . -B build \
  -DCMAKE_INSTALL_PREFIX=$LUXCPP_PREFIX \
  -DCMAKE_BUILD_TYPE=Release \
  -DBUILD_TESTS=OFF
cmake --build build --target amm_xyk_cuda dex_clob_cuda -j
cmake --install build

Expected output:

  • $LUXCPP_PREFIX/lib/libamm_xyk_cuda.a
  • $LUXCPP_PREFIX/lib/libdex_clob_cuda.a
  • $LUXCPP_PREFIX/lib/pkgconfig/lux-dex-amm-cuda.pc
  • $LUXCPP_PREFIX/lib/pkgconfig/lux-dex-clob-cuda.pc
  • $LUXCPP_PREFIX/include/lux/dex/cuda/amm_xyk_driver.h
  • $LUXCPP_PREFIX/include/lux/dex/cuda/dex_clob_host.h
  • $LUXCPP_PREFIX/include/lux/cuda/dex_swap.h

Sanity:

pkg-config --cflags --libs lux-dex-amm-cuda
pkg-config --cflags --libs lux-dex-clob-cuda

Both should print -I<...>/include/lux/dex/cuda -L<...>/lib -lamm_xyk_cuda and -I<...>/include -L<...>/lib -ldex_clob_cuda respectively.

2. ABI guards (catch struct drift at compile time)

The new C headers carry DEX_ABI_ASSERT (C11 _Static_assert) for every struct size + field offset. If a refactor breaks the ABI, the build fails here, not at runtime.

# Triggers asserts; should compile clean.
cc -std=c11 -c -o /tmp/dexabi.o -x c \
  -I$LUXCPP_PREFIX/include \
  -I$LUXCPP_PREFIX/include/lux/dex/cuda \
  - <<'EOF'
#include <lux/cuda/dex_swap.h>
#include "amm_xyk_driver.h"
#include "dex_clob_host.h"
int main(void) { return 0; }
EOF

3. Go-side build (CUDA + ABI mirror asserts)

cd ~/work/lx/dex
go build ./pkg/lx/
go vet ./pkg/lx/

init() in pkg/lx/orderbook_cuda_types.go mirrors the C-side _Static_asserts in Go. Any size or offset drift panics at startup.

4. CLOB CUDA parity (the headline test)

cd ~/work/lx/dex
go test -count=1 -v -run TestMatchOrder_GPUMatchesCPU ./pkg/lx/

Expected:

  • 8/8 subtests PASS
  • MatchOrderGPU actually dispatches to CUDA (no fallback log)
  • Trade list + remaining quantity byte-equal to MatchOrderCPU field-by-field
  • Book state after match identical on both paths

If parity fails: the kernel produced different output for the same input — real ABI or numeric bug. Save the failing seed (t.Run("", ...) index) and dump incoming, book, indices.

5. AMM CUDA parity

cd ~/work/lx/dex
go test -count=1 -v -run TestBatchEvalConstantProduct ./pkg/lx/

Expected:

  • All AMM batch evals byte-equal to the CPU oracle (mulDiv64)
  • Length-mismatch + degenerate-pool edge cases handled

6. Backend probe + fallback observability

cd ~/work/lx/dex
go test -count=1 -v -run TestProbeSurfacesDisabledAndFallbacks \
  github.com/luxfi/crypto/backend

Print the probe live:

cat > /tmp/probe.go <<'EOF'
package main

import (
    "fmt"
    "github.com/luxfi/crypto/backend"
)

func main() {
    fmt.Println(backend.Probe())
}
EOF
go run /tmp/probe.go

Expected (NVIDIA box, CUDA available):

backend{default=auto resolved=gpu cgo=true gpu=true gpu_backend=cuda devices=N accel=<ver>}

7. GPU_DISABLE kill switch

cd ~/work/lx/dex

# Default: CUDA path
go run /tmp/probe.go

# Forced CPU
GPU_DISABLE=1 go run /tmp/probe.go

Second run should report gpu=false disabled=true. Re-run the parity test under the kill switch and it must still pass (CPU oracle on both paths).

GPU_DISABLE=1 go test -count=1 -v -run TestMatchOrder_GPUMatchesCPU ./pkg/lx/

Expected single log line:

[crypto/backend] GPU fallback: reason=disabled where=clob

8. cevm declines to the Go EVM

cevm's Go entry (gpu_execute_block, go_bridge.h ABI 7) answers ok=0 for any block it does not run — any batch with code, and any refusal on balance, nonce, price, block gas limit, revision or hashes. A loaded library that reports another ABI than the binding reads is sent nothing, and every block is declined. The executor in evm/cevm/parallel then returns (nil, nil), and the Go EVM runs the block. There is no switch: a decline is never an error.

cd ~/work/lux/chains
go test -count=1 ./evm/cevm/parallel/

9. Full sweep (build everything, run everything)

# crypto layer (under the workspace go.work)
cd ~/work/lux/crypto
go test -count=1 -short ./...

# touched packages
cd ~/work/lux/lattice && go test -count=1 -short ./gpu/
cd ~/work/lux/corona  && go test -count=1 -short ./dkg2/
cd ~/work/lux/fhe     && go test -count=1 -short -run TestNTT .
cd ~/work/lux/chains  && go test -count=1 -short ./evm/cevm/parallel/

# DEX
cd ~/work/lx/dex && go test -count=1 -short ./pkg/lx/ ./pkg/trading/

# V4 DEX precompile e2e integration (7 steps)
cd ~/work/lux/precompile/dex
go test -count=1 -v -run TestIntegrationMultiHopMultiVenue .

All should be green. The single permitted failure is pulsar/TestAlgebraic_RealRNG_Smokes — pre-existing, flagged as "byte-equality pending" in commit 734d7e0, unrelated to this PR set.

10. Bench (optional, NVIDIA-only signal)

cd ~/work/lx/dex
go test -bench BenchmarkMatchOrder -benchtime=3s -run xxx ./pkg/lx/

This is your "did the GPU actually help" sanity check. Expect significant speedup over CPU for batch sizes ≥ 64; below that the kernel-launch overhead dominates and CPU wins.

What to look at if something breaks

Symptom Likely cause First check
init() panic at startup, message mentions ABI size drifted Go struct out of sync with C header git diff luxcpp/cuda/include/lux/cuda/dex_swap.h and lx/dex/pkg/lx/orderbook_cuda_types.go
dex_clob_match_order_host rc=-2 log line CUDA driver not loaded or no device visible nvidia-smi, ldconfig -p | grep libcudart
dex_clob_match_order_host rc=-3 Device OOM Reduce book size in the failing case; check device free memory
dex_clob_match_order_host rc=-4 Kernel launch / memcpy failure Run with cuda-memcheck, check cudaGetLastError
Parity test fails with field mismatch Kernel actually returned different value than CPU oracle — real bug Dump incoming, book, indices from the failing seed and run both paths in isolation
pkg-config --cflags lux-dex-clob-cuda empty cmake --install step skipped Re-run step 1

What's still on the luxcpp roadmap (not blocking this PR)

  • cevm V5 kernel: CALL/CREATE on device per chains/evm/cevm/V5_ABI.md. Until a Go entry runs code, cevm declines every block with code and the Go EVM runs it.
  • Precompile-engine → MatchOrderGPU bridge: the lux/precompile/dex engine's matching path uses its own embedded path today. Hooking it to MatchOrderGPU is one small Go change in engine_embedded.go::Swap / the CLOB match dispatch — wait for this PR to land + NVIDIA validation first.
  • Magnetar threshold SLH-DSA: separate LP track, see lps/LP-181-magnetar-threshold-slhdsa.md.