Files
Viper_VRTE/torch/__init__.vp
T
TermiNexus 0055ab7ea7 refactor: rawptr[T] -> T* 与 t.CConst 前缀迁移(R14/R16)
R14:rawptr[X] -> X*(41 文件 / 2037 行改动),X 含模块限定名与嵌套下标(rawptr[GSList[NameEntry]] -> GSList[NameEntry]*)。保留 6 处 rawptr[t.CPtr](规范例外:存纯裸指针);# 注释与字符串里的 rawptr[...] 属元语法描述,未动。 R16(保守子集):t.CConst | str/bytes -> const str/bytes,t.CConst | char | t.CPtr -> const str;typedef 右值位置豁免(const str 在该处无法注册,与 R1 同源)。 验证:TPC 自举三重重编 SUCCESS;TransPyV.exe 全量 77/77 顺序回归通过。
2026-10-05 17:38:21 +08:00

803 lines
31 KiB
Plaintext

# -*- coding: utf-8 -*-
# ============================================================
# torch-lite: 仿 PyTorch 的极简张量/神经网络库(Viper)
#
# 设计原则:
# * "足够用就行": 只覆盖 MNIST 两层 MLP 训练/推理所需的原语。
# * API 命名贴近 PyTorch: Tensor / linear_fwd(bias 融合) / relu /
# relu_backward / softmax_crossentropy(前向+梯度一体) / sgd_step。
# * 张量为 2D 稠密 double (rows x cols, 行主序), 权重形状同 PyTorch:
# Linear(in, out) -> weight (out, in) / bias (out,), forward = x @ W^T + b。
# * 计算热点结合 vipersimd:
# - linear_fwd 内积按 k%4 用 simd_dot4d 累加(纯 AVX2 点积);
# - relu_fwd 用 simd_max4d(x, 0) 四路取大;
# - 向量乘标量用 simd_mul_scalar4d(归一化 /255 等)。
# backward / softmax / sgd 等按标量写(数据量小, 可读性优先)。
# * 内存: 所有 Tensor 及 data/grad 都从调用方传入的 memhub.MemBuddy
# 分配(与 includes 既有约定一致, pool.alloc 免手动 free)。
# ============================================================
from stdint import *
import t, c
import stdlib
import string
import stdio
import math
import vipersimd
import memhub
# ------------------------------------------------------------
# Tensor
# ------------------------------------------------------------
novirtual class Tensor:
Data: t.CPtr # rows*cols*8 contiguous bytes of double
Grad: t.CPtr # same-shape gradient (may be None)
Rows: sizet
Cols: sizet
# Fields Data/Grad/Rows/Cols = 32 bytes (no vtable)
define TENSOR_SIZE = 32
def _zeros_block(pool: memhub.MemBuddy*, bytes_n: sizet) -> t.CPtr:
"""Allocate and zero a block of memory"""
p: t.CPtr = pool.alloc(bytes_n)
if p is not None:
string.memset(p, 0, bytes_n)
return p
def zeros(pool: memhub.MemBuddy*, rows: sizet,
cols: sizet, with_grad: int = 1) -> Tensor*:
"""torch.zeros: create a rows x cols all-zero Tensor, optionally allocating a Grad buffer"""
if pool is None:
return None
t0: Tensor* = pool.alloc(TENSOR_SIZE)
if t0 is None:
return None
string.memset(t0, 0, TENSOR_SIZE)
nb: sizet = rows * cols * sizet(8)
t0.Data = _zeros_block(pool, nb)
if t0.Data is None:
return None
if with_grad != 0:
t0.Grad = _zeros_block(pool, nb)
t0.Rows = rows
t0.Cols = cols
return t0
def from_raw(pool: memhub.MemBuddy*, data: t.CPtr,
rows: sizet, cols: sizet,
with_grad: int = 0) -> Tensor*:
"""torch.from_raw: wrap existing memory as a Tensor (no copy, not owned)"""
if pool is None:
return None
t0: Tensor* = pool.alloc(TENSOR_SIZE)
if t0 is None:
return None
string.memset(t0, 0, TENSOR_SIZE)
t0.Data = data
t0.Grad = None
t0.Rows = rows
t0.Cols = cols
return t0
def tensor_free(t0: Tensor*):
"""Free the Tensor shell (data is reclaimed by the MemBuddy pool; no manual free needed)"""
pass
def tensor_data(t0: Tensor*) -> t.CPtr:
if t0 is None:
return None
return t0.Data
def tensor_grad(t0: Tensor*) -> t.CPtr:
if t0 is None:
return None
return t0.Grad
def tensor_rows(t0: Tensor*) -> sizet:
if t0 is None:
return 0
return t0.Rows
def tensor_cols(t0: Tensor*) -> sizet:
if t0 is None:
return 0
return t0.Cols
def fill(t0: Tensor*, val: double):
"""torch.fill: set all elements to val"""
if t0 is None or t0.Data is None:
return
d: double* = t0.Data
n: sizet = t0.Rows * t0.Cols
for i in range(n):
d[i] = val
def zero_grad(t0: Tensor*):
"""torch.zero_grad: zero the Grad buffer"""
if t0 is None or t0.Grad is None:
return
string.memset(t0.Grad, 0, t0.Rows * t0.Cols * sizet(8))
# ------------------------------------------------------------
# Deterministic pseudo-random (LCG, minstd) and Kaiming uniform init
# ------------------------------------------------------------
_rng_state: double = 12345.0
LCG_M: double = 2147483647.0
LCG_A: double = 48271.0
def manual_seed(seed: int64):
global _rng_state
_rng_state = double(seed % int64(2147483647))
if _rng_state <= double(0.0):
_rng_state = double(12345.0)
def _next_rand() -> double:
"""(0,1) uniform random"""
global _rng_state
_rng_state = math.fmod(_rng_state * LCG_A, LCG_M)
return _rng_state / LCG_M
def uniform_fill(t0: Tensor*, lo: double, hi: double):
"""Fill with U(lo, hi)"""
if t0 is None or t0.Data is None:
return
d: double* = t0.Data
n: sizet = t0.Rows * t0.Cols
span: double = hi - lo
for i in range(n):
d[i] = lo + _next_rand() * span
def kaiming_uniform_fill(w: Tensor*, fan_in: sizet,
fan_out: sizet):
"""PyTorch Linear default init: U(-sqrt(6/(fan_in+fan_out)), +...)"""
bound: double = math.sqrt(
double(6.0) / double(fan_in + fan_out))
uniform_fill(w, -bound, bound)
# ------------------------------------------------------------
# Basic element-wise ops (using vipersimd 4-wide instructions)
# ------------------------------------------------------------
def vector_mul_scalar4(src: t.CPtr, scalar_ptr: t.CPtr, dst: t.CPtr,
n: sizet):
"""dst = src * scalar (4-wide simd_mul_scalar4d + scalar tail)"""
if src is None or scalar_ptr is None or dst is None:
return
i: sizet = 0
while i + 4 <= n:
vipersimd.simd_mul_scalar4d(
t.CPtr(uint64(src) + uint64(i) * 8),
scalar_ptr,
t.CPtr(uint64(dst) + uint64(i) * 8))
i += 4
sd: double* = src
dd: double* = dst
# Scalar tail
sp: double* = scalar_ptr
sval: double = sp[0]
while i < n:
dd[i] = sd[i] * sval
i += 1
def vector_add_inplace(a: t.CPtr, b: t.CPtr, n: sizet):
"""a += b (4-wide simd_add_array + scalar tail)"""
if a is None or b is None:
return
tmp: t.CPtr = stdlib.malloc(8)
if tmp is None:
return
i: sizet = 0
while i + 4 <= n:
vipersimd.simd_add_array(
t.CPtr(uint64(a) + uint64(i) * 8),
t.CPtr(uint64(b) + uint64(i) * 8),
t.CPtr(uint64(a) + uint64(i) * 8))
i += 4
da: double* = a
db: double* = b
while i < n:
da[i] = da[i] + db[i]
i += 1
stdlib.free(tmp)
# ------------------------------------------------------------
# Linear layer: forward = x @ W^T + b (W: out x in, x: m x in, out: m x out)
# Inner product in k%4 chunks via vipersimd.simd_dot4d -- the MNIST compute hotspot
# ------------------------------------------------------------
def linear_fwd(w: Tensor*, b: Tensor*,
x: Tensor*, out: Tensor*):
if w is None or b is None or x is None or out is None:
return
m: sizet = x.Rows
k: sizet = x.Cols
n: sizet = w.Rows
wd: double* = w.Data
bd: double* = b.Data
xd: double* = x.Data
od: double* = out.Data
acc_buf: t.CPtr = stdlib.malloc(8)
if acc_buf is None:
return
accp: double* = acc_buf
for i in range(m):
for j in range(n):
acc: double = bd[j]
kk: sizet = 0
while kk + 4 <= k:
vipersimd.simd_dot4d(
t.CPtr(uint64(xd) + uint64(i * k + kk) * 8),
t.CPtr(uint64(wd) + uint64(j * k + kk) * 8),
acc_buf)
acc = acc + accp[0]
kk += 4
while kk < k:
acc = acc + xd[i * k + kk] * wd[j * k + kk]
kk += 1
od[i * n + j] = acc
stdlib.free(acc_buf)
# ------------------------------------------------------------
# Linear backward: inputs w(out,in) x(m,in) g(m,out)
# dw[j,i] += sum_b g[b,j]*x[b,i] ; db[j] += sum_b g[b,j]
# dx[b,i] = sum_j g[b,j]*w[j,i]
# Note: dw/db use accumulate semantics (zero Grad before the call)
# ------------------------------------------------------------
def linear_bwd(w: Tensor*, x: Tensor*,
g: Tensor*, dw: Tensor*,
db: Tensor*, dx: Tensor*,
accum: int = 0):
if w is None or x is None or g is None:
return
if dw is None or db is None or dx is None:
return
m: sizet = x.Rows
k: sizet = x.Cols
n: sizet = w.Rows
wd: double* = w.Data
xd: double* = x.Data
gd: double* = g.Data
dwd: double* = dw.Data
dbd: double* = db.Data
dxd: double* = dx.Data
# Zero dw/db/dx (dx is always zeroed: it is this sample's input gradient, not accumulated across samples)
# accum=1 keeps dw/db -- for callers accumulating whole-batch parameter gradients sample by sample
if accum == 0:
string.memset(dw.Data, 0, n * k * sizet(8))
string.memset(db.Data, 0, n * sizet(8))
string.memset(dx.Data, 0, m * k * sizet(8))
for b in range(m):
for j in range(n):
gj: double = gd[b * n + j]
if gj != double(0.0):
dbd[j] = dbd[j] + gj
for i in range(k):
dwd[j * k + i] = dwd[j * k + i] + gj * xd[b * k + i]
dxd[b * k + i] = dxd[b * k + i] + gj * wd[j * k + i]
# ------------------------------------------------------------
# ReLU (simd_max4d version) and backward
# ------------------------------------------------------------
def relu_fwd(x: Tensor*, out: Tensor*):
if x is None or out is None:
return
n: sizet = x.Rows * x.Cols
zero4: t.CPtr = stdlib.malloc(32)
if zero4 is None:
return
string.memset(zero4, 0, 32)
xd: double* = x.Data
od: double* = out.Data
i: sizet = 0
while i + 4 <= n:
vipersimd.simd_max4d(
t.CPtr(uint64(xd) + uint64(i) * 8),
zero4,
t.CPtr(uint64(od) + uint64(i) * 8))
i += 4
while i < n:
if xd[i] > double(0.0):
od[i] = xd[i]
else:
od[i] = double(0.0)
i += 1
stdlib.free(zero4)
def relu_backward(x: Tensor*, g: Tensor*,
gx: Tensor*):
"""gx = g * (x > 0)"""
if x is None or g is None or gx is None:
return
n: sizet = x.Rows * x.Cols
xd: double* = x.Data
gd: double* = g.Data
od: double* = gx.Data
for i in range(n):
if xd[i] > double(0.0):
od[i] = gd[i]
else:
od[i] = double(0.0)
# ------------------------------------------------------------
# softmax + CrossEntropy (fused, numerically stable) -- MNIST output layer
# logits: (m, C), labels: m ints (0..C-1)
# loss written to *loss, gradient (normalized by 1/m) written to grad (m, C)
# ------------------------------------------------------------
def softmax_crossentropy(logits: Tensor*,
labels: int*,
n_labels: sizet,
loss: double*,
grad: Tensor*):
if logits is None or labels is None or grad is None:
return
if loss is None:
return
m: sizet = logits.Rows
c: sizet = logits.Cols
ld: double* = logits.Data
gd: double* = grad.Data
lb: int* = labels
total: double = double(0.0)
inv_m: double = double(1.0) / double(m)
for b in range(m):
# Numerically stable softmax
mx: double = ld[b * c]
j: sizet = 1
while j < c:
if ld[b * c + j] > mx:
mx = ld[b * c + j]
j += 1
s: double = double(0.0)
j = 0
while j < c:
s = s + math.exp(ld[b * c + j] - mx)
j += 1
y: int = lb[b]
# loss += -(logits[lab] - max - log(s))
total = total - (ld[b * c + sizet(y)] - mx - math.log(s))
# Gradient: (softmax - onehot) / m
j = 0
while j < c:
p: double = math.exp(ld[b * c + j] - mx) / s
o: double = double(0.0)
if sizet(y) == j:
o = double(1.0)
gd[b * c + j] = (p - o) * inv_m
j += 1
loss[0] = total
# ------------------------------------------------------------
# Per-row argmax (classification prediction)
# ------------------------------------------------------------
def row_argmax(x: Tensor*, pred: int*):
if x is None or pred is None:
return
m: sizet = x.Rows
c: sizet = x.Cols
d: double* = x.Data
for i in range(m):
best: sizet = 0
bv: double = d[i * c]
j: sizet = 1
while j < c:
if d[i * c + j] > bv:
bv = d[i * c + j]
best = j
j += 1
pred[i] = int(best)
# ------------------------------------------------------------
# SGD: data -= lr * grad (full gradient) / momentum: v = m*v+g, data -= lr*v
# ------------------------------------------------------------
def sgd_step(param: Tensor*, lr: double):
if param is None or param.Data is None or param.Grad is None:
return
d: double* = param.Data
g: double* = param.Grad
n: sizet = param.Rows * param.Cols
for i in range(n):
d[i] = d[i] - lr * g[i]
def sgd_step_momentum(param: Tensor*, vel: t.CPtr,
lr: double, momentum: double):
"""SGD with momentum: v = m*v + g ; w -= lr*v
vel is a caller-allocated, zeroed double buffer with the same element count as param (held across steps); pass None to fall back to plain sgd_step."""
if param is None or param.Data is None or param.Grad is None:
return
if vel is None:
sgd_step(param, lr)
return
d: double* = param.Data
g: double* = param.Grad
v: double* = vel
n: sizet = param.Rows * param.Cols
for i in range(n):
vv: double = momentum * v[i] + g[i]
v[i] = vv
d[i] = d[i] - lr * vv
# ------------------------------------------------------------
# Element-wise multiply-add (for debug/extension, vipersimd fma_array)
# a = a*b + c
# ------------------------------------------------------------
def fma_array_inplace(a: Tensor*, b: t.CPtr, c: t.CPtr):
if a is None or a.Data is None:
return
vipersimd.simd_fma_array(a.Data, b, c, a.Data, a.Rows * a.Cols)
# ------------------------------------------------------------
# Conv2d / MaxPool2d raw-buffer kernels for a lightweight CNN (NCHW contiguous double, weights [cout][cin][k][k]).
# Conventions: *_fwd overwrites output; conv2d_bwd_dx/maxpool2d_bwd zero then accumulate (per-sample dx); conv2d_bwd_dw accumulates params (+=, caller zeroes per batch).
# SIMD inner loop over output columns, safe range ox in [pad, win+pad-k-3] when stride==1 (maxpool2d is scalar only).
# ------------------------------------------------------------
def _dptr(base: t.CPtr, elem: sizet) -> t.CPtr:
"""Convert an element index into a byte address for a raw double buffer.
Raw t.CPtr is i8* and `p + n` is a byte offset, so the index must be multiplied by 8 for vipersimd 4-wide (32-byte) access."""
return t.CPtr(uint64(base) + uint64(elem) * 8)
def conv2d_fwd(x: t.CPtr, w: t.CPtr, b: t.CPtr, out: t.CPtr,
n: sizet, cin: sizet, hin: sizet, win: sizet,
cout: sizet, k: sizet, stride: sizet,
pad: sizet):
"""out[n][cout][hout][wout] = conv2d(x, w) + b
hout = (hin + 2*pad - k)/stride + 1 (same for wout; integer division floors)."""
if x is None or w is None or out is None:
return
hout: sizet = (hin + pad * 2 - k) / stride + 1
wout: sizet = (win + pad * 2 - k) / stride + 1
xd: double* = x
wd: double* = w
od: double* = out
kk: sizet = cin * k * k
# SIMD safe range (see file-header notes)
simd_on: int = 0
smax: sizet = 0
if stride == 1 and win + pad >= k + 3:
simd_on = 1
smax = win + pad - (k + 3)
wb: t.CPtr = None # 4-lane broadcast copy of weight scalars (kk scalars)
t4: t.CPtr = None # 4-lane accumulator
if simd_on != 0:
wb = stdlib.malloc(kk * 32)
t4 = stdlib.malloc(32)
if wb is None or t4 is None:
if wb is not None:
stdlib.free(wb)
if t4 is not None:
stdlib.free(t4)
wb = None
t4 = None
simd_on = 0
wbd: double* = wb
t4d: double* = t4
for bi in range(n):
for co in range(cout):
bias: double = double(0.0)
if b is not None:
bd: double* = b
bias = bd[co]
# Pre-broadcast weight scalars: one 4-lane copy per (ci,ky,kx), shared by all output rows/cols of this channel.
# Otherwise vbroadcastsd (4 stores) repeats in the 4-lane loop and halves the SIMD gain.
if simd_on != 0:
for wb4 in range(kk):
wq: double = wd[co * kk + wb4]
wbd[wb4 * 4] = wq
wbd[wb4 * 4 + 1] = wq
wbd[wb4 * 4 + 2] = wq
wbd[wb4 * 4 + 3] = wq
for oy in range(hout):
oy0: sizet = oy * stride
ox: sizet = 0
while ox < wout:
if simd_on != 0 and ox >= pad and ox <= smax and ox + 4 <= wout:
# ---- 4-lane: compute 4 adjacent output columns at once ----
t4d[0] = bias
t4d[1] = bias
t4d[2] = bias
t4d[3] = bias
for ci in range(cin):
for ky in range(k):
iy: sizet = oy0 + ky
if iy >= pad:
yy: sizet = iy - pad
if yy < hin:
xrow: sizet = ((bi * cin + ci) * hin + yy) * win
for kx in range(k):
vipersimd.simd_fma4d(
_dptr(x, xrow + ox + kx - pad),
_dptr(wb, ((ci * k + ky) * k + kx) * 4),
t4, t4)
ob: sizet = ((bi * cout + co) * hout + oy) * wout + ox
od[ob] = t4d[0]
od[ob + 1] = t4d[1]
od[ob + 2] = t4d[2]
od[ob + 3] = t4d[3]
ox += 4
else:
# ---- scalar: boundary columns / stride != 1 ----
ox0: sizet = ox * stride
acc: double = bias
for ci2 in range(cin):
for ky2 in range(k):
iy2: sizet = oy0 + ky2
if iy2 >= pad:
yy2: sizet = iy2 - pad
if yy2 < hin:
for kx2 in range(k):
ix2: sizet = ox0 + kx2
if ix2 >= pad:
xx2: sizet = ix2 - pad
if xx2 < win:
acc = acc + \
wd[co * kk + (ci2 * k + ky2) * k + kx2] * \
xd[((bi * cin + ci2) * hin + yy2) * win + xx2]
od[((bi * cout + co) * hout + oy) * wout + ox] = acc
ox += 1
if wb is not None:
stdlib.free(wb)
if t4 is not None:
stdlib.free(t4)
def conv2d_bwd_dw(x: t.CPtr, dout: t.CPtr, dw: t.CPtr, db: t.CPtr,
n: sizet, cin: sizet, hin: sizet, win: sizet,
cout: sizet, k: sizet, stride: sizet,
pad: sizet):
"""Accumulate: dw[co][ci][ky][kx] += sum dout*x ; db[co] += sum dout (+= semantics, caller zeroes before the batch).
Loop order (ci,ky,kx) outer, (oy,ox) inner: one read-modify-write per weight; inner loop 4-lane SIMD, then hsum to scalar."""
if x is None or dout is None or dw is None:
return
hout: sizet = (hin + pad * 2 - k) / stride + 1
wout: sizet = (win + pad * 2 - k) / stride + 1
xd: double* = x
gd: double* = dout
dwd: double* = dw
dbd: double* = None
if db is not None:
dbd = db
kk: sizet = cin * k * k
simd_on: int = 0
smax: sizet = 0
if stride == 1 and win + pad >= k + 3:
simd_on = 1
smax = win + pad - (k + 3)
acc: t.CPtr = None
if simd_on != 0:
acc = stdlib.malloc(32)
if acc is None:
simd_on = 0
accd: double* = acc
for bi in range(n):
for co in range(cout):
gp: sizet = (bi * cout + co) * hout * wout
# ---- db[co] += sum(g): the whole channel plane of g is contiguous ----
if dbd is not None:
tot0: double = double(0.0)
if simd_on != 0:
accd[0] = double(0.0)
accd[1] = double(0.0)
accd[2] = double(0.0)
accd[3] = double(0.0)
gcnt: sizet = hout * wout
gof: sizet = 0
while gof + 4 <= gcnt:
if simd_on != 0:
vipersimd.simd_add4d(_dptr(dout, gp + gof), acc, acc)
else:
tot0 = tot0 + gd[gp + gof]
tot0 = tot0 + gd[gp + gof + 1]
tot0 = tot0 + gd[gp + gof + 2]
tot0 = tot0 + gd[gp + gof + 3]
gof += 4
while gof < gcnt:
tot0 = tot0 + gd[gp + gof]
gof += 1
if simd_on != 0:
vipersimd.simd_hsum4d(acc, acc)
tot0 = tot0 + accd[0]
dbd[co] = dbd[co] + tot0
# ---- dw[co][ci][ky][kx] ----
for ci in range(cin):
for ky in range(k):
for kx in range(k):
tot: double = double(0.0)
if simd_on != 0:
accd[0] = double(0.0)
accd[1] = double(0.0)
accd[2] = double(0.0)
accd[3] = double(0.0)
for oy in range(hout):
iy: sizet = oy * stride + ky
if iy >= pad:
yy: sizet = iy - pad
if yy < hin:
xrow: sizet = ((bi * cin + ci) * hin + yy) * win
goff: sizet = gp + oy * wout
ox: sizet = 0
while ox < wout:
if simd_on != 0 and ox >= pad and ox <= smax and ox + 4 <= wout:
vipersimd.simd_fma4d(
_dptr(dout, goff + ox),
_dptr(x, xrow + ox + kx - pad),
acc, acc)
ox += 4
else:
ix: sizet = ox * stride + kx
if ix >= pad:
xx: sizet = ix - pad
if xx < win:
tot = tot + gd[goff + ox] * \
xd[xrow + xx]
ox += 1
if simd_on != 0:
vipersimd.simd_hsum4d(acc, acc)
tot = tot + accd[0]
wj: sizet = co * kk + (ci * k + ky) * k + kx
dwd[wj] = dwd[wj] + tot
if acc is not None:
stdlib.free(acc)
def conv2d_bwd_dx(w: t.CPtr, dout: t.CPtr, dx: t.CPtr,
n: sizet, cin: sizet, hin: sizet, win: sizet,
cout: sizet, k: sizet, stride: sizet,
pad: sizet):
"""dx is zeroed then accumulated (per-sample input gradient): dx[ci][iy][ix] += sum dout*w.
With stride==1 the inner loop is 4-lane SIMD; the lanes map to contiguous dx cells, so dx can be updated in place with fma(g4 * w4 + dx4)."""
if w is None or dout is None or dx is None:
return
hout: sizet = (hin + pad * 2 - k) / stride + 1
wout: sizet = (win + pad * 2 - k) / stride + 1
wd: double* = w
gd: double* = dout
dxd: double* = dx
string.memset(dx, 0, n * cin * hin * win * sizet(8))
kk: sizet = cin * k * k
simd_on: int = 0
smax: sizet = 0
if stride == 1 and win + pad >= k + 3:
simd_on = 1
smax = win + pad - (k + 3)
wb: t.CPtr = None # 4-lane broadcast copy of the whole weight tensor (cout*kk scalars)
if simd_on != 0:
wb = stdlib.malloc(cout * kk * 32)
if wb is None:
simd_on = 0
wbd: double* = wb
if simd_on != 0:
# Weights are constant within one call, so pre-broadcast once (do not repeat vbroadcastsd in the 4-lane loop: 4 stores halves the SIMD gain)
wjn: sizet = cout * kk
for wj4 in range(wjn):
wq2: double = wd[wj4]
wbd[wj4 * 4] = wq2
wbd[wj4 * 4 + 1] = wq2
wbd[wj4 * 4 + 2] = wq2
wbd[wj4 * 4 + 3] = wq2
for bi in range(n):
for ci in range(cin):
for ky in range(k):
for oy in range(hout):
iy: sizet = oy * stride + ky
if iy >= pad:
yy: sizet = iy - pad
if yy < hin:
dxrow: sizet = ((bi * cin + ci) * hin + yy) * win
ox: sizet = 0
while ox < wout:
if simd_on != 0 and ox >= pad and ox <= smax and ox + 4 <= wout:
for co in range(cout):
for kx in range(k):
goff: sizet = ((bi * cout + co) * hout + oy) * wout + ox
doff: sizet = dxrow + ox + kx - pad
vipersimd.simd_fma4d(
_dptr(dout, goff),
_dptr(wb, (co * kk + (ci * k + ky) * k + kx) * 4),
_dptr(dx, doff), _dptr(dx, doff))
ox += 4
else:
for co2 in range(cout):
for kx2 in range(k):
ix: sizet = ox * stride + kx2
if ix >= pad:
xx: sizet = ix - pad
if xx < win:
xi: sizet = dxrow + xx
gi: sizet = ((bi * cout + co2) * hout + oy) * wout + ox
dxd[xi] = dxd[xi] + \
gd[gi] * wd[co2 * kk + (ci * k + ky) * k + kx2]
ox += 1
if wb is not None:
stdlib.free(wb)
# ------------------------------------------------------------
# MaxPool2d (k x k, stride usually = k)
# ------------------------------------------------------------
def maxpool2d_fwd(x: t.CPtr, out: t.CPtr, argmax: t.CPtr,
n: sizet, c: sizet, hin: sizet, win: sizet,
k: sizet, stride: sizet):
"""argmax: int array [n*c*hout*wout] recording the max index within the channel (yy*win+xx)"""
if x is None or out is None or argmax is None:
return
hout: sizet = (hin - k) / stride + 1
wout: sizet = (win - k) / stride + 1
xd: double* = x
od: double* = out
am: int* = argmax
for bi in range(n):
for ch in range(c):
plano: sizet = (bi * c + ch) * hin * win
for oy in range(hout):
oy0: sizet = oy * stride
for ox in range(wout):
ox0: sizet = ox * stride
best: double = double(0.0)
bidx: int = 0
first: int = 1
for ky in range(k):
yy: sizet = oy0 + ky
for kx in range(k):
xx: sizet = ox0 + kx
v: double = xd[plano + yy * win + xx]
if first != 0 or v > best:
best = v
bidx = int(yy * win + xx)
first = 0
oi: sizet = (bi * c + ch) * hout * wout + oy * wout + ox
od[oi] = best
am[oi] = bidx
def maxpool2d_bwd(dout: t.CPtr, argmax: t.CPtr, dx: t.CPtr,
n: sizet, c: sizet, hin: sizet, win: sizet,
k: sizet, stride: sizet):
"""dx is zeroed then scatter-accumulated: dx[argmax] += dout"""
if dout is None or argmax is None or dx is None:
return
hout: sizet = (hin - k) / stride + 1
wout: sizet = (win - k) / stride + 1
gd: double* = dout
am: int* = argmax
dxd: double* = dx
string.memset(dx, 0, n * c * hin * win * sizet(8))
for bi in range(n):
for ch in range(c):
plano: sizet = (bi * c + ch) * hin * win
for oy in range(hout):
for ox in range(wout):
oi: sizet = (bi * c + ch) * hout * wout + oy * wout + ox
tgt: sizet = plano + sizet(am[oi])
dxd[tgt] = dxd[tgt] + gd[oi]