feat(dqn): enable Branching DQN with 45 factored actions (5×3×3)
Restore 45-action factored space via Branching DQN (Tavakoli 2018), outputting 11 Q-values (5+3+3) instead of 45. This was reduced to 5 exposure-only actions during debugging and was never intended as permanent. - Enable use_branching: true by default in DQNConfig and DQNHyperparameters - Add branching paths to select_action_with_confidence and select_action_inference - Update agent.rs select_action_factored for branching-aware selection - Expand CountBonus to per-branch tracking with bonuses_branched() - Add order_type + urgency distribution tracking in monitoring - Add DQN_ORDER_ACTIONS=3, DQN_URGENCY_ACTIONS=3, DQN_TOTAL_ACTIONS=45 to CUDA header - Fix 7 pre-existing clippy doc_markdown errors in regime_conditional.rs - Fix pre-existing cognitive_complexity in replay_buffer_type.rs (extract helpers) - Fix flaky GPU test OOM under parallel execution (CPU fallback + test VRAM safety) - Delete unused flash_attention submodules (block_sparse, causal_masking, etc.) - Add GPU hot-path guard scripts and ensemble/hyperopt adapter improvements Tests: ml-dqn 416/0, ml 905/0, clippy 0 errors on both crates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
218
scripts/gpu-hotpath-guard.sh
Executable file
218
scripts/gpu-hotpath-guard.sh
Executable file
@@ -0,0 +1,218 @@
|
||||
#!/bin/bash
|
||||
# GPU Hot-Path Guard — detects CPU roundtrip leaks and GPU perf killers.
|
||||
#
|
||||
# Production HFT system on H100 GPUs. Every microsecond matters.
|
||||
# This guard catches ALL patterns that cause GPU pipeline stalls,
|
||||
# unnecessary CPU↔GPU memory transfers, or heap allocations in hot paths.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/gpu-hotpath-guard.sh <file> # check single file
|
||||
# scripts/gpu-hotpath-guard.sh --staged # check all staged .rs files
|
||||
# scripts/gpu-hotpath-guard.sh --diff # check unstaged changes only
|
||||
# scripts/gpu-hotpath-guard.sh --all # check all hot-path .rs files
|
||||
#
|
||||
# Exit codes:
|
||||
# 0 = clean (no leaks, or file not in hot path)
|
||||
# 1 = GPU perf violation detected in hot-path file
|
||||
#
|
||||
# Suppress reviewed boundary points with: // gpu-ok: <reason>
|
||||
# NOTE: New code should NEVER use // gpu-ok: — eliminate the transfer instead.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Hot-path file patterns ──────────────────────────────────────────────
|
||||
# Only files matching these patterns are checked. Everything else is ignored.
|
||||
HOT_PATHS=(
|
||||
# DQN training hot loop (entire trainer directory)
|
||||
"trainers/dqn/"
|
||||
"cuda_pipeline/"
|
||||
"gpu_action_selector"
|
||||
"gpu_portfolio"
|
||||
"gpu_replay_buffer"
|
||||
"gpu_experience_collector"
|
||||
"gpu_backtest_evaluator"
|
||||
"gpu_training_guard"
|
||||
"gpu_weights"
|
||||
"mixed_precision.rs"
|
||||
# DQN crate — all modules (inference, networks, replay, regularization)
|
||||
"ml-dqn/src/"
|
||||
# PPO training + inference
|
||||
"trainers/ppo"
|
||||
"ml-ppo/src/"
|
||||
# Supervised model forward passes
|
||||
"ml-supervised/src/"
|
||||
# Hyperopt backtest inner loops
|
||||
"hyperopt/adapters/"
|
||||
"hyperopt/walk_forward.rs"
|
||||
# Evaluation binaries (GPU inference paths)
|
||||
"examples/evaluate_baseline.rs"
|
||||
"examples/train_baseline"
|
||||
# Ensemble inference
|
||||
"ensemble/"
|
||||
# Feature extraction (GPU-resident)
|
||||
"ml-features/src/"
|
||||
# Flash attention (GPU-only computation)
|
||||
"flash_attention/"
|
||||
)
|
||||
|
||||
# ── GPU perf violation patterns ────────────────────────────────────────
|
||||
# Each pattern is a grep -E regex. Lines with "// gpu-ok:" are excluded.
|
||||
# Doc comments (/// or //!) and Storage match arms (error guards) are excluded.
|
||||
LEAK_PATTERNS=(
|
||||
# ─── GPU→CPU data transfers (tensor → CPU memory) ───
|
||||
# Each of these forces a CUDA stream synchronize + PCIe DMA transfer.
|
||||
# On H100: ~5-15μs per transfer, blocks entire GPU pipeline.
|
||||
'\.to_vec1'
|
||||
'\.to_vec2'
|
||||
'\.to_vec3'
|
||||
'\.to_scalar'
|
||||
'\.to_device\(&Device::Cpu\)'
|
||||
'\.to_device\(&candle_core::Device::Cpu\)'
|
||||
'\.get\([0-9]+\)\?\.to_vec'
|
||||
|
||||
# ─── CPU→GPU data transfers (CPU heap alloc + memcpy to VRAM) ───
|
||||
# Tensor::from_vec: allocates Vec on CPU heap, copies to GPU.
|
||||
# On H100: CPU alloc ~0.5-2μs + PCIe DMA ~5-15μs = 5-17μs per call.
|
||||
# Must be eliminated from inner loops. Acceptable at data-load boundaries.
|
||||
'Tensor::from_vec'
|
||||
'Tensor::from_slice'
|
||||
|
||||
# ─── CPU device usage (creating anything on CPU in GPU code) ───
|
||||
'&Device::Cpu'
|
||||
|
||||
# ─── Dead GPU transfers (underscore-hidden variables suppress clippy) ───
|
||||
# let _foo = Tensor::from_vec/from_slice — allocated on GPU, never used.
|
||||
# let _foo = tensor.to_vec1/to_scalar — forced GPU→CPU sync, result discarded.
|
||||
'let _\w+\s*=.*Tensor::from_vec'
|
||||
'let _\w+\s*=.*Tensor::from_slice'
|
||||
'let _\w+\s*=.*\.to_vec[123]'
|
||||
'let _\w+\s*=.*\.to_scalar'
|
||||
|
||||
# NOTE: memcpy_dtoh/htod and .synchronize() are not checked here —
|
||||
# they are CUDA implementation primitives in cuda_pipeline/.
|
||||
# Audit those separately with: scripts/cuda-perf-audit.sh
|
||||
)
|
||||
|
||||
# ── Excluded paths (test-only infra that deliberately uses CPU) ────────
|
||||
EXCLUDE_PATHS=(
|
||||
"smoke_tests/"
|
||||
"_test.rs"
|
||||
"_tests.rs"
|
||||
"performance_tests.rs"
|
||||
"performance_validation.rs"
|
||||
"demo_dqn.rs"
|
||||
)
|
||||
|
||||
is_hot_path() {
|
||||
local file="$1"
|
||||
# Check exclusions first
|
||||
for excl in "${EXCLUDE_PATHS[@]}"; do
|
||||
if [[ "$file" == *"$excl"* ]]; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
for pattern in "${HOT_PATHS[@]}"; do
|
||||
if [[ "$file" == *"$pattern"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
check_file() {
|
||||
local file="$1"
|
||||
local found=0
|
||||
|
||||
if ! is_hot_path "$file"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Find the line number where #[cfg(test)] starts — everything after is test code
|
||||
local test_start
|
||||
test_start=$(grep -n '#\[cfg(test)\]' "$file" 2>/dev/null | head -1 | cut -d: -f1 || echo "999999")
|
||||
|
||||
for pattern in "${LEAK_PATTERNS[@]}"; do
|
||||
# Find matches, exclude: suppressed lines, doc comments, Storage match arms, regular comments
|
||||
local hits
|
||||
hits=$(grep -nE "$pattern" "$file" 2>/dev/null \
|
||||
| grep -v '// gpu-ok:' \
|
||||
| grep -v '[0-9]*:\s*///' \
|
||||
| grep -v '[0-9]*:\s*//!' \
|
||||
| grep -v '[0-9]*:\s*//' \
|
||||
| grep -v 'Storage::Cpu' \
|
||||
| grep -v 'Storage::Metal' \
|
||||
|| true)
|
||||
|
||||
# Filter out lines in test code (line number >= test_start)
|
||||
if [ -n "$hits" ]; then
|
||||
local filtered=""
|
||||
while IFS= read -r line; do
|
||||
local lineno
|
||||
lineno=$(echo "$line" | cut -d: -f1)
|
||||
if [ "$lineno" -lt "$test_start" ] 2>/dev/null; then
|
||||
filtered="${filtered}${line}"$'\n'
|
||||
fi
|
||||
done <<< "$hits"
|
||||
filtered="${filtered%$'\n'}" # trim trailing newline
|
||||
|
||||
if [ -n "$filtered" ]; then
|
||||
if [ "$found" -eq 0 ]; then
|
||||
echo "GPU HOT-PATH LEAK: $file"
|
||||
found=1
|
||||
fi
|
||||
echo "$filtered" | while IFS= read -r line; do
|
||||
echo " $line"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
return $found
|
||||
}
|
||||
|
||||
# ── Entrypoint ──────────────────────────────────────────────────────────
|
||||
FILES=()
|
||||
MODE="file"
|
||||
|
||||
if [ "${1:-}" = "--staged" ]; then
|
||||
MODE="staged"
|
||||
while IFS= read -r f; do
|
||||
FILES+=("$f")
|
||||
done < <(git diff --cached --name-only --diff-filter=ACM | grep '\.rs$' || true)
|
||||
elif [ "${1:-}" = "--diff" ]; then
|
||||
MODE="diff"
|
||||
while IFS= read -r f; do
|
||||
FILES+=("$f")
|
||||
done < <(git diff --name-only | grep '\.rs$' || true)
|
||||
elif [ "${1:-}" = "--all" ]; then
|
||||
MODE="all"
|
||||
while IFS= read -r f; do
|
||||
FILES+=("$f")
|
||||
done < <(find crates -name '*.rs' -type f 2>/dev/null)
|
||||
elif [ -n "${1:-}" ]; then
|
||||
FILES=("$1")
|
||||
else
|
||||
echo "Usage: $0 <file|--staged|--diff|--all>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
LEAKED=0
|
||||
LEAK_COUNT=0
|
||||
for file in "${FILES[@]}"; do
|
||||
if [ -f "$file" ]; then
|
||||
if ! check_file "$file"; then
|
||||
LEAKED=1
|
||||
LEAK_COUNT=$((LEAK_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$LEAKED" -eq 1 ]; then
|
||||
echo ""
|
||||
echo "⛔ $LEAK_COUNT file(s) with GPU perf violations in hot paths."
|
||||
echo " MUST be eliminated — do not suppress with // gpu-ok:"
|
||||
echo " Move transfers out of hot paths or keep data GPU-resident."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
33
scripts/gpu-hotpath-hook.sh
Executable file
33
scripts/gpu-hotpath-hook.sh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
# Claude Code PostToolUse hook: HARD ERROR on GPU→CPU leaks in hot-path files.
|
||||
# Reads JSON from stdin (Claude Code hook input), extracts file_path, runs guard.
|
||||
# Exit 1 = leak detected → Claude Code blocks the edit and must fix immediately.
|
||||
set -euo pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty' 2>/dev/null)
|
||||
|
||||
if [ -z "$FILE" ] || [ ! -f "$FILE" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Only check Rust files
|
||||
case "$FILE" in
|
||||
*.rs) ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
GUARD="$SCRIPT_DIR/gpu-hotpath-guard.sh"
|
||||
|
||||
if [ -x "$GUARD" ]; then
|
||||
OUTPUT=$("$GUARD" "$FILE" 2>&1) || {
|
||||
echo "⛔ GPU HOT-PATH HARD ERROR in $FILE"
|
||||
echo "$OUTPUT"
|
||||
echo ""
|
||||
echo "CPU usage in GPU hot paths is NOT allowed. Fix the code — do not annotate with // gpu-ok:"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -57,10 +57,23 @@ if [ -n "$STAGED_FILES" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# GPU hot-path leak detection
|
||||
echo "🔎 Checking for GPU→CPU leaks in hot paths..."
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
if [ -x "$SCRIPT_DIR/gpu-hotpath-guard.sh" ]; then
|
||||
if ! "$SCRIPT_DIR/gpu-hotpath-guard.sh" --staged; then
|
||||
echo "⛔ GPU hot-path leak detected — fix or suppress with // gpu-ok: <reason>"
|
||||
exit 1
|
||||
fi
|
||||
echo " No GPU→CPU leaks in hot paths"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "✅ All pre-commit checks passed!"
|
||||
echo ""
|
||||
echo "Summary:"
|
||||
echo " - Code quality checks: ✅"
|
||||
echo " - GPU hot-path guard: ✅"
|
||||
echo ""
|
||||
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user