dev / templates / bot / src / __NAME__ / rl / train.cljs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
;; The trainer: the only process that ever updates weights. One run ingests
;; the live bots' episode logs, plays fast gym self-play (peer-kit's pure
;; sims — thousands of episodes a minute, no networking), and atomically
;; rewrites the shared checkpoints the live bots reload.
;;
;; This is a skeleton — no games train yet. Filling it in is stage §5 of
;; docs/NEW-BOT-PROMPT.md, which delegates each brain to train-kit's
;; NEW-BRAIN-PROMPT.md. The game-agnostic core (forward pass, episode
;; ingestion, checkpoint/gate file logic, tf fit helpers) is all in
;; ardegazu-train-kit; what lands HERE is game knowledge — per-game
;; collection loops, evals and promotion thresholds. bot.git's
;; src/bot/rl/train.cljs is the full worked version. Note tfjs-node is an
;; optional peer of train-kit behind a dynamic import (`loadTf`) — install
;; @tensorflow/tfjs-node only once a net-fitting game arrives; tabular
;; games never need it.
;;
;; The require menu once games train:
;;   ["ardegazu-peer-kit" :refer (SeanceGym NeonGridGym ValleyBlocksGym mulberry32)]
;;   ["ardegazu-train-kit" :refer (readNewTransitions   ; exactly-once ingestion (offsets)
;;                                 ensureRow qUpdate    ; tabular Q
;;                                 netQs dqnTargets fitNet ; net fitting (lazy tfjs-node)
;;                                 capReplay
;;                                 resolveModel gatePromotion ; checkpoint discipline
;;                                 expandDirs loadIngestState saveIngestState
;;                                 mlpInit)]
;;
;;   node dist/rl/train.js --config /etc/ardegazu-__NAME__/trainer.json
;;     [--game <name>] [--gym-only] [--ingest-only]
(ns __NAME__.rl.train
  (:require ["node:fs" :as fs]
            ["node:path" :as path]))

(def ^:private DEFAULTS
  #js {:modelDir "/var/lib/ardegazu-__NAME__/models"
       ;; Episode dirs; a single "*" path segment globs (per-bot state dirs).
       :episodeDirs #js ["/var/lib/ardegazu-__NAME__/*/episodes"]
       ;; TODO: one hyperparameter block per learned game, e.g.
       ;; :myGame #js {:gymEpisodes 400 :players 2 :gamma 0.95 …}
       })

;; CLI contract (kept stable so the systemd unit and NEW-BRAIN-PROMPT flows
;; apply unchanged): --config <path> --game <name> --gym-only --ingest-only
(def ^:private argv (.slice js/process.argv 2))
(defn- flag? [name] (.includes argv (str "--" name)))
(defn- opt [name]
  (let [i (.indexOf argv (str "--" name))]
    (if (>= i 0) (aget argv (inc i)) js/undefined)))

(defn- load-trainer-config [cfg-path]
  (let [^js file-cfg (if (some? cfg-path)
                       (js/JSON.parse (fs/readFileSync cfg-path "utf8"))
                       #js {})]
    (js/Object.assign #js {} DEFAULTS file-cfg)))

(defn- run []
  (let [^js cfg (load-trainer-config (opt "config"))
        only-game (opt "game")]
    (js/console.log
     (str "trainer: modelDir=" (.-modelDir cfg)
          " game=" (if (some? only-game) only-game "all")
          (if (flag? "gym-only") " (gym only)" "")
          (if (flag? "ingest-only") " (ingest only)" "")))
    ;; TODO per learned game: a train-<game> fn that
    ;;   1. ingests live transitions (readNewTransitions — offsets, exactly-once),
    ;;   2. runs gym self-play with the current frozen model (seat 0 learns
    ;;      ε-greedy; other seats alternate net/stock so the learner feels real
    ;;      opposition),
    ;;   3. fits (dqnTargets → fitNet → capReplay, or qUpdate for tabular),
    ;;   4. gates greedy-net-vs-stock and promotes via gatePromotion.
    ;; Then dispatch here on only-game / --gym-only / --ingest-only.
    (throw (js/Error. "no games registered for training yet — see docs/NEW-BOT-PROMPT.md §5"))))

(defn init
  "Run only as the process entry script (`node dist/rl/train.js …`) — an
  import of this module (tests, tooling) must stay side-effect free."
  []
  (when (identical? (some-> (aget js/process.argv 1) (path/basename)) "train.js")
    (run)))

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/dev.git