Tensor Logic: de 0 a 100

Companheiro executável: samples/ai/tltutor.prg — compile, execute e leia tltutor.log junto com esta página

Este tutorial leva você de "o que é um tensor" até treinar um modelo de linguagem com o código fonte do FWH, usando uma única ideia do Tensor Logic de Pedro Domingos (arXiv:2510.12269): uma regra lógica e uma soma de Einstein são a mesma operação. Cada lição é uma função de tltutor.prg — as saídas mostradas são sua saída real.

API completa: referência Tensor Logic.

Como usar Tensor Logic — passo a passo

Passo 0 — Requisitos

Passo 1 — Compilar e executar o sample do tutorial

cd /d C:\fwteam\samples\ai
C:\fwteam\samples\build_new.bat tltutor hb32
tltutor.exe
type tltutor.log

Você deve ver nove blocos de lição que terminam com TUTORIAL COMPLETE — next: tltransformer.prg, tlminillm.prg, tlfwcode.prg. Mantenha tltutor.log aberto enquanto lê as lições.

Passo 2 — Modelo mental (30 segundos)

flowchart TD T[FW_Tensor float32] --> E[FWT_EinSum join + projetar] E --> P[Programa TTensorLogic: tensores com nome + Eq] P --> N[Redes: atención, MLP, transformer] P --> S[Simbólico: Datalog sobre DBF] P --> A[Autodiff: Forward / Seed / Backward / StepAdam]
  1. Um tensor é um array n-D de float32 em memória C.
  2. Um einsum multiplica em índices compartidos (join) e soma os índices ausentes da saída (projeção).
  3. Um programa de tensor logic são tensores com nome + strings de equação avaliados com Run() (ponto fixo) ou Forward() (uma passagem para treinar).
  4. O gradiente de uma equação é o mesmo einsum com um fator e a saída trocados — qualquer programa é treinável sem backprop à mão.

Passo 3 — Seu primeiro programa (copiar e colar)

#include "FiveWin.ch"

FUNCTION Main()
   LOCAL oTL := TTensorLogic():New()

   oTL:Let( "W", { { 1, -1, 0 }, { 0, 1, -1 } } )
   oTL:Let( "X", { 1, 2, 0.5 } )
   oTL:Let( "B", { 2, -2 } )
   oTL:Eq( "Y[i] = step( W[i,j] * X[j] + B[i] )" )
   if ! oTL:Run()
      ? "Error:", oTL:cError
      RETURN NIL
   endif
   ? oTL:GetArray( "Y" )     // {1, 0}
   ? "BLAS ativo?", FWT_BlasActive()
RETURN NIL
C:\fwteam\samples\build_new.bat myfirst hb32
myfirst.exe

Passo 4 — Treinar algo (porta AND)

oTL := TTensorLogic():New()
oTL:Let(   "X", { { 0, 0 }, { 0, 1 }, { 1, 0 }, { 1, 1 } } )
oTL:Let(   "Ones", { 1, 1, 1, 1 } )
oTL:Param( "W", { { 0.1 }, { -0.1 } } )   // Param = treinável
oTL:Param( "B", { 0 } )
oTL:Eq( "Y[n,o] = sigmoid( X[n,f] * W[f,o] + Ones[n] * B[o] )" )
tT := FWT_FromArray( { { 0 }, { 0 }, { 0 }, { 1 } } )

for e := 1 to 300
   oTL:Forward()
   oTL:ZeroGrad()
   oTL:Seed( "Y", FWT_Scale( FWT_Sub( oTL:Get( "Y" ), tT ), 2 ) )
   oTL:Backward()
   oTL:ClipGrads( 1.0 )
   oTL:StepAdam( 0.05 )
next
// Y ≈ {0.00, 0.11, 0.11, 0.88} para targets {0,0,0,1}
oTL:SaveParams( "and.chk" )

Esse loop — Forward / ZeroGrad / Seed / Backward / Clip / Adam — é o mesmo usado pelos samples de modelos de linguagem.

Passo 5 — Suba a escada de samples

OrdemSampleO que você aprende
1tltutor.prgAs nove lições desta página.
2tensorlogic.prgTestes do motor, treino XOR (9 PASS).
3tldatalog.prgRegras sobre um DBF familiar + temperatura em embeddings.
4tltransformer.prgModelo estilo GPT como equações; paridade com Transformer clássico.
5tlbench.prgNúmeros de velocidade BLAS vs C.
6tlminillm.prgMLA, RoPE, MoE, predição multi-token — ainda são equações.
7tlfwcode.prgTreina um LM char-level com fontes FWH reais (warmup, val, checkpoints).
8tlc.prgTreino sintético de código + decoding com gramática.
C:\fwteam\samples\build_new.bat tensorlogic hb32
tensorlogic.exe
type tensorlogic.log

Lição 1 — Tensores

Um tensor é um array n-dimensional de números. No FWH é um FW_Tensor: un buffer float32 plano em memória C (rápido, compacto), criado e inspecionado do PRG:

t := FWT_New( { 2, 3 } )                 // 2x3, zerado
FWT_Set( t, 1, 10 )                      // indexação plana 1-based, row-major
FWT_Set( t, 6, 60 )
? FWT_Shape( t )                         // {2, 3}
t := FWT_FromArray( { { 1, 2 }, { 3, 4 } } )   // de array aninhado

Saída esperada (de tltutor.log):

=== Lesson 1: tensors ===
shape = {2, 3}, size = 6
t = {{10.00, 0.00, 0.00}, {0.00, 0.00, 60.00}}
from array = {{1.00, 2.00}, {3.00, 4.00}}

Lição 2 — Einsum: a única operação necessária

Uma especificação einsum nomeia os índices de cada operando e os da saída. Duas regras:

A := FWT_FromArray( { { 1, 2, 3 }, { 4, 5, 6 } } )
FWT_EinSum( "ij->i",     A )        // somas por linha  -> {6, 15}
FWT_EinSum( "ij->ji",    A )        // transpuesta
FWT_EinSum( "ij,jk->ik", A, B )     // produto matricial (join j, projeta j)
FWT_EinSum( "i,j->ij",   v, w )     // produto externo  (sem projeção)
FWT_EinSum( "i,i->",     v, v )     // produto escalar   (tudo projetado)

Toda a álgebra linear é uma primitiva com diferentes padrões de índices. Com OpenBLAS presente rodam sobre cblas_sgemm (ver a referência para o benchmark de 497 GFLOPS).

=== Lesson 2: einsum ===
row sums     'ij->i'    = {6.00, 15.00}
transpose    'ij->ji'   = {{1.00, 4.00}, {2.00, 5.00}, {3.00, 6.00}}
matmul       'ij,jk->ik' = {{14.00, 32.00}, {32.00, 77.00}}
dot 'i,i->' of {1,0,2} = 5.00

Lição 3 — Seu primeiro programa: equações

TTensorLogic guarda tensores com nome e equações escritas como strings. Este perceptron completo é UMA equação:

oTL := TTensorLogic():New()
oTL:Let( "W", { { 1, -1, 0 }, { 0, 1, -1 } } )
oTL:Let( "X", { 1, 2, 0.5 } )
oTL:Let( "B", { 2, -2 } )
oTL:Eq( "Y[i] = step( W[i,j] * X[j] + B[i] )" )
oTL:Run()
? oTL:GetArray( "Y" )        // {1, 0}

Leia como einsum: j é joinado e projetado (isso é W·X), + B[i] adiciona um segundo termo, step() é a não-linearidade. Disponíveis: step, sigmoid, relu, tanh, gelu, softmax, lnorm.

=== Lesson 3: first equation (a perceptron) ===
W.X + B = {1, -0.5} ; step -> Y = {1.00, 0.00}

Lição 4 — As regras lógicas SÃO equações

Guarde uma relação como tensor booleano (1 = o fato é verdadeiro). Então a regra Datalog Grandparent(x,z) <- Parent(x,y), Parent(y,z) é literalmente o mesmo padrão de equação: a variável compartilhada y é o join, e step() converte "pelo menos um caminho" em verdadeiro:

oTL:Let( "Parent", { {0,1,0,0}, {0,0,1,0}, {0,0,0,1}, {0,0,0,0} } )   // 1->2->3->4
oTL:Eq( "Grandparent[x,z] = step( Parent[x,y] * Parent[y,z] )" )
oTL:Run()      // Grandparent: (1,3) y (2,4)

Esta é a tese central do paper, em código funcionando: redes neurais (lição 3) e lógica (lição 4) rodam no mesmo motor.

=== Lesson 4: logic rules (Grandparent) ===
Grandparent = {{0,0,1,0}, {0,0,0,1}, {0,0,0,0}, {0,0,0,0}}

Lição 5 — Recursão: forward chaining até ponto fixo

Run() reavalia todas as equações até que nada mude, então as regras recursivas simplesmente funcionam. Fechamento transitivo:

oTL:Let( "Ancestor", { {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0} } )  // semente vazia
oTL:Eq( "Ancestor[x,y] = step( Parent[x,y] + Ancestor[x,z] * Parent[z,y] )" )
oTL:Run( 20 )  // Ancestor de 1: {2,3,4}; de 2: {3,4}; de 3: {4}
=== Lesson 5: recursion to fixpoint (Ancestor) ===
Ancestor = {{0,1,1,1}, {0,0,1,1}, {0,0,0,1}, {0,0,0,0}}

Lição 6 — Datalog sobre suas tabelas DBF

Uma tabela xBase É uma relação. TTLDomain mapeia nomes de entidades a índices, LetDbf() converte os registros, Query() retorna os resultados como nomes:

oDom := TTLDomain():New()
oDom:Harvest( "FAM", { "PARENT", "CHILD" } )       // registre todos os nomes PRIMEIRO
oTL:LetDbf( "Parent", "FAM", { "PARENT", "CHILD" }, oDom )
oTL:Eq( "Grandparent[x,z] = step( Parent[x,y] * Parent[y,z] )" )
oTL:Run()
AEval( oTL:Query( "Grandparent", oDom ), ;
       {| a | QOut( a[1] + " -> " + a[2] ) } )     // abe -> bart, abe -> lisa
=== Lesson 6: Datalog over a DBF ===
grandparent: abe -> bart
grandparent: abe -> lisa

Dica: faça sempre Harvest() de cada relação antes de dimensionar tensores, para que o domínio esteja completo e as dimensões coincidam entre regras.

Lição 7 — Blocos neurais: softmax e atenção

Un . após um índice do lado esquerdo marca o eixo do softmax. Uma cabeça de atenção são seis equações — compare com as centenas de linhas de uma implementação manual:

oTL:Eq( "Q[p,k] = X[p,d] * WQ[d,k]" )
oTL:Eq( "K[p,k] = X[p,d] * WK[d,k]" )
oTL:Eq( "V[p,k] = X[p,d] * WV[d,k]" )
oTL:Eq( "S[p,q] = Q[p,k] * K[q,k] * 0.7071" )   // scores escalados (1/sqrt(dk))
oTL:Eq( "A[p,q.] = softmax( S[p,q] )" )          // as linhas somam 1
oTL:Eq( "O[p,k] = A[p,q] * V[q,k]" )             // valores atendidos
=== Lesson 7: attention in 6 equations ===
attention row 1 = {0.33, 0.33, 0.34}
row sum = 1.000000  (softmax => 1)

Lição 8 — Autodiff: o gradiente é outro programa

Para Z = X1 * X2 * ... * Xn, o gradiente em relação a qualquer fator é o mesmo einsum com esse fator e a saída trocados. O motor aplica esta regra mecanicamente a cada equação: nenhuma camada precisa de backprop escrito à mão:

oTL:Param( "W", { { 0.3, -0.2 }, { 0.1, 0.4 } } )   // Param = treinável
oTL:Eq( "Y[i] = sigmoid( W[i,j] * X[j] )" )
oTL:Forward()                                        // cacheia pré-ativações
oTL:ZeroGrad()
oTL:Seed( "Y", FWT_Scale( FWT_Sub( oTL:Get("Y"), tTarget ), 2 ) )  // dL/dY
oTL:Backward()                                       // varredura inversa
? FWT_Get( oTL:GetGrad( "W" ), 1 )   // -0.19283922
// diferenças finitas sobre o mesmo peso: -0.19283742  — coincidem
=== Lesson 8: automatic differentiation ===
dL/dW[1,1] analytic = -0.19283922, finite differences = -0.19283742

Use Forward() (não Run()) ao treinar: cacheia as pré-ativações que Backward() precisa.

Lição 9 — Treinar de verdade: Adam, clipping, batches

O loop profissional adiciona otimizador Adam (kernel C), clipping global de gradientes, acumulação por mini-batch e checkpointing. Aprendendo a porta AND:

for e := 1 to 300
   oTL:Forward()
   oTL:ZeroGrad()
   oTL:Seed( "Y", FWT_Scale( FWT_Sub( oTL:Get("Y"), tT ), 2 ) )
   oTL:Backward()
   oTL:ClipGrads( 1.0 )
   oTL:StepAdam( 0.05 )
next        // Y -> {0.00, 0.11, 0.11, 0.88} para targets {0,0,0,1}
=== Lesson 9: Adam training (AND gate) ===
epoch  100  loss 0.179205
epoch  200  loss 0.073157
epoch  300  loss 0.039337
Y after training (targets 0,0,0,1) = {{0.00}, {0.11}, {0.11}, {0.88}}

Para batches com TTransformerTL:

oT:oTL:ZeroGrad()
for i := 1 to nBatch
   oT:AccumStep( aWin[i], aTgt[i], 1 / nBatch )  // grads de params acumulam
next
oT:oTL:ClipGrads( 1.0 )
oT:oTL:StepAdam( nLr )
oT:oTL:SaveParams( "model.chk", { "vocab" => aVocab } )

Lição 10 — Fachada transformer e além

Não é preciso escrever todas as equações à mão. TTransformerTL constrói um stack estilo GPT para você:

oT := TTransformerTL():New( 2, 8, 2, nVocab )   // camadas, d_model, cabeças, vocab
aProbs := oT:ForwardSeq( { 2, 3, 4, 5, 6 } )
nLoss  := oT:TrainStep( aIds, aTargets, 0.01 )
aGen   := oT:Generate( { 1 }, 8 )

Por baixo ainda é um programa TTensorLogic — inspecione oT:oTL:aEqs ou abra samples/ai/tltransformer.prg.

O modelo mental a guardar: tudo — uma regra, uma camada, um gradiente — é um conjunto de equações tensoriais, e uma equação é um einsum mais uma não-linearidade.

Cola

ObjetivoAPI
Ligar dadosoTL:Let( "X", aOrTensor )
Peso treináveloTL:Param( "W", aOrTensor )
Adicionar equaçãooTL:Eq( "Y[i] = relu( W[i,j] * X[j] )" )
Ponto fixo simbólicooTL:Run( 20 )
Passo de treinoForward → ZeroGrad → Seed → Backward → ClipGrads → StepAdam
Ler resultadooTL:Get( "Y" ) / GetArray( "Y" )
DBF → relaçãooDom:Harvest e depois LetDbf
Relação → nomesoTL:Query( "R", oDom )
CheckpointSaveParams / LoadParams
Transformer prontoTTransformerTL():New(...)
¿BLAS ativo?FWT_BlasActive()

Veja também