🕑 ~15 min read
In the previous post, I introduced CJC-Lang and the design decisions behind its determinism guarantees. This post goes deeper into one specific question: can you build real neural network architectures in a language with no ML framework?
The answer is yes — and the determinism makes verification straightforward. Every architecture below has been implemented in pure CJC-Lang, run through both execution engines, and verified with assertions. Same seed, same output, every time.
Feedforward MLP (Multi-Layer Perceptron)
The most fundamental architecture — and the building block for everything else. CJC-Lang expresses neural networks as struct-based layers with explicit weight tensors:
struct Layer {
weights: Tensor,
bias: Tensor
}
fn forward(layer: Layer, input: Tensor) -> Tensor {
matmul(input, layer.weights) |> add_bias(layer.bias)
}
fn relu_scalar(x: f64) -> f64 {
if x > 0.0 { return x; }
return 0.0;
}
let x = Tensor.from_vec([1.0, 2.0, 3.0], [1, 3]);
let layer1 = Layer {
weights: Tensor.from_vec(
[0.1, 0.2, -0.1, 0.3,
0.4, -0.2, 0.1, 0.5,
-0.3, 0.1, 0.2, -0.1], [3, 4]),
bias: Tensor.from_vec([0.1, -0.1, 0.05, 0.0], [1, 4])
};
let layer2 = Layer {
weights: Tensor.from_vec(
[0.2, -0.3, 0.1, 0.4,
-0.2, 0.1, 0.3, -0.1], [4, 2]),
bias: Tensor.from_vec([0.0, 0.1], [1, 2])
};
let h = forward(layer1, x);
let h_relu = relu_1x4(h);
let out = forward(layer2, h_relu);
print("Network output:", out);
Every weight is a plain Tensor you can inspect, serialize with snap_hash(), and verify for bit-identity across runs. The pipe operator (|>) makes the data flow explicit. There’s no hidden state behind a class — the math is transparent.
The benchmarks push this further: a 50-layer deep network (width 64, batch 4) and a 10-layer wide network (width 256, batch 16) are tested with snap_hash() checkpoints every few layers. Both produce identical hashes across runs, verifying that even deep forward passes through random weight matrices stay deterministic.
REINFORCE Policy Network (Reinforcement Learning)
The chess RL benchmark implements a full policy gradient agent in pure CJC-Lang — not a toy example, but an agent that plays chess, selects moves via softmax sampling, and updates weights via REINFORCE.
flowchart LR
B["Board 64sq\n+ Move from/to"] --> F["Features\n[1, 66]"] --> H["Hidden\nW₁ [66,16] + b₁\nReLU"] --> S["Score\nW₂ [16,1]"] --> A["Softmax\n→ Sample"]
REINFORCE policy network for chess. Board state and move coordinates flow through a hidden layer to produce action probabilities.
fn forward_move(W1: Tensor, b1: Tensor, W2: Tensor,
board_features: Tensor, from_sq: i64, to_sq: i64) -> Any {
let feat_data = [];
let i = 0;
while i < 64 {
feat_data = array_push(feat_data, board_features.get([0, i]));
i = i + 1;
}
feat_data = array_push(feat_data, float(from_sq) / 63.0);
feat_data = array_push(feat_data, float(to_sq) / 63.0);
let input = Tensor.from_vec(feat_data, [1, 66]);
let hidden = (matmul(input, W1) + b1).relu();
let score_t = matmul(hidden, W2);
[score_t.get([0, 0])]
}
fn select_action(W1: Tensor, b1: Tensor, W2: Tensor,
board_features: Tensor, moves: Any) -> Any {
let num_moves = len(moves) / 2;
let scores = [];
let i = 0;
while i < num_moves {
let result = forward_move(W1, b1, W2, board_features,
moves[i * 2], moves[i * 2 + 1]);
scores = array_push(scores, result[0]);
i = i + 1;
}
let scores_t = Tensor.from_vec(scores, [num_moves]);
let probs = scores_t.softmax();
let action_idx = categorical_sample(probs);
let log_prob = log(probs.get([action_idx]));
[float(action_idx), log_prob, float(num_moves)]
}
This is a complete RL pipeline — feature construction, forward pass, softmax action selection, log-probability tracking, and gradient computation — written entirely in CJC-Lang. The agent manually computes d(log_prob)/d(theta) through softmax, ReLU, and matmul.
The key property: categorical_sample() uses CJC-Lang’s seeded SplitMix64 RNG, and the softmax goes through Kahan summation. Running cjcl run chess_agent.cjcl --seed 42 produces the exact same game, moves, and weight updates every time. 69 tests verify the chess RL pipeline end-to-end.
Convolutional Neural Networks (CNN)
CJC-Lang’s tensor runtime includes native 1D and 2D convolution, maxpooling, and the full pipeline for building convolutional architectures.
flowchart LR
I["Input\n[4,3,28,28]"] --> C["Conv2D\n32 filters 3x3"] --> R["ReLU"] --> BN["BatchNorm"] --> MP["MaxPool2D\n2x2"] --> O["Output\n[4,32,13,13]"]
CNN pipeline in CJC-Lang. Native conv2d, ReLU, batch normalization, and maxpooling — all deterministic.
// 2D convolution: batch of images through learned filters
let input = Tensor.randn([4, 3, 28, 28]); // batch=4, channels=3, 28x28
let filters = Tensor.randn([32, 3, 3, 3]); // 32 output channels, 3x3 kernel
let bias = Tensor.zeros([32]);
let conv_out = input.conv2d(filters, bias, 1); // -> [4, 32, 26, 26]
// Activation + normalization + pooling
let activated = conv_out.relu();
let pooled = activated.maxpool2d(2, 2); // -> [4, 32, 13, 13]
CJC-Lang also supports 1D convolution with circular padding (conv1d_circular) for signal processing, and the full set of CNN building blocks: batch_norm, dropout_mask, arbitrary strides, and multi-channel operations.
Deep Network Stress Tests
Beyond the architecture-specific examples above, CJC-Lang includes benchmark programs that push forward passes to realistic scale:
bench_nn_deep |
50 layers |
64 |
4 |
Bit-identical via snap_hash |
bench_nn_wide |
10 layers |
256 |
16 |
Bit-identical via snap_hash |
Both benchmarks insert snap_hash() checkpoints every few layers, producing cryptographic hashes of intermediate activations. Running twice with --seed 42 produces identical hashes at every checkpoint — proving that determinism holds through deep chains of matmul, bias, ReLU, and normalization.
What This Proves
These aren’t theoretical capabilities. Each architecture has been implemented, run through both execution engines, and verified with assertions:
- The REINFORCE agent plays chess (69 tests verify the full pipeline)
- The PINN converges to the analytical solution of a differential equation
- The CNN processes real 4D tensor shapes with convolution, pooling, and batch normalization
- The attention mechanism handles batched multi-sequence inputs
- The deep network benchmarks verify hash-level determinism through 50 sequential layers
All of them produce identical results across runs. In CJC-Lang, that’s not an opt-in mode — it’s how computation works.
If you haven’t read the introduction to CJC-Lang and the design decisions behind the language, start with Introducing CJC-Lang.
Next up: I’ll write about what it took to build a dual-engine compiler pipeline in Rust — and the design decisions that made parity testing between an AST interpreter and a register machine actually tractable.
CJC-Lang is open source and under active development. Follow @adamezzat for updates as new features land.