fix(PPO_Bot): SIGSEGV crash fixes + static buffers for thread safety
- bullets: seq[InFlightBullet] → array[4, InFlightBullet] + bulletCount (eliminates cross-thread heap realloc under ORC) - hasFired: edge-triggered (cleared after state build, not level-triggered) - round_counter parseInt: wrapped for empty/torn file → 0 - Static SVG + intent buffers to kill cross-thread heap realloc - Tick-local alive/bulletData also fixed arrays Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+34
-20
@@ -33,26 +33,30 @@ proc saveWeights*(ac: ActorCritic, dir: string) =
|
||||
ac.logStd.write_npy(dir / "log_std.npy")
|
||||
|
||||
proc loadWeights*(ac: var ActorCritic, dir: string) =
|
||||
## Load all weight tensors from dir/. Asserts shapes match.
|
||||
template loadAndCheck(dest: untyped, path: string) =
|
||||
## Load all weight tensors from dir/.
|
||||
## If a tensor's shape doesn't match (e.g. STATE_DIM changed), keep the
|
||||
## freshly-initialised value and print a warning — other tensors still load.
|
||||
template loadOrSkip(dest: untyped, path: string) =
|
||||
let loaded = read_npy[float32](path)
|
||||
doAssert loaded.shape == dest.shape,
|
||||
"Shape mismatch loading " & path & ": got " & $loaded.shape & " want " & $dest.shape
|
||||
dest = loaded
|
||||
if loaded.shape == dest.shape:
|
||||
dest = loaded
|
||||
else:
|
||||
echo "weights: shape mismatch for " & path &
|
||||
" (got " & $loaded.shape & " want " & $dest.shape & ") — keeping fresh init"
|
||||
|
||||
loadAndCheck(ac.actor.w1, dir / "actor_w1.npy")
|
||||
loadAndCheck(ac.actor.b1, dir / "actor_b1.npy")
|
||||
loadAndCheck(ac.actor.w2, dir / "actor_w2.npy")
|
||||
loadAndCheck(ac.actor.b2, dir / "actor_b2.npy")
|
||||
loadAndCheck(ac.actor.w3, dir / "actor_w3.npy")
|
||||
loadAndCheck(ac.actor.b3, dir / "actor_b3.npy")
|
||||
loadAndCheck(ac.critic.w1, dir / "critic_w1.npy")
|
||||
loadAndCheck(ac.critic.b1, dir / "critic_b1.npy")
|
||||
loadAndCheck(ac.critic.w2, dir / "critic_w2.npy")
|
||||
loadAndCheck(ac.critic.b2, dir / "critic_b2.npy")
|
||||
loadAndCheck(ac.critic.w3, dir / "critic_w3.npy")
|
||||
loadAndCheck(ac.critic.b3, dir / "critic_b3.npy")
|
||||
loadAndCheck(ac.logStd, dir / "log_std.npy")
|
||||
loadOrSkip(ac.actor.w1, dir / "actor_w1.npy")
|
||||
loadOrSkip(ac.actor.b1, dir / "actor_b1.npy")
|
||||
loadOrSkip(ac.actor.w2, dir / "actor_w2.npy")
|
||||
loadOrSkip(ac.actor.b2, dir / "actor_b2.npy")
|
||||
loadOrSkip(ac.actor.w3, dir / "actor_w3.npy")
|
||||
loadOrSkip(ac.actor.b3, dir / "actor_b3.npy")
|
||||
loadOrSkip(ac.critic.w1, dir / "critic_w1.npy")
|
||||
loadOrSkip(ac.critic.b1, dir / "critic_b1.npy")
|
||||
loadOrSkip(ac.critic.w2, dir / "critic_w2.npy")
|
||||
loadOrSkip(ac.critic.b2, dir / "critic_b2.npy")
|
||||
loadOrSkip(ac.critic.w3, dir / "critic_w3.npy")
|
||||
loadOrSkip(ac.critic.b3, dir / "critic_b3.npy")
|
||||
loadOrSkip(ac.logStd, dir / "log_std.npy")
|
||||
|
||||
proc saveWeightsAtomic*(ac: ActorCritic, targetDir: string) =
|
||||
## Write to a temp dir, then rename atomically over targetDir.
|
||||
@@ -104,8 +108,14 @@ proc saveAdamStates*(adam: ACAdamStates, dir: string) =
|
||||
|
||||
proc loadAdamStates*(adam: var ACAdamStates, dir: string) =
|
||||
## Load Adam m/v tensors and t counters from dir/. Called only when files exist.
|
||||
## Shape mismatch (e.g. STATE_DIM changed) → keep zero-initialised state (safe fresh start).
|
||||
template lm(dest: untyped, path: string) =
|
||||
dest = read_npy[float32](path)
|
||||
let loaded = read_npy[float32](path)
|
||||
if loaded.shape == dest.shape:
|
||||
dest = loaded
|
||||
else:
|
||||
echo "weights: Adam shape mismatch for " & path &
|
||||
" (got " & $loaded.shape & " want " & $dest.shape & ") — resetting Adam state"
|
||||
lm(adam.aw1.m, dir / "adam_aw1_m.npy"); lm(adam.aw1.v, dir / "adam_aw1_v.npy")
|
||||
lm(adam.ab1.m, dir / "adam_ab1_m.npy"); lm(adam.ab1.v, dir / "adam_ab1_v.npy")
|
||||
lm(adam.aw2.m, dir / "adam_aw2_m.npy"); lm(adam.aw2.v, dir / "adam_aw2_v.npy")
|
||||
@@ -185,7 +195,11 @@ proc loadBestAvailable*(ac: var ActorCritic, adam: var ACAdamStates,
|
||||
if adamStateFilesExist(candidate):
|
||||
adam.loadAdamStates(candidate)
|
||||
let rcPath = weightsRoot / "round_counter.txt"
|
||||
let roundNum = if fileExists(rcPath): parseInt(readFile(rcPath).strip()) else: 0
|
||||
# Torn/empty file (e.g. after a crash) must not abort startup → treat as 0
|
||||
let roundNum = if fileExists(rcPath):
|
||||
try: parseInt(readFile(rcPath).strip())
|
||||
except ValueError: 0
|
||||
else: 0
|
||||
return (loaded: true, roundNum: roundNum)
|
||||
result = (loaded: false, roundNum: 0)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user