The RTX 4070 Ti + CUDA 12 toolkit + libnvblas.so are already installed on this machine (from pyRBM's cupy setup); Bandicoot (the alternative, much bigger Armadillo-to-GPU rewrite) is not. NVBLAS is a drop-in BLAS shim -- zero source changes -- that intercepts Armadillo's large matrix-multiply calls (v_to_h, h_to_v, CD gradients) and offloads them to the GPU via cuBLAS, falling back to the system's OpenBLAS otherwise. Add nvblas.conf (CPU fallback pointed at openblas-pthread, GPU_LIST ALL) and run_gpu.sh, an opt-in LD_PRELOAD wrapper: ./run_gpu.sh <binary> [args...]. Verified for real: poet.elf f produced byte-identical output through the wrapper, and a real training run against moby_ch1.txt showed GPU utilization rise from ~0-1% idle to 7-28% and memory usage grow from 893MiB to 1.4GB, while training progressed correctly (error decreasing, coherent generated text). Added nvblas.log to .gitignore. Also fixed two stale CLAUDE.md sections found along the way: the TEST target description still described the old broken manual smoke-test main.cpp instead of the minimal test suite that replaced it, and the "Project files on disk" section still described the flat repo-root layout from before the prj/<name>/ restructure. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
119 lines
7.3 KiB
Markdown
119 lines
7.3 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Overview
|
|
|
|
C++11 library and set of executables implementing Restricted Boltzmann Machines (RBMs), stacked into
|
|
deep belief networks (`DeepStack`) or recurrent structures (`RnnStack`) for character-level text generation
|
|
("poet"). There are three build targets sharing the same core sources: a JUCE-based desktop GUI (`GUI`), a
|
|
console test harness (`TEST`), and a text-generation CLI (`POET`).
|
|
|
|
## Build
|
|
|
|
Build system is a hand-written `Makefile` (no CMake). Select the target with `PRJ` and the build flavor with
|
|
`CONFIG`:
|
|
|
|
```sh
|
|
make PRJ=POET CONFIG=release # builds build/release/poet.elf (default PRJ=POET, CONFIG=release)
|
|
make PRJ=TEST CONFIG=debug # builds build/debug/test.elf
|
|
make PRJ=GUI # builds build/release/rbm.elf, also builds the JUCE static lib first
|
|
make clean # removes build/${CONFIG}
|
|
```
|
|
|
|
The `GUI` target additionally depends on `juce/build/${CONFIG}/libjuce.a`, built via `make -C juce` (uses
|
|
`juce/Makefile`, `juce/pkg.mk`, `juce/JUCE-3.1.1.mk` against the vendored `juce/JUCE-3.1.1` source tree).
|
|
`install` copies `build/release/rbm.elf` to `${HOME}/bin`.
|
|
|
|
Object/dependency files land in `build/${CONFIG}/`; `.d` files are auto-included for header dependency
|
|
tracking. There is no separate lint command. `TEST_CXX_SRCS` builds `main.cpp` into `test.elf`, a
|
|
minimal dependency-free test suite: for each named project under `prj/<name>/`, it loads the project,
|
|
loads weights and training batch, runs a full up-pass/down-pass reconstruction (`DeepStack::upDownPass`),
|
|
and checks the reconstruction error is finite and beats a trivial per-feature-mean baseline. Prints
|
|
PASS/FAIL (or SKIP for known issues) per project and a summary line; exit code reflects only genuine
|
|
failures.
|
|
|
|
```sh
|
|
make PRJ=TEST CONFIG=debug && ./build/debug/test.elf
|
|
```
|
|
|
|
### GPU acceleration (optional)
|
|
|
|
`run_gpu.sh` runs any built binary with NVBLAS (`nvblas.conf`), which transparently intercepts Armadillo's
|
|
large matrix-multiply BLAS calls (`v_to_h`, `h_to_v`, CD gradients) and offloads them to an NVIDIA GPU via
|
|
cuBLAS, falling back to the system's OpenBLAS otherwise. No source changes — it's a runtime `LD_PRELOAD`
|
|
shim, opt-in only:
|
|
|
|
```sh
|
|
./run_gpu.sh ./build/release/poet.elf t some_text.txt
|
|
```
|
|
|
|
Requires an NVIDIA GPU + the CUDA runtime's `libnvblas.so` (already present on this machine, alongside
|
|
pyRBM's cupy setup). Confirmed via nvidia-smi utilization/memory during a real `poet` training run — no
|
|
behavior change, same output, just optionally GPU-accelerated matrix multiplies.
|
|
|
|
### External dependencies (system-installed, not vendored)
|
|
|
|
- **Armadillo** (`<armadillo>`, linked `-larmadillo`) — all matrix/vector math goes through `arma::mat`.
|
|
- **JsonCpp** (`<jsoncpp/json/json.h>`, linked `-ljsoncpp`) — project files (`*.prj`) and layer weight
|
|
metadata serialize to/from `Json::Value`.
|
|
- JUCE (vendored under `juce/`, only needed for the `GUI` target) plus its Linux deps: freetype, X11,
|
|
Xinerama, Xext, GL, pthread, dl, rt.
|
|
|
|
## Architecture
|
|
|
|
### Core RBM hierarchy
|
|
|
|
- **`Rbm`** (`Rbm.hpp/cpp`) — a single restricted Boltzmann machine: visible/hidden units, weights (`m_whv`),
|
|
biases (`m_bh`, `m_bv`), `Params` (learning rate, momentum, Gibbs sampling options, etc.), and the
|
|
contrastive-divergence training step (`cd`, `cd_hinton`, `cd_jens`, ...). Params and trained state
|
|
round-trip through `toJson()`/`fromJson()`.
|
|
- **`Layer`** (`Layer.hpp/cpp`) — wraps an `Rbm` with 2D visible geometry (`numVisibleX/Y`, for image-like
|
|
data) plus an optional context block (`numContext`, used by the RNN stack to feed in previous state).
|
|
Layers form an intrusive doubly-linked list via raw `next`/`prev` pointers (not `std::vector`); `root()`
|
|
walks to the first layer. Per-layer weights persist as separate `<prj>.Layer.<id>.{w,bh,bv}.dat` files
|
|
(`weightsLoad`/`weightsSave`), independent from the JSON project file.
|
|
- **`AStack`** (`AStack.hpp/cpp`) — abstract base owning the `Layer` linked list for a named project
|
|
("stack"). Handles layer add/remove, weight init/load/save for the whole stack, and the training-batch
|
|
matrix (`m_trainingBatch`, loaded from `*.training.dat`). Declares `train()` as pure virtual — subclasses
|
|
decide how data flows between layers.
|
|
- **`DeepStack`** — classic layer-wise DBN: `upPass`/`downPass`/`upDownPass` propagate a sample through
|
|
the full stack; `train()` greedily trains each layer on the representation produced by the layers below.
|
|
- **`RnnStack`** — treats the stack as a recurrent cell operating on one-hot character codes
|
|
(`NUM_CODES = 37`: space + a-z + 0-9). `step_forward` advances the recurrent state one timestep;
|
|
`v_to_vc`/`vc_to_v` pack/unpack the visible+context vector. Used by `poet.cpp` for text generation.
|
|
- **`StackCreator`** (`StackCreator.hpp/cpp`) — serializes/deserializes an `AStack` (and its `Layer`s) to/from
|
|
a `<name>.prj` JSON file. Reads `stack.type` to decide whether to instantiate a `DeepStack` or `RnnStack`.
|
|
Accepts an optional `LayerConstructor` callback so callers (e.g. the GUI) can substitute their own
|
|
`Layer` subclass (see `RbmComponent`) when building layers from JSON.
|
|
- **`RnnTextHelper`** / **`Matutils`** (`matutils.hpp`) — character <-> one-hot vector encoding
|
|
(`char2vec`/`vec2char`, `ch2idx`/`idx2ch`), training-batch construction from text files, and small
|
|
Armadillo helpers (`sample`, `prob`, `normalize`, `uniform`) used by the CD training routines.
|
|
|
|
### Entry points
|
|
|
|
- **`source/main.cpp`** — `TEST` target; exercises `DeepStack` directly (build/train/save a small DBN,
|
|
or load an existing one, depending on `CREATE_TEST`/`TRAIN_TEST` compile-time flags).
|
|
- **`source/poet.cpp`** — `POET` target; loads an `RnnStack` project (default `poet_2v_5s`) and dispatches
|
|
on `argv[1]`: `c`(reate)/`r`(eset weights)/`t`(rain, optionally with a training-text path in `argv[2]`)/
|
|
`f`(orward-generate text, optional seed string in `argv[2]`). Training periodically calls back into
|
|
`forward()` to sample generated text as a progress check.
|
|
- **`source/gui.cpp`** + **`MainComponent`** — JUCE app entry point; `MainComponent` implements
|
|
`LayerConstructor` to build `RbmComponent` layers (interactive `Layer` subclass with sliders/toggles bound
|
|
to `Rbm::Params`) and drives training via the `Rbm::IListener` progress callback, mirroring what
|
|
`RbmListener` does in the CLI tools.
|
|
|
|
### Project files on disk
|
|
|
|
Each named project `<name>` (e.g. `mnist`, `poet_2v_5s`, `context99` — see the ~40 project folders under
|
|
`prj/`, mostly saved experiment artifacts) lives under `prj/<name>/` and consists of:
|
|
- `<name>.prj` — JSON describing the stack type and layer geometry (via `StackCreator`).
|
|
- `<name>.training.dat` — the training batch matrix.
|
|
- `<name>.Layer.<id>.w.dat` / `.bh.dat` / `.bv.dat` — per-layer weights/biases (Armadillo binary/text format).
|
|
|
|
`StackCreator`/`AStack` resolve these paths themselves (`{dir}/prj/{name}/{name}.*`) from whatever base
|
|
`dir` the caller passes (`poet.cpp`/the GUI's startup auto-load both pass `"."`, the repo root) — callers
|
|
never construct the `prj/<name>/` part themselves. These are experiment data and generated artifacts, not
|
|
something to edit by hand; treat them as fixtures unless a task specifically concerns training data or
|
|
saved models.
|