cd /news/machine-learning/pytorch-written-in-c Β· home β€Ί topics β€Ί machine-learning β€Ί article
[ARTICLE Β· art-136720] src=github.com β†— pub= topic=machine-learning verified=true sentiment=Β· neutral

PyTorch Written in C

Developer thevoxium released bare-lm, a minimal autograd tensor library written in C, installable via a git clone and make build or prebuilt Linux and macOS tarballs. The library provides a Memory object with 256 MB permanent and temporary arenas, a ParameterList for trainable tensors, and operations including linear_t, relu_t, sigmoid_t, mseloss_t, backward, zero_grad, and sgd_step, as shown in a 500-epoch XOR training example. bare-lm requires a C compiler, make, and OpenBLAS, and installs to /usr/local/include/bare.h and /usr/local/lib/libbare.{so,dylib}.

read9 min views2 publishedSep 22, 2026
PyTorch Written in C
Image: Michielbdejong (auto-discovered)

A minimal autograd tensor library in C.

You can install bare-lm in two ways.

Requirements:

  • A C compiler (cc ,gcc , orclang )
  • make
  • OpenBLAS (headers + library)

Install OpenBLAS first:

brew install openblas

sudo apt update && sudo apt install -y libopenblas-dev

Then build and install:

git clone https://github.com/thevoxium/bare-lm.git
cd bare-lm

make OPENBLAS_PREFIX=/opt/homebrew/opt/openblas

make

sudo make install

By default this installs to /usr/local/include/bare.h and /usr/local/lib/libbare.{so,dylib}.

Each tagged release publishes prebuilt archives for Linux and macOS:

  • bare-<version>-linux.tar.gz
  • bare-<version>-macos.tar.gz

After down and extracting a release tarball, install headers and library:

tar -xzf bare-<version>-<artifact>.tar.gz
cd bare-<version>-<artifact>
sudo cp include/bare.h /usr/local/include/
sudo cp lib/libbare.* /usr/local/lib/
cc file.c -lbare

If your system does not search /usr/local/lib by default, add -L/usr/local/lib and -I/usr/local/include.

#include "bare.h"

int main() {
  Memory *mem = create_global_mem(1 << 28);
  ParameterList *pl = create_param_list(mem);

  int x_shape[] = {4, 2};
  int y_shape[] = {4, 1};

  Tensor *x = tensor_init(mem, x_shape, 2, PERM);
  Tensor *y = tensor_init(mem, y_shape, 2, PERM);

  float x_data[] = {0, 0, 0, 1, 1, 0, 1, 1};
  float y_data[] = {0, 1, 1, 0};
  for (int i = 0; i < 8; i++) x->data[i] = x_data[i];
  for (int i = 0; i < 4; i++) y->data[i] = y_data[i];

  Linear *l1 = create_linear(mem, pl, 2, 8);
  Linear *l2 = create_linear(mem, pl, 8, 1);

  load_checkpoint(pl, "xor.btw");

  for (int epoch = 0; epoch < 500; epoch++) {
    zero_grad(pl);

    Tensor *h = linear_t(mem, l1, x);
    h = relu_t(mem, h);
    Tensor *o = linear_t(mem, l2, h);
    o = sigmoid_t(mem, o);

    Tensor *loss = mseloss_t(mem, o, y);
    backward(mem, loss);

    if (epoch % 100 == 0)
      printf("epoch %3d  loss=%.4f\n", epoch, loss->data[0]);

    sgd_step(pl, 0.1f);
    reset_temp_mem(mem);
  }

  save_checkpoint(pl, "xor.btw");

  free_global_mem(mem);
}

Step 1: Create memory and a parameter list. A single Memory object holds two arenas (permanent and temporary). 1 << 28 = 256 MB per arena. A ParameterList tracks all trainable tensors.

Memory *mem = create_global_mem(1 << 28);
ParameterList *pl = create_param_list(mem);

Step 2: Allocate persistent tensors. Inputs x and y are plain PERM tensors (not trainable). Pass PERM so they survive reset_temp_mem.

Tensor *x = tensor_init(mem, x_shape, 2, PERM);
Tensor *y = tensor_init(mem, y_shape, 2, PERM);

Step 3: Create layers with auto-registration. create_linear allocates weights and bias as PERM tensors and automatically adds them to the parameter list.

Linear *l1 = create_linear(mem, pl, 2, 8);
Linear *l2 = create_linear(mem, pl, 8, 1);
// pl now contains l1->weights, l1->bias, l2->weights, l2->bias

Step 4: Forward pass. Every operation (linear_t, relu_t, sigmoid_t, mseloss_t) allocates its result from the temp arena. No malloc calls, no cleanup code.

Tensor *h = linear_t(mem, l1, x);
h = relu_t(mem, h);
Tensor *o = linear_t(mem, l2, h);
o = sigmoid_t(mem, o);
Tensor *loss = mseloss_t(mem, o, y);

Step 5: Backward pass. backward builds a topological sort of the computation graph (also temp-allocated) and propagates gradients. Intermediate tensor data is still valid at this point.

backward(mem, loss);

Step 6: Zero grads, update, reset. zero_grad clears all parameter gradients. sgd_step applies SGD to all parameters in one call. After gradients are consumed, reset_temp_mem zeroes the temp arena in O(1). All intermediate tensors from the forward pass are gone.

zero_grad(pl);
sgd_step(pl, 0.1f);
reset_temp_mem(mem);

Step 7: Cleanup. free_global_mem releases both arenas and the Memory struct.

free_global_mem(mem);

All allocations go through allocate_mem(mem, size, perm). There are no individual free calls.

Flag Lifetime Used for
PERM Until free_global_mem Weights, biases, persistent inputs/targets
TEMP Until reset_temp_mem Operation results, graph traversal arrays, loss

reset_temp_mem resets the temp arena pointer to 0. It is called after both forward and backward complete, because backward reads intermediate tensor data.

epoch N:  forward (temp fills) β†’ backward (reads temp) β†’ update β†’ reset_temp_mem
epoch N+1: forward (temp fills from 0) β†’ ...

ParameterList is a dynamic array of trainable tensors. It is a typedef of Dt_array allocated in the PERM arena.

ParameterList *pl = create_param_list(mem);

// Manual registration
param_list_add(mem, pl, my_tensor);

// Or automatic β€” create_linear registers weights and bias for you
Linear *l = create_linear(mem, pl, 128, 64);

Once built, the parameter list drives the training loop:

zero_grad(pl);       // zero all parameter gradients
backward(mem, loss); // backprop
sgd_step(pl, 0.01f); // update all parameters
Function Signature Description
create_global_mem (size_t size) β†’ Memory* Allocate perm + temp arenas (e.g., 1<<28 = 256MB each)
reset_temp_mem (Memory *mem) Reset temp arena to empty
allocate_mem (Memory *mem, size_t size, uint8_t perm) β†’ void* Arena allocation
free_global_mem (Memory *mem) Free both arenas and Memory
save_checkpoint (ParameterList *pl, const char *path) Save all parameters to binary file
load_checkpoint (ParameterList *pl, const char *path) Load parameters from binary file
Function Signature Description
create_param_list (Memory *mem) β†’ ParameterList* Create an empty PERM parameter list
param_list_add (Memory *mem, ParameterList *pl, Tensor *t) Add a tensor to the list
zero_grad (ParameterList *pl) Zero gradients for all parameters
sgd_step (ParameterList *pl, float lr) SGD update: data -= lr * grad
clip_gradients (ParameterList *pl, float threshold) Clip gradients to [-threshold, threshold]
adam_init (Memory*, ParameterList*, lr, beta1, beta2, eps, t) β†’ Adam* Initialize Adam optimizer state
adam_step (Adam*, ParameterList*) Adam update step
adamw_init (Memory*, ParameterList*, lr, beta1, beta2, eps, weight_decay, t) β†’ AdamW* Initialize AdamW optimizer state
adamw_step (AdamW*, ParameterList*) AdamW update step
Function Signature Description
tensor_init (Memory*, int *shape, int ndim, uint8_t perm) β†’ Tensor* Zero-initialized tensor
tensor_zeros (Memory*, int *shape, int ndim, uint8_t perm) β†’ Tensor* Same as tensor_init
tensor_ones (Memory*, int *shape, int ndim, uint8_t perm) β†’ Tensor* All ones
tensor_randn (Memory*, int *shape, int ndim, uint8_t perm) β†’ Tensor* Random normal (Box-Muller)
tensor_xavier (Memory*, int *shape, int ndim, uint8_t perm) β†’ Tensor* Xavier initialization
tensor_eye (Memory*, int n, int m, uint8_t perm) β†’ Tensor* Identity matrix (nΓ—n or nΓ—m)
tensor_full_like (Memory*, Tensor *a, float fill_value, uint8_t perm) β†’ Tensor* Same shape as a, filled with fill_value
tensor_zeros_like (Memory*, Tensor *a, uint8_t perm) β†’ Tensor* Same shape as a, filled with zeros
tensor_ones_like (Memory*, Tensor *a, uint8_t perm) β†’ Tensor* Same shape as a, filled with ones
print_t (Tensor*, uint8_t grad) Print tensor (grad=1 to include gradients)
Function Signature Description
add_t (Memory*, Tensor *a, Tensor *b) β†’ Tensor* Element-wise a + b
sub_t (Memory*, Tensor *a, Tensor *b) β†’ Tensor* Element-wise a - b
mul_t (Memory*, Tensor *a, Tensor *b) β†’ Tensor* Element-wise a * b
divide_t (Memory*, Tensor *a, Tensor *b) β†’ Tensor* Element-wise a / b
neg_t (Memory*, Tensor *a) β†’ Tensor* Element-wise -a
pow_t (Memory*, Tensor *a, float exp) β†’ Tensor* Element-wise pow(a, exp)
scale_t (Memory*, Tensor *a, float v) β†’ Tensor* Element-wise a * scalar
Function Signature Description
exp_t (Memory*, Tensor *a) β†’ Tensor* Element-wise exp
log_t (Memory*, Tensor *a) β†’ Tensor* Element-wise log
Function Signature Description
sum_t (Memory*, Tensor *a, int dim) β†’ Tensor* Sum along dimension
mean_t (Memory*, Tensor *a, int dim) β†’ Tensor* Mean along dimension
max_t (Memory*, Tensor *a, int dim) β†’ Tensor* Max along dimension
Function Signature Description
relu_t (Memory*, Tensor *a) β†’ Tensor* ReLU
gelu_t (Memory*, Tensor *a) β†’ Tensor* GELU (tanh approximation)
sigmoid_t (Memory*, Tensor *a) β†’ Tensor* Sigmoid
tanh_t (Memory*, Tensor *a) β†’ Tensor* Tanh
softmax_t (Memory*, Tensor *a, int dim) β†’ Tensor* Softmax along dimension
Function Signature Description
mask_t (Memory*, Tensor *a, Tensor *b, float val) β†’ Tensor* Where b==1, set to val; else keep a
Function Signature Description
matmul_t (Memory*, Tensor *a, Tensor *b) β†’ Tensor* Matrix multiply (2D)
bmm_t (Memory*, Tensor *a, Tensor *b) β†’ Tensor* Batch matrix multiply (3D: [B,T,D] x [B,D,T] β†’ [B,T,T])
transpose_t (Memory*, Tensor *a) β†’ Tensor* Transpose 2D
dot_t (Memory*, Tensor *a, Tensor *b) β†’ Tensor* Dot product (1D)
Function Signature Description
embedding_t (Memory*, Tensor *vocab, Tensor *indices) β†’ Tensor* Look up embeddings (vocab: [V,D], indices: [B,T])
Function Signature Description
reshape_t (Memory*, Tensor *a, int *shape, int ndim) β†’ Tensor* Reshape (shares data)
squeeze_t (Memory*, Tensor *a, int dim) β†’ Tensor* Remove dim of size 1
unsqueeze_t (Memory*, Tensor *a, int dim) β†’ Tensor* Insert dim of size 1
broadcast_t (Memory*, Tensor *a, int *shape, int tar_dim) β†’ Tensor* Broadcast to target shape
permute_t (Memory*, Tensor *a, int *dims, int total_dim) β†’ Tensor* Permute dimensions
concat_t (Memory*, Tensor *a, Tensor *b, int dim) β†’ Tensor* Concatenate along dimension
slice_t (Memory*, Tensor *a, int dim, int split_size) β†’ Pair_T* Split tensor along dim, returns pair {F, S}
Function Signature Description
mseloss_t (Memory*, Tensor *a, Tensor *b) β†’ Tensor* Mean squared error
crossentropyloss_t (Memory*, Tensor *logits, Tensor *targets) β†’ Tensor* Cross-entropy (logits: [N,C], targets: [N])
Function Signature Description
create_linear (Memory*, ParameterList *pl, int d_in, int d_out) β†’ Linear* Linear layer, auto-registers weights + bias
linear_t (Memory*, Linear*, Tensor *x) β†’ Tensor* Forward: x @ W^T + b
create_layernorm (Memory*, ParameterList *pl, int normalized_shape, float eps) β†’ LayerNorm* LayerNorm, auto-registers weight + bias
layernorm_t (Memory*, LayerNorm*, Tensor *x) β†’ Tensor* Layer normalization forward
Function Signature Description
backward (Memory*, Tensor *root) Backpropagate from root

All operations register backward passes and track parent tensors automatically.

── more in #machine-learning 4 stories Β· sorted by recency
── more on @bare-lm 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/pytorch-written-in-c] indexed:0 read:9min 2026-09-22 Β· β€”