Files
Rbm/CLAUDE.md
T
jensandClaude Sonnet 5 7bd3bbad57 Add optional NVBLAS GPU acceleration; fix stale CLAUDE.md sections
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
2026-07-27 16:24:34 +02:00

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 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 Layers) 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.cppTEST 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.cppPOET 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.