Conv
conv.conv2d
tensor.conv2d — NCHW cross-correlation (no kernel flip):
conv2d(input, weight, bias?, stride?, padding?, groups?) with input [N, C_in, H, W] and weight [C_out, C_in/groups, kH, kW]. The output [N, C_out, H_out, W_out] is produced in row-major order; each output pixel's accumulation loop is spelled exactly as the Rust impl's (acc starts at the bias, out-of-range input positions are the implicit zero padding).
Signature: conv.conv2d((input, weight, ...rest))
conv.conv2d_backward
tensor.conv2d_backward — the adjoint, mirroring the host's
Conv2dBackward: conv2d_backward(input, weight, grad_out, bias?, stride?, padding?, groups?) scatters grad_out back through the primal's index arithmetic — dx is the transposed convolution, dw the input–gradient correlation, db the per-channel grad sum (emitted only when bias was given). Same validation order and loop nesting as conv2d.
Signature: conv.conv2d_backward((input, weight, grad_out, ...rest))
conv.max_pool
tensor.max_pool — NCHW max pooling `max_pool(input, kernel,
stride?): input [N, C, H, W], kernel/stride int-or-[h, w](stride defaults to the kernel);out[n, c, oy, ox]is the max of thekH x kWwindow at(oy·sH, ox·sW), trailing partial windows dropped (floor mode). The window max starts at the first cell and folds over the rest (the Rust impl's f64::NEG_INFINITY` seed is equivalent — the kernel always fits, so a first cell always exists).
Signature: conv.max_pool((input, ...rest))
conv.max_pool_backward
tensor.max_pool_backward — the adjoint, mirroring the host's
MaxPoolBackward: max_pool_backward(input, grad_out, kernel, stride?, padding?) scatters each grad element to its window's argmax — re-derived by the same (ky, kx) row-major scan the primal used, so a tie picks the first maximum the same way. Returns {input}.
Signature: conv.max_pool_backward((t, g, kernel, ...rest))