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
7.3 KiB
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:
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.
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:
./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 througharma::mat. - JsonCpp (
<jsoncpp/json/json.h>, linked-ljsoncpp) — project files (*.prj) and layer weight metadata serialize to/fromJson::Value. - JUCE (vendored under
juce/, only needed for theGUItarget) 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 throughtoJson()/fromJson().Layer(Layer.hpp/cpp) — wraps anRbmwith 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 rawnext/prevpointers (notstd::vector);root()walks to the first layer. Per-layer weights persist as separate<prj>.Layer.<id>.{w,bh,bv}.datfiles (weightsLoad/weightsSave), independent from the JSON project file.AStack(AStack.hpp/cpp) — abstract base owning theLayerlinked 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). Declarestrain()as pure virtual — subclasses decide how data flows between layers.DeepStack— classic layer-wise DBN:upPass/downPass/upDownPasspropagate 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_forwardadvances the recurrent state one timestep;v_to_vc/vc_to_vpack/unpack the visible+context vector. Used bypoet.cppfor text generation.
StackCreator(StackCreator.hpp/cpp) — serializes/deserializes anAStack(and itsLayers) to/from a<name>.prjJSON file. Readsstack.typeto decide whether to instantiate aDeepStackorRnnStack. Accepts an optionalLayerConstructorcallback so callers (e.g. the GUI) can substitute their ownLayersubclass (seeRbmComponent) 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—TESTtarget; exercisesDeepStackdirectly (build/train/save a small DBN, or load an existing one, depending onCREATE_TEST/TRAIN_TESTcompile-time flags).source/poet.cpp—POETtarget; loads anRnnStackproject (defaultpoet_2v_5s) and dispatches onargv[1]:c(reate)/r(eset weights)/t(rain, optionally with a training-text path inargv[2])/f(orward-generate text, optional seed string inargv[2]). Training periodically calls back intoforward()to sample generated text as a progress check.source/gui.cpp+MainComponent— JUCE app entry point;MainComponentimplementsLayerConstructorto buildRbmComponentlayers (interactiveLayersubclass with sliders/toggles bound toRbm::Params) and drives training via theRbm::IListenerprogress callback, mirroring whatRbmListenerdoes 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 (viaStackCreator).<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.