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
- FiveWin for Harbour instalado (esta árvore em
C:\fwteam). - Uma variante de compilador já capaz de construir samples (p.ex. Harbour + BCC32 →
hb32). Ver Sistema de compilação. - Bibliotecas FWH reconstruídas com
tensorlogic.prgefwtensor.c:
(Usecd /d C:\fwteam C:\fwteam\mfwh_new.bat hb32hm32,hm64,hg64, … para outras variantes.) - Opcional mas recomendado: OpenBLAS. Coloque
libopenblas.dllno PATH ou junto ao exe (há uma cópia emsamples\ai\libopenblas.dll). Sem ele os loops C puros funcionam, só mais lentos. Force o modo portátil comset FWT_BLAS_DLL=off.
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)
- Um tensor é um array n-D de float32 em memória C.
- Um einsum multiplica em índices compartidos (join) e soma os índices ausentes da saída (projeção).
- Um programa de tensor logic são tensores com nome + strings de equação
avaliados com
Run()(ponto fixo) ouForward()(uma passagem para treinar). - 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
| Ordem | Sample | O que você aprende |
|---|---|---|
| 1 | tltutor.prg | As nove lições desta página. |
| 2 | tensorlogic.prg | Testes do motor, treino XOR (9 PASS). |
| 3 | tldatalog.prg | Regras sobre um DBF familiar + temperatura em embeddings. |
| 4 | tltransformer.prg | Modelo estilo GPT como equações; paridade com Transformer clássico. |
| 5 | tlbench.prg | Números de velocidade BLAS vs C. |
| 6 | tlminillm.prg | MLA, RoPE, MoE, predição multi-token — ainda são equações. |
| 7 | tlfwcode.prg | Treina um LM char-level com fontes FWH reais (warmup, val, checkpoints). |
| 8 | tlc.prg | Treino 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:
- JOIN: um índice compartilhado por dois operandos multiplica seus elementos correspondentes.
- PROJEÇÃO: um índice ausente da saída é somado (desaparece).
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.
- Un transformer real:
samples/ai/tltransformer.prgescreve ~15 equações por camada e reproduz de perto a saída da classeTransformerclásica com os mesmos pesos (teste:tl_parity_forwardseq). - Un LLM moderno:
samples/ai/tlminillm.prgadiciona as técnicas de DeepSeek/MiMo — atenção latente MLA, RoPE (um join com matriz de permutação com sinal!), mixture-of-experts, predição multi-token — tudo ainda são equações. - Treinar com dados reais:
samples/ai/tlfwcode.prgtreina um modelo char-level com o código fonte do FWH: warmup+cosseno, split train/val, checkpoints do melhor-val e resume (set TLFW_RESUME=1). Ajuste comTLFW_STEPS,TLFW_BATCH,TLFW_LAYERS,TLFW_DMODEL,TLFW_HEADS,TLFW_WINDOW,TLFW_LR, … - Treino sintético de código:
samples/ai/tlc.prg— gerador + oráculo, aprendizado de execução, decoding com gramática (100% programas válidos mesmo sem treinar). - Velocidade:
samples/ai/tlbench.prg— com OpenBLAS o einsum atinge 497 GFLOPS em um matmul de 1024 (PyTorch/MKL: 982 na mesma máquina).
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
| Objetivo | API |
|---|---|
| Ligar dados | oTL:Let( "X", aOrTensor ) |
| Peso treinável | oTL:Param( "W", aOrTensor ) |
| Adicionar equação | oTL:Eq( "Y[i] = relu( W[i,j] * X[j] )" ) |
| Ponto fixo simbólico | oTL:Run( 20 ) |
| Passo de treino | Forward → ZeroGrad → Seed → Backward → ClipGrads → StepAdam |
| Ler resultado | oTL:Get( "Y" ) / GetArray( "Y" ) |
| DBF → relação | oDom:Harvest e depois LetDbf |
| Relação → nomes | oTL:Query( "R", oDom ) |
| Checkpoint | SaveParams / LoadParams |
| Transformer pronto | TTransformerTL():New(...) |
| ¿BLAS ativo? | FWT_BlasActive() |