Tensor Logic
Source: source/classes/tensorlogic.prg, source/function/fwtensor.c
FiveWin implements Pedro Domingos' Tensor Logic: The Language of AI (arXiv:2510.12269). Its key insight: a logical rule and an Einstein summation are the same operation. A program is just a set of tensor equations; each equation joins the tensors on its right-hand side (product over shared indices), projects out the indices that do not appear on the left-hand side (summation), and optionally applies a nonlinearity. One engine therefore runs both neural networks (attention, MLPs) and symbolic reasoning (Datalog over DBF tables) — and can differentiate any program automatically.
Y[i] = f( W[i,j] * X[j] )"] --> N[Neural: attention, MLP, transformer] EQ --> S[Symbolic: Datalog rules over DBFs] EQ --> G["Autodiff: gradient =
same einsum, factors swapped"]
Quick start
- Build the FWH library for your compiler variant (e.g.
mfwh_new.bat hb32) sotensorlogic.prgandfwtensor.care in the lib. - From
samples\ai, build and run the tutorial companion:
Output lands incd /d C:\fwteam\samples\ai C:\fwteam\samples\build_new.bat tltutor hb32 tltutor.exetltutor.log. Walk it side by side with the step-by-step tutorial. - Optional speed: put
libopenblas.dllon the PATH or next to the exe (or setFWT_BLAS_DLL). Check with? FWT_BlasActive().
Tensor equations
oTL := TTensorLogic():New()
oTL:Let( "W", { { 1, -1, 0 }, { 0, 1, -1 } } ) // bind tensors (arrays or FW_Tensor)
oTL:Let( "X", { 1, 2, 0.5 } )
oTL:Let( "B", { 2, -2 } )
oTL:Eq( "Y[i] = step( W[i,j] * X[j] + B[i] )" ) // a perceptron, one equation
oTL:Run() // forward chaining to fixpoint
? oTL:GetArray( "Y" ) // {1, 0}
Indices repeated on the right are joined; indices missing from the left are summed out.
Supported nonlinearities: step, sigmoid, relu, tanh,
gelu, softmax (last index), lnorm (layer norm, last index, no affine
part — express gamma/beta as further equations). Numeric factors scale a term:
"S[p,q] = Q[p,k] * K[q,k] * 0.7071".
Equation syntax
| Form | Meaning |
|---|---|
Out[i,k] = A[i,j] * B[j,k] | Join on j, project j (matrix product). |
Y[i] = step( W[i,j] * X[j] + B[i] ) | Several additive terms + nonlinearity. |
S[p,q] = Q[p,d] * K[q,d] * 0.7071 | Numeric scale factor on a term. |
A[p,q.] = softmax( S[p,q] ) | Trailing . marks the softmax axis. |
N[p,d] = lnorm( R[p,d] ) | Layer norm over the last index. |
N2[p,d] = N[p,d] * G[d] + Ones[p] * B[d] | Affine gamma/beta after lnorm. |
Terms with any number of factors are evaluated by chaining pairwise contractions: only the letters still needed for the final output survive each step, so every pairwise step can take the OpenBLAS GEMM fast path.
On parse failure, Eq() returns .F. and sets oTL:cError.
TTensorLogic
| Method | Description |
|---|---|
New() | Create an empty program (tensors, equations, Adam state). |
Let( cName, uData ) | Bind a tensor (nested array of any rank or FW_Tensor). |
Param( cName, uData ) | Let + mark as learnable (updated by Step() / StepAdam()). |
Eq( cEquation ) | Add a tensor equation (string form). Returns .F. on parse error. |
Run( nMaxPasses, nTol ) | Forward chaining until fixpoint (default 100 passes, tol 1e-6). Recursive Datalog rules converge. |
Forward() | Single pass in declaration order; caches pre-activations for Backward(). Use this for training, not Run(). |
ZeroGrad() | Clear all accumulated gradients. |
Seed( cName, tGrad ) | Set dL/dName (gradient of the loss w.r.t. an output tensor). |
Backward() | Reverse sweep over equations. Gradient of an equation = same einsum with factor and output swapped. |
GetGrad( cName ) | Return dL/dName as FW_Tensor. |
Step( nLr ) | SGD: each Param tensor -= lr * grad. |
StepAdam( nLr, nB1, nB2, nEps ) | Adam optimizer (C kernel FWT_AdamUpdate with bias correction). Defaults: β1=0.9, β2=0.999, ε=1e-8. |
ClipGrads( nMaxNorm ) | Global L2 gradient clipping. Returns the pre-clip norm. |
ClearActGrads() | Drop activation grads, keep param grads (mini-batch accumulation). |
SaveParams( cFile, hUser ) / LoadParams( cFile ) | Checkpoint every Param() tensor as raw float32 plus an optional user hash (vocab, config). Load returns the user hash or NIL on error. |
Get( cName ) / GetArray( cName ) | Result tensor / nested Harbour array (2D). |
LetDbf( cName, cAlias, aFields, oDomain ) | DBF records → boolean relation tensor (one dimension per field). |
LetSet( cName, aEntities, oDomain ) | List of names → unary boolean tensor. |
Query( cName, oDomain, nThresh ) | Result tensor → tuples of entity names (default thresh 0.5). |
DATA: hTensors, aEqs, cError, hPre, hGrads,
aParams, hAdamM, hAdamV, nAdamT.
Training loop patterns
// Single-sample / full-batch
oTL:Forward()
oTL:ZeroGrad()
oTL:Seed( "Y", FWT_Scale( FWT_Sub( oTL:Get( "Y" ), tTarget ), 2 ) ) // d MSE
oTL:Backward()
oTL:ClipGrads( 1.0 )
oTL:StepAdam( 0.05 )
// Mini-batch accumulation (TTransformerTL style)
oTL:ZeroGrad()
for i := 1 to nBatch
oT:AccumStep( aIds[i], aTgt[i], 1 / nBatch ) // param grads add up
next
oTL:ClipGrads( 1.0 )
oTL:StepAdam( nLr )
TTLDomain
Shared entity ↔ index map used by all relations so tensor dimensions match.
| Method | Description |
|---|---|
New() | Empty domain. |
Add( cName ) | Register name if new; return 1-based id. |
Id( cName ) | Id or 0 if unknown. |
Name( nId ) | Name for id. |
Len() | Number of entities. |
Harvest( cAlias, aFields ) | Register every value of those fields in the open alias. Call before LetDbf(). |
TTransformerTL
GPT-style transformer facade over a tensor logic program (~15 equations per layer). Heads are an
index (no column slicing): WQ[d,h,k] absorbs the per-head split and
WO[h,k,d] absorbs concat+Wo. With identical weights it reproduces the classic
Transformer class ForwardSeq() closely, and it trains and
generates through the generic autodiff — zero transformer-specific backprop code.
| Method | Description |
|---|---|
New( nLayers, dModel, nHeads, nVocab ) | Build program + init params (Xavier-style). Defaults: 1, 8, 2, 4. |
BuildProgram() | Emit residual stream, multi-head attention, FFN, vocab projection as equations. |
InitParams() | Random (reproducible) Param() tensors for all weights. |
LoadFromTransformer( oT ) | Copy weights from classic Transformer for parity tests. |
SetInput( aIds ) | One-hot X, causal Mask, Pos, ones vectors. |
ForwardSeq( aIds ) | → nested array of probs [seq][vocab]. |
TrainStep( aIds, aTargets, nLr ) | CE loss step (ZeroGrad + Backward + Step). Returns loss. |
AccumStep( aIds, aTargets, nScale ) | Accumulate grads only (for mini-batches). Returns scaled loss. |
EvalLoss( aIds, aTargets ) | Forward-only CE (validation). |
Generate( aSeed, nNew ) | Greedy argmax generation of nNew tokens. |
oTLT := TTransformerTL():New( 2, 8, 2, nVocab )
aProbs := oTLT:ForwardSeq( { 2, 3, 4, 5, 6 } )
nLoss := oTLT:TrainStep( aIds, aTargets, 0.01 )
aOut := oTLT:Generate( { 1 }, 8 )
// multi-batch:
oTLT:oTL:ZeroGrad()
for i := 1 to nBatch
oTLT:AccumStep( aWin[i], aTgt[i], 1 / nBatch )
next
oTLT:oTL:ClipGrads( 1.0 )
oTLT:oTL:StepAdam( nLr )
Attention skeleton as equations (from BuildProgram()):
oTL:Eq( "Q[h,p,k] = H0[p,d] * WQ[d,h,k]" )
oTL:Eq( "S[h,p,q] = Q[h,p,k] * K[h,q,k] * 0.5 + OnesH[h] * Mask[p,q]" )
oTL:Eq( "A[h,p,q.] = softmax( S[h,p,q] )" )
oTL:Eq( "O[h,p,k] = A[h,p,q] * V[h,q,k]" )
oTL:Eq( "Att[p,d] = O[h,p,k] * WO[h,k,d]" )
oTL:Eq( "N1[p,d] = lnorm( Att[p,d] + H0[p,d] )" ) // residual + layer norm
// ... FFN, second norm, vocabulary projection, softmax
Datalog over DBF tables
USE tlfam ALIAS FAM NEW // fields PARENT, CHILD
oDom := TTLDomain():New()
oDom:Harvest( "FAM", { "PARENT", "CHILD" } ) // fix the domain first
oTL:LetDbf( "Parent", "FAM", { "PARENT", "CHILD" }, oDom )
oTL:LetSet( "Male", { "abe", "homer" }, oDom )
oTL:Let( "NotEq", TL_NotEq( oDom:Len() ) )
oTL:Let( "Ancestor", FWT_New( { oDom:Len(), oDom:Len() } ) )
oTL:Eq( "Father[x,y] = step( Parent[x,y] * Male[x] )" )
oTL:Eq( "Sibling[x,y] = step( Parent[p,x] * Parent[p,y] * NotEq[x,y] )" )
oTL:Eq( "Ancestor[x,y] = step( Parent[x,y] + Ancestor[x,z] * Parent[z,y] )" ) // recursive
oTL:Run( 20 ) // fixpoint
AEval( oTL:Query( "Ancestor", oDom ), {| a | QOut( a[1] + " -> " + a[2] ) } )
Reasoning in embedding space (the paper's neural-symbolic bridge): embed entities as unit vectors, embed the relation, query it back, and use a temperature sigmoid — T→0 is deductive (hard 0/1), higher T analogical:
oE:Let( "Emb", TL_UnitEmb( n, 256, 1.234 ) )
oE:Eq( "EmbR[i,j] = Parent[x,y] * Emb[x,i] * Emb[y,j]" )
oE:Eq( "Score[x,y] = EmbR[i,j] * Emb[x,i] * Emb[y,j]" ) // ~1 iff (x,y) in Parent
tSoft := FWT_Sigmoid( tShifted, nTemperature )
Helpers (PRG)
| Function | Description |
|---|---|
TL_TensorND( aNested ) | Nested Harbour array (any rank) → FW_Tensor. |
TL_NotEq( n ) | [n,n] ones except diagonal (Datalog x != y guard). Same as FWT_NotEq. |
TL_UnitEmb( n, d, nSeed ) | Unit-vector embeddings [n,d] (deterministic). Same as FWT_UnitEmb. |
TL_Rnd2 / TL_Rnd3 / TL_Const1 | Xavier-ish init and constant vectors used by TTransformerTL. |
C engine (fwtensor.c)
| Function | Description |
|---|---|
FWT_New( aShape ) / FWT_FromArray / FWT_ToArray / FWT_Shape / FWT_Size / FWT_Get / FWT_Set | Create and inspect flat float32 tensors (GC-managed). |
FWT_EinSum( cSpec, t1, ... ) | Generalized Einstein summation, e.g. "ij,jk->ik", "pd,qd->pq", up to 6 operands. The tensor-logic primitive. |
FWT_MatMul / FWT_Add / FWT_AddBias / FWT_Scale / FWT_Transpose | Core linear-algebra ops (MatMul uses BLAS when available). |
FWT_SoftmaxLast / FWT_LNormLast | Softmax / layer norm over the last dimension, any rank. |
FWT_Softmax / FWT_LayerNorm | 2D row-wise variants (classic GPT-2 path). |
FWT_Sigmoid( t, nT ) / FWT_Step( t, nThresh ) / FWT_ReLU / FWT_Tanh / FWT_GELU | Elementwise nonlinearities; sigmoid takes the reasoning temperature. |
FWT_ActDeriv / FWT_SoftmaxBackwardLast / FWT_LNormBackwardLast | Backward kernels used by TTensorLogic:Backward(). |
FWT_Mul / FWT_Sub / FWT_Fill / FWT_Clone / FWT_MaxDiff | Elementwise and utility operations. |
FWT_AdamUpdate / FWT_SumSq / FWT_ScaleInPlace | Adam step, gradient norms, clipping. |
FWT_GetBytes / FWT_SetBytes | Raw float32 buffer as binary string (checkpointing). |
FWT_OneHot( aIds, nVocab ) | [len,vocab] one-hot from 1-based token ids. |
FWT_CausalMask( nSeq, nVal ) | Additive attention mask (0 on/below diagonal, nVal above; default −1e9). |
FWT_SinPos( nSeq, nDim ) | Sinusoidal positional encoding [seq,dim]. |
FWT_NotEq( n ) / FWT_UnitEmb( n, d, nSeed ) | C-speed builders for Datalog / embedding helpers. |
FWT_GatherRows / FWT_SliceCols / FWT_SetCols | Embedding lookup and column views (classic transformer path). |
FWT_LoadSafe( cFile, nOffset, aShape ) | Read float32 tensor from a safetensors-style blob. |
FWT_BlasActive() | .T. when an OpenBLAS sgemm backend is loaded. |
OpenBLAS acceleration
fwtensor.c loads OpenBLAS dynamically at first use — never a hard dependency:
libopenblas.dll on the PATH or beside the exe, or the FWT_BLAS_DLL environment
variable (set it to off to force the portable C loops; ILP64 builds such as numpy's
libscipy_openblas64_ are detected too). FWT_MatMul and every 2-operand
FWT_EinSum reducible to a (batched) GEMM run on cblas_sgemm, using BLAS
transposes so the common attention patterns need no repacking.
| Pattern (Xeon W-2140B, 8c) | C loops | OpenBLAS | PyTorch 2.11 / MKL |
|---|---|---|---|
| matmul 1024x1024x1024 | 0.32 GFLOPS | 497 GFLOPS | 982 GFLOPS |
| matmul 512x512x512 | 0.32 GFLOPS | 290 GFLOPS | 806 GFLOPS |
| attention scores p=512 d=64 | 0.31 GFLOPS | 55 GFLOPS | 466 GFLOPS |
numpy's own OpenBLAS reaches 446 GFLOPS on the 1024 matmul on the same machine — the FWH einsum matches BLAS-class performance; the remaining gap to PyTorch is MKL's AVX-512 kernels and lower per-call overhead, not the language.
Samples
samples/ai/tltutor.prg— runnable companion of the tutorial (lessons 1–9 →tltutor.log).samples/ai/tensorlogic.prg— engine test suite: einsum vs matmul, attention head as 6 equations, Datalog fixpoint, gradient checks vs finite differences, XOR training (9 PASS).samples/ai/tltransformer.prg— transformer as equations, parity with the classic class, training + generation (3 PASS).samples/ai/tldatalog.prg— Datalog over a family DBF + embedding-space reasoning with temperature (6 PASS).samples/ai/tlbench.prg— einsum benchmark, BLAS vs C loops.samples/ai/tlminillm.prg— DeepSeek/MiMo-style mini-LLM as pure equations: MLA latent attention, RoPE, mixture-of-experts, multi-token prediction (3 PASS).samples/ai/tlfwcode.prg— char-level language model on real FWH sources: Adam + warmup/cosine, batch gradient accumulation, clipping, train/val split, best-val checkpointing and resume, top-k sampling. Configure viaTLFW_*env vars; resume withTLFW_RESUME=1.samples/ai/tlc.prg— synthetic code-training recipe in miniature: program generator + exact oracle, execution learning (36–40% vs 10% chance) and grammar-constrained decoding (100% valid output even untrained).
Build any sample the same way:
cd /d C:\fwteam\samples\ai
C:\fwteam\samples\build_new.bat <name> hb32
<name>.exe