Skip to content

Repository files navigation

LoomFormer-Paraplex

Autoregressive language model with Paraplex neurons, depth-wise attention and Tria operator carry

Causal GQA remains the token mixer. Paraplex changes the FFN. DepthAttn changes the residual route. Tria adds a small operator state that is composed through depth and across selected temporal boundaries.

by srose69 (SimpleRose)

Author mechanisms · Авторские механизмы

Architecture LoomFormer Neuron Paraplex Weight pseudo-complex Imaginary pseudo-paravector Activation PvPowLU Residual DepthAttn Operator Tria 3x3 Carrier joint RMS Stabilizer PolARM Temporal boundary refeed Gate identity anchored Phase bounded trace Memory carrier replay Readout sparse final CA

Runtime and compatibility · Runtime и совместимость

Python 3.10+ PyTorch 2.1+ Release pre-release CUDA 12.x / 13.x GPU Pascal to Blackwell Precision FP32 FP16 BF16 Attention GQA Attention backends Checkpoints HF Training PT + SFT Distributed DDP Data packed + OTF Tests GPU matrix Docs EN / RU

Badges link to the corresponding architecture, runtime and project sections.

English · Русский


Note: LoomFormer is pre-release research code. The equations below describe the current implementation, but checkpoint compatibility, configuration names and fused CUDA paths may still change.

Примечание: LoomFormer — исследовательский код в статусе pre-release. Формулы ниже описывают текущую реализацию, но совместимость чекпойнтов, имена параметров и fused CUDA-пути ещё могут меняться.

Contents


What is LoomFormer?

LoomFormer is a decoder-only autoregressive language model. It is still a Transformer in the literal sense: tokens are mixed by causal self-attention, generation is token-by-token, and inference uses a KV cache. The architectural experiment begins after that point.

I built LoomFormer around three coupled changes:

  1. Paraplex replaces the ordinary scalar FFN activation with a real path, a bounded phase path and a phase-conditioned output nonlinearity.
  2. DepthAttn replaces the fixed residual source with a learned softmax read over states produced earlier in network depth.
  3. Tria converts the internal Paraplex coordinates into a 3×3 operator, composes that operator through layers and time, and returns selected summaries to later computation.

Tria is the part of LoomFormer that I can describe mechanically and mathematically, but not yet semantically. I know how it is constructed, propagated and read, and ablation shows that a trained model can incorporate it deeply enough for its removal to change autoregressive logits and loss. At the same time, Tria does not map cleanly to tokens, concepts or a stable human-readable latent vocabulary: direct decoding and correlation analysis have not produced a reliable interpretation.

The base model can operate without Tria (on early steps compared with BIG train runs), so I do not present it as a necessary condition for language modelling. Rather, it is an auxiliary operator pathway whose learned role remains an open research question. In the spirit of the Bitter Lesson, I build an instrument, give it a task, and let learning determine how that instrument is used. Probes indicate that Tria does not remain an inert architectural ornament: its states become increasingly structured during training, and the model learns to rely on them as part of its computation.

I can describe how Tria works, but not yet what it means. If you want an interpretation, try asking the trained model why it uses Tria; its answer may be no less speculative than mine :)

The implementation works entirely with real tensors and produces three internal coordinates per FFN neuron:

  • R — the real/preactivation coordinate;
  • I — the bounded phase coordinate;
  • O — the activated Paraplex output before the FFN down-projection.

I use the words pseudo-complex and pseudo-paravector to describe the structure of the effective Paraplex weight. The implementation uses real tensors, but each hidden neuron is parameterized by a paired real and imaginary weight:

$$W_{\mathrm{eff}}=\left(W_{\mathrm{real}},W_{\mathrm{imag}} \right).$$

This is the pseudo-complex part. W_real produces the real coordinate, while W_imag produces the phase argument from the same FFN input together with attention and depth context:

$$R=W_{\mathrm{real}}u+b_{\mathrm{real}},$$ $$\beta = W_{\mathrm{imag}} \begin{bmatrix} Q\\\ K_{\mathrm{ctx}}\\\ C\\\ u\\\ D \end{bmatrix} +b_{\mathrm{imag}}.$$

The full U sector inside W_imag is a second projection of the same model-stream input u; the remaining sectors condition that projection on query, attended key, attention context and depth context (the sector labels C and D stand for the attention value context V^ctx and the depth context d). The two weight parts therefore form one effective pseudo-complex parameterization rather than two unrelated branches. No complex dtype or closed complex multiplication is required for this pairing.

W_imag is itself pseudo-paravector-valued. Its own-stream U sector acts as the scalar component, while the context sectors form the vector component:

$$W_{\mathrm{imag}}=\left(W^{I,U},\left[W^{I,Q},W^{I,K},W^{I,C},W^{I,D}\right]\right).$$

The complete nesting is therefore

$$W_{\mathrm{eff}}=\left(W_{\mathrm{real}},\left(W^{I,U},\mathbf W^{I,\mathrm{ctx}}\right)\right).$$

I call this a pseudo-paravector because it has the scalar-plus-vector organization of a paravector, while the implementation does not claim a full Clifford product or a complete Clifford basis.

The name LoomFormer refers to the way these paths are interwoven: ordinary causal attention, a depth history, a phase trace and an operator carry all participate in the same forward pass. The -former part is there because causal attention is still present; Tria does not replace attention.

Reference checkpoint

Published checkpoints are available on Hugging Face:

Hugging Face checkpoint repository

The reference run currently described in this README used:

Property Value
Parameters 112,999,621
Blocks 10
Model width 768
Query heads 8 × 96
GQA group size 4, therefore 2 KV heads
FFN hidden width 3072
Sequence length 1024
Tria temporal window 128 tokens / 8 windows per sequence
Precision bf16
Embeddings untied
Dataset FSS1STR
Training hardware one NVIDIA L40S
Observed throughput approximately 27.3k tok/s

At step 150,000, I measured a full sequential evaluation over 45,040,704 tokens:

Metric Value
Full evaluation loss 3.2584 nats/token
Bits per token 4.7009
Bits per byte 1.3288

The nominal 120,000-step training budget was 4,608,000,000 tokens, or approximately 40.8 training tokens per parameter. The source dataset contains approximately 4,684,230,241 tokens, or 41.5 data tokens per parameter. The published run was continued beyond the nominal budget to step 150,000.

These numbers describe one dataset, tokenizer, configuration and hardware setup. They are not presented as a cross-model benchmark.

Architecture at a glance

For block l, token t, the main path is:

h_l,t
  │
  ├── causal GQA ───────────────► attention output, q, attended-k, v-context
  │
  ├── DepthAttn over depth history ─► attention skip
  │
  └── LayerNorm(skip + attention)
                    │
                    ▼
                  u_l,t
                    │
                    ├── second DepthAttn ─► FFN skip and depth context d_l,t
                    │
                    ├── Paraplex(u, q, attended-k, context, d, Tria gate)
                    │         └── R_l,t,h , I_l,t,h , O_l,t,h
                    │
                    ├── Tria depth composition ─► C_l,t,h and p_l,t,h
                    │
                    └── LayerNorm(FFN skip + W2 O)
                              │
                              ▼
                            h_l+1,t

After the last block, each token has a finished depth carrier D_t of shape [H,3,3]. Outside the block stack, these carriers are composed in token order until a fixed window boundary or an explicit <CARRY> boundary fires. The fired endpoint has two uses:

D_s, D_s+1, ... , D_b
          │
          └── temporal composition ─► accT_b
                                       ├── sparse final cross-attention key/value
                                       └── seed for Tria layer 1 of the next segment

The refeed seed is consumed once at the next segment start. Temporal accumulation is then restarted from that newly depth-composed token, which prevents the boundary carrier from being multiplied into the path twice.

How it works

Notation

Symbol Meaning
u_t residual-stream input of the FFN at token t
Q, K^ctx, V^ctx query, attended-key and attention-value contexts from causal GQA
d_t depth context from the second DepthAttn read; sector label D inside W_imag
C (sector label) the V^ctx sector inside W_imag
m_s pooled DepthAttn read at sublayer s
R, I, O Paraplex real, phase and output coordinates
A positive PvPowLU amplitude softplus(g)
a, b, c jointly RMS-scaled and bounded pairwise relations derived from RI, RO, IO
K(a,b,c) skew-symmetric Tria generator (distinct from attention keys K)
T_{l,t,h} local Tria operator
C_l depth carrier after layer l; D_t = C_{L,t} is the finished depth carrier
accT_t temporal Tria accumulator (code name accT)
b_k token position of a fired temporal boundary
I_3, 𝒩(·) identity matrix; per-matrix RMS normalization over all nine entries

Causal GQA

The attention path is ordinary causal grouped-query attention with YaRN-scaled rotary position embeddings. Query heads may share fewer key/value heads.

For one query head, suppressing batch and head indices:

$$A_t = \mathrm{softmax}\!\left(\frac{Q_tK_{\le t}^{\top}}{\sqrt{d_h}}\right), \qquad V^{\mathrm{ctx}}_t = A_tV_{\le t}, \qquad K^{\mathrm{ctx}}_t = A_tK_{\le t}.$$

LoomFormer keeps both the attention context Vctx and the attended key context Kctx. The ordinary attention output is projected back into the residual stream, while Q, Kctx and Vctx are also passed into Paraplex.

The causal-attention implementation supports flat, chunked and token-by-token execution. Packed SFT rows carry a linear segment_ids/cu_seqlens layout. With attn_impl: auto, Ampere-and-newer training sends that layout to a validated FlashAttention or Transformer Engine varlen backend. The compact SDPA path processes each document independently on Pascal and in reference execution. Temporal chunks use precomputed gather plans for the K/V history. Document-layout metadata remains O(BT) and preserves isolation across packed examples.

Attention depth and token density are constructor options. attn_layers is a sorted 1-based list; omitted or null keeps every layer active. Inactive layers contain no QKV/output parameters, allocate no KV cache, emit a zero attention residual, and reuse the nearest lower active layer's current conditioning context. Layer 1 must remain active.

attn_token_stride: 2 stores and attends only half of the document-relative positions. shared uses offset 0 in every active layer; staggered rotates offsets by active-layer ordinal. Selected tokens compute Q/K/V and write compact KV. Gaps receive an inherited context, while the first active layer uses document-safe sample-and-hold. RoPE and Tria retain logical positions; cache_pos counts physical KV writes. A logical 16K configuration therefore allocates roughly 8K KV entries per stride-2 active layer. Sparse schedules change the trained architecture and should not be enabled only after training a dense checkpoint.

The layer mixer is chosen once by the constructor; inactive blocks do not take a per-token runtime branch:

┌───────────────────────────────────────────┐
│               Decoder block               │
└─────────────────────┬─────────────────────┘
                      │ constructor selection
          ┌───────────┴───────────┐
          ▼                       ▼
┌───────────────────────┐  ┌───────────────────────────┐
│    AttentionMixer     │  │  InheritedContextMixer   │
│ active layer          │  │ inactive layer           │
│ QKV + O + RoPE        │  │ 0 attention parameters   │
│ compact physical KV   │  │ 0 KV memory, passthrough │
└───────────────────────┘  └───────────────────────────┘

For attn_layers: [1, 3, 5], stride 2 and staggered, offsets follow the ordinal among active attention layers, not the absolute decoder-layer number:

document-relative position:       0  1  2  3  4  5  6  7
config layer 1 (ordinal 0):       1  0  1  0  1  0  1  0
config layer 3 (ordinal 1):       0  1  0  1  0  1  0  1
config layer 5 (ordinal 2):       1  0  1  0  1  0  1  0
inactive layers 2 and 4:          ·  ·  ·  ·  ·  ·  ·  ·

Each packed document restarts this pattern at logical position zero. Selection compacts only the physical attention path:

logical abs_pos:       0  1  2  3  4  5  6  7
write KV?:             1  0  1  0  1  0  1  0
physical cache_pos:    0  ·  1  ·  2  ·  3  ·
                       │     │     │     │
selected token ── gather ── QKV/RoPE ── compact causal attention ── scatter
gap token      ── inherited context / document-safe sample-and-hold
Tria + RoPE    ── retain the full logical timeline 0…7

Common schedules are:

# Token sparsity only; attention remains present in every decoder layer.
attn_layers: null
attn_token_stride: 2
attn_token_schedule: staggered

# Attention exists only in the first two layers; each stores one third of tokens.
attn_layers: [1, 2]
attn_token_stride: 3
attn_token_schedule: staggered

Attention selection never edits LM targets or the SFT loss mask. Dense, layer-sparse and checkerboard models are evaluated on the same shifted target tensor; only their hidden states and therefore their logits differ.

The resolved schedule is stored in checkpoints and AIO configuration. DDP verifies that every rank resolved the same Config before constructing the model. Resume rejects attention-architecture changes; use init_checkpoint for an intentional dense-to-sparse initialization.

DepthAttn

A standard residual block always adds the immediately preceding hidden state. LoomFormer instead keeps keys and values for the sequence of states already produced in network depth.

For sublayer s, a learned query q_s reads that history:

$$\pi_{s,j} = \mathrm{softmax}_{j} \left(\frac{\langle q_s,k_j\rangle}{\sqrt{d_h}}\right), \qquad m_s = \sum_{j\le s}\pi_{s,j}v_j, \qquad \mathrm{skip}_s = W^{\mathrm{depth}}_{o,s}m_s.$$

The softmax axis is depth, not token position. There are two reads per block:

  • one read supplies the skip used around causal attention;
  • the second supplies both the FFN skip and the depth context given to Paraplex.

The readout projection may be shared by all sublayers or separated per sublayer. With depth_attn_qkv_rms: true, the code applies, independently to every last-dimension vector,

$$\mathrm{FixRMS}_{\rho}(x) = x\frac{\rho}{\sqrt{\mathrm{mean}_i(x_i^2)+\varepsilon}}.$$

The targets match the initialization scales:

$$q_s\leftarrow\mathrm{FixRMS}_{0.88/\sqrt{d_h}}(q_s), \qquad k_j\leftarrow\mathrm{FixRMS}_{0.88}(k_j), \qquad v_j\leftarrow\mathrm{FixRMS}_{0.88\beta_{\mathrm{init}}}(v_j),$$

where βinit=(8L)^(-1/4) for residual_init: beta and βinit=1 for fanin. The attention formula above is evaluated after these transforms.

residual_branch_rms_cap: c applies

$$\mathrm{CapRMS}_{c}(x) = x\min\!\left( 1,\frac{c}{\sqrt{\mathrm{mean}_i(x_i^2)+\varepsilon}} \right)$$

to the depth skip and the attention/FFN branch separately, immediately before each sum and LayerNorm. Quiet branches retain their scale. Flat, chunked and token-by-token paths use the same order.

Paraplex

For a token representation u and hidden neuron h, the ordinary FFN up-projection first produces the weight-dependent term

$$X_{t,h}=(W_{\mathrm{real}}u_t)_h.$$

When no Tria signal enters the layer,

$$R_{t,h}=X_{t,h}+b_h.$$

When a Tria gate enters from the previous layer, it scales only X, not the bias:

$$R_{t,h}=X_{t,h}\left(1+\gamma_l p_{t,h}\right)+b_h, \qquad \gamma_l=0.25\tanh(\widehat\gamma_l).$$

In vector form,

$$R_t= \mathrm{diag}\!\left(\mathbf 1+\gamma_l p_t\right) W_{\mathrm{real},l}u_t+b_l.$$

The stored matrix W_real is not rewritten by Tria. The carrier creates a token-dependent diagonal gain on its output rows. A carrier has RMS approximately one over its nine entries, so |vec(C)[j]| ≤ 3; the softmax slot mixture therefore gives |p[t,h]| ≤ 3. With the fixed local limit |γ[l]| ≤ 0.25, the multiplicative factor stays in [0.25, 1.75]. The bias remains an untranslated origin rather than being scaled together with the input-dependent response.

For the gate itself,

$$\frac{\partial R_{t,h}}{\partial X_{t,h}}=1+\gamma_l p_{t,h}, \qquad \frac{\partial R_{t,h}}{\partial b_h}=1.$$

The carrier changes the sensitivity to Wx; it does not scale the bias derivative.

The phase argument is a structured projection of five sources:

$$\beta_{t,h}= W^{\mathrm{imag}}_h \begin{bmatrix} Q_t\\\ K^{\mathrm{ctx}}_t\\\ V^{\mathrm{ctx}}_t\\\ u_t\\\ d_t \end{bmatrix} +b^{\mathrm{imag}}_h.$$

The phase weights are sector-constrained. In head mode, a hidden group reads the Q/Kctx/Vctx/Depth channels associated with its own query head and the full model-state source u. In open mode, Q remains head-local while Kctx, Vctx, u and Depth are full-width sources shared across head boundaries. The compact w1_imag parameter stores only live sector weights; the dense matrix used by the reference GEMM is a transient expansion, not a second trainable matrix.

The current phase is bounded by a saturating sinusoidal map:

$$I^{\mathrm{base}}_{t,h} = \sin\!\left( \frac{\pi}{2} \frac{\beta_{t,h}}{\sqrt{1+\beta_{t,h}^{2}}} \right).$$

The same neuron's previous-token base phase may enter through a learned trace coefficient:

$$z_{t,h}=I^{\mathrm{base}}_{t,h}+w^{\mathrm{trace}}_h I^{\mathrm{base}}_{t-1,h}, \qquad I_{t,h}=\frac{z_{t,h}}{\sqrt{1+z_{t,h}^{2}}}.$$

Document boundaries reset that trace. The trace remains parallel during full-sequence training because position t reads the shifted Ibase from t-1, not the already enriched I_t.

The displayed phase equations are the exact forward function. Backward is configurable: phase_grad_mode: floor lower-bounds the cosine factor in the local derivative, while secant uses a secant slope to an EMA phase-radius anchor away from a small local neighborhood.

PvPowLU

In the reference Paraplex/PvPowLU path, a positive amplitude is built as

$$A_{t,h}=\mathrm{softplus}(g_{t,h})>0.$$

By default the gate is self-referential, g=R. An optional independent gate_proj exists for donor-model transplantation.

The real and phase coordinates are combined before the output activation:

$$P_{t,h}=R_{t,h}+A_{t,h}I_{t,h}.$$

For exponent parameter m, PvPowLU uses the positive PowLU gate

$$G(A)=A^{\frac{m}{\sqrt{A}+1}}\sigma(A),$$

and the Paraplex output coordinate is

$$\begin{aligned} O_{t,h} &=P_{t,h}G(A_{t,h}) \\\ &=R_{t,h}G(A_{t,h})+I_{t,h}A_{t,h}G(A_{t,h}). \end{aligned}$$

The FFN branch returned to the model stream is

$$F_t=W_2O_t.$$

This is not an ordinary GLU. The gate controls both the external nonlinear gain and the amount of phase mixed into the real coordinate before that gain is applied.

The effective pseudo-complex weight

For every Paraplex neuron, W_real and W_imag participate in the same forward transformation. Their shared-input part can be written as

$$W^{(u)}_{\mathrm{eff},h}=\left(W^{R}_{h},W^{I,U}_{h}\right).$$

with

$$R_h=W^{R}_{h}u+b^{R}_{h},$$ $$\begin{aligned} \beta_h={}&W^{I,U}_{h}u+W^{I,Q}_{h}Q+W^{I,K}_{h}K_{\mathrm{ctx}} \\\ &+W^{I,C}_{h}C+W^{I,D}_{h}D+b^{I}_{h}. \end{aligned}$$

The phase map converts β_h into I_h, and PvPowLU combines R_h and I_h into the neuron output. Thus W_real is the real part of the effective weight, while W_imag is its pseudo-paravector imaginary part. The U sector supplies the imaginary projection of the same u; the other sectors provide its context-dependent vector coordinates.

For the default self-referential mode, A(R)=softplus(R) and A'(R)=σ(R). The direct Paraplex derivatives are

$$\frac{\partial O}{\partial R} = \left(1+I\sigma(R)\right)G(A) + \left(R+AI\right)G'(A)\sigma(R),$$ $$\frac{\partial O}{\partial I}=A\,G(A).$$

Therefore the update of a real projection row contains the phase-dependent coefficient

$$\frac{\partial \mathcal L}{\partial W_{\mathrm{real},h}} = \frac{\partial \mathcal L}{\partial R_h} \left(1+\gamma_l p_h\right)u^{\top},$$

while the phase projection receives gradients through I(β) and through all Tria uses of R, I and O. The learned W_real is consequently co-adapted with w_imag, even though they remain separate parameter tensors. In this precise sense, the W later used in Wx+b has been learned under the influence of the phase path.

There is also a cross-layer route. w_imag,l changes I_l, then O_l, then W_{2,l}O_l, which changes the next residual stream. The next block's real projection multiplies that changed input:

$$w^{\mathrm{imag}}_l \longrightarrow I_l \longrightarrow O_l \longrightarrow W_{2,l}O_l \longrightarrow u_{l+1} \longrightarrow W_{\mathrm{real},l+1}u_{l+1}.$$

The code also supports GELU and an ungated single-input PowLU path. The equations above describe the Paraplex/PvPowLU configuration.

One neuron from L1H1 to L2H1

Consider one token t and one aligned hidden channel h=1. I omit t and h from the notation below.

The first layer has no incoming Tria gate:

$$R_1=w^{\mathrm{real}}_1u_1+b_1.$$

Its phase and output are

$$\beta_1=w^{\mathrm{imag}}_1 [Q_1,K^{\mathrm{ctx}}_1,V^{\mathrm{ctx}}_1,u_1,d_1]+b^{\mathrm{imag}}_1,$$ $$I_1=\mathrm{phase}(\beta_1,I^{\mathrm{base}}_{1,t-1}), \qquad A_1=\mathrm{softplus}(R_1),$$ $$P_1=R_1+A_1I_1, \qquad O_1=P_1G(A_1).$$

The ordinary FFN route is dense across channels:

$$F_1=W_{2,1}O_1, \qquad h_2=\mathrm{LN}_{\mathrm{ffn},1} \left(S^{\mathrm{ffn}}_1+F_1\right).$$

Consequently, the phase of hidden neuron H1 can affect many model-width coordinates through W2. Block 2 then builds its own attention output and depth skip from h2:

$$u_2=\mathrm{LN}_{\mathrm{attn},2} \left(S^{\mathrm{attn}}_2+\mathrm{Attn}_2(h_2)\right).$$

In parallel, R1, I1 and O1 produce the first Tria carrier C1. The first layer has one learned distribution over its nine carrier slots:

$$w^{\mathrm{slot}}_1=\mathrm{softmax}(\lambda_1), \qquad p_1=\left\langle w^{\mathrm{slot}}_1,\mathrm{vec}(C_1)\right\rangle.$$

The same hidden index in the second layer receives that scalar through the identity-anchored gate:

$$X_2=w^{\mathrm{real}}_2u_2, \qquad R_2=X_2\left(1+\gamma_2p_1\right)+b_2.$$

It then builds its own phase and output:

$$\beta_2=w^{\mathrm{imag}}_2 [Q_2,K^{\mathrm{ctx}}_2,V^{\mathrm{ctx}}_2,u_2,d_2]+b^{\mathrm{imag}}_2,$$ $$I_2=\mathrm{phase}(\beta_2,I^{\mathrm{base}}_{2,t-1}), \qquad A_2=\mathrm{softplus}(R_2),$$ $$P_2=R_2+A_2I_2, \qquad O_2=P_2G(A_2).$$

The concrete L1H1 → L2H1 route therefore has two simultaneous paths:

L1H1 phase/output ──► dense W2 ──► residual stream ──► attention/depth ──► u2 ──► Wreal,2
          │
          └─────────► C1[H1,3,3] ──► nine-slot selector ──► gain on X2[H1]

The immediate Tria gate preserves the hidden-channel index. Cross-channel mixing is supplied by W2, the next W_real, attention, DepthAttn and the final population pool.

Tria

The equations below define Tria exactly, but they do not by themselves identify its learned semantic role. In trained checkpoints, the carrier is causally active: disabling it changes downstream computation and autoregressive logits. However, its states have not yielded a stable token-level dictionary or a straightforward human-readable interpretation. Tria may encode its useful function through distributed population geometry, operator composition and trajectories across depth and time rather than through individually decodable coordinates.

Tria takes the three Paraplex coordinates R, enriched phase I and pre-W2 activated output O for every token and hidden channel. It first forms

$$x=(RI,RO,IO), \qquad \sigma_x=\sqrt{\frac{x_1^2+x_2^2+x_3^2}{3}+\varepsilon},$$

and then jointly RMS-scales the three relations before bounding them:

$$a=\tanh\!\left(\frac{RI}{\sigma_x}\right), \qquad b=\tanh\!\left(\frac{RO}{\sigma_x}\right), \qquad c=\tanh\!\left(\frac{IO}{\sigma_x}\right).$$

The shared positive divisor preserves their relative magnitudes and signs. Away from the epsilon floor, this map is invariant to a common rescaling of R, I and O. Before the joint RMS and tanh, the products obey

$$(RI)(RO)(IO)=R^2I^2O^2\ge 0.$$

They are invariant to a simultaneous sign flip (R,I,O)→(-R,-I,-O). Tria therefore reacts to relative sign and magnitude relations, not to an arbitrary global sign convention.

The implementation inserts these three bounded relations into a skew-symmetric generator, multiplies by a fixed axis rotation, and composes the resulting carrier. The nine slots read by selectors are the nine entries of that carrier after rotation and composition.

The generator is

$$K(a,b,c)= \begin{bmatrix} 0 & -c & b\\\ c & 0 & -a\\\ -b & a & 0 \end{bmatrix}, \qquad K^{\top}=-K.$$

The local Tria operator is

$$T_{l,t,h}=\left(I_3+\alpha K_{l,t,h}\right)R_{\mathrm{axis}(l)},$$

where Raxis is a fixed +90° rotation around one coordinate axis. The axis cycles with layer depth. α is a small carrier coefficient, configured directly or selected by the startup calibration path.

The joint bound gives

$$\kappa_2(I_3+\alpha K) \le \sqrt{1+3\alpha^2},$$

and the fixed rotation leaves the singular values unchanged.

Every normalization is local to one [batch, token, hidden] matrix and uses the RMS of all nine entries:

$$\mathcal N(M)= \frac{M}{ \sqrt{\frac{1}{9}\sum_{i=1}^{3}\sum_{j=1}^{3}M_{ij}^{2}+\varepsilon} }.$$

A useful invariant follows directly from this construction. If R=I=O=0, then a=b=c=0, but

$$T=R_{\mathrm{axis}},$$

not the zero matrix. Zero local RIO modulation is the base transport mode. The local Jacobian of (RI,RO,IO) is zero only at the triple origin. If one partner of a weak component is nonzero, the corresponding product supplies a derivative; if both partners are nonzero, there are two such paths. The complete network also retains the phase path, the positive softplus amplitude, the ordinary FFN route and the previous carrier.

One Tria step from L1 to L2

For one token and hidden channel, the first layer initializes the depth carrier:

$$C_1=\mathcal N(T_1).$$

The second layer left-composes its local operator with the previous carrier:

$$C_2=\mathcal N(T_2C_1).$$

The general depth recurrence is

$$C_l=\mathcal N(T_lC_{l-1}).$$

For every non-final block, the nine entries of C_l are flattened and reduced by that layer's learned slot distribution:

$$p_l= \sum_{j=1}^{9} \mathrm{softmax}(\lambda_l)_j \mathrm{vec}(C_l)_j.$$

The next block applies this value only to its weight-dependent real term:

$$X_{l+1}=W_{\mathrm{real},l+1}u_{l+1},$$ $$R_{l+1}=X_{l+1}\odot\left(\mathbf 1+\gamma_{l+1}p_l\right)+b_{l+1}.$$

Thus Tria changes the per-neuron gain of Wx while leaving b untouched. The gate starts exactly at identity because its raw coefficient is initialized to zero. The last block has no selector because there is no following Paraplex layer; its carrier is the finished depth carrier D_t for that token.

Boundary-to-boundary temporal carry

Let

$$D_t=C_{L,t}$$

be the final depth-composed carrier produced at the end of the block stack for token t. Temporal Tria composes these finished matrices outside the network depth.

Inside a segment with no reset,

$$\mathrm{accT}_t=\mathcal N(D_t\,\mathrm{accT}_{t-1}).$$

At a document reset, accumulation starts from the local depth carrier:

$$\mathrm{accT}_t=\mathcal N(D_t).$$

The active chunked training path and token-by-token inference use a streaming endpoint recurrence. A fixed boundary is scheduled after at most W=tria_temporal_window tokens; an explicit <CARRY> token may fire earlier. A hard boundary is suppressed when the next token starts a new document, because there is no same-document token to seed.

Suppose a valid boundary fires at token b_k. The endpoint

$$\mathrm{accT}_{b_k} = \mathcal N\!\left( D_{b_k}D_{b_k-1}\cdots D_{s_k} \right)$$

contains the depth carriers accumulated since the current segment start s_k, with local normalization after each implemented multiplication.

PolARM

At the fired boundary, PolARM applies one Gram correction before the carrier is stored as a final-CA key and reused as the next segment's seed:

$$G_b=\mathrm{accT}_{b_k}^{\top}\mathrm{accT}_{b_k}, \qquad s_b=\frac{\mathrm{tr}(G_b)}{3},$$ $$\widetilde{\mathrm{accT}}_{b_k} = \mathrm{accT}_{b_k} \left[ I_3-\frac{\beta_{\mathrm{P}}}{2} \left(\frac{G_b}{\max(s_b,\varepsilon)}-I_3\right) \right], \qquad 0\le\beta_{\mathrm{P}}<1.$$

For the first token of the next segment, t_0=b_k+1, the boundary endpoint is injected into Tria layer 1:

$$C_{1,t_0}^{\mathrm{seed}} = \mathcal N\!\left(T_{1,t_0}\,\widetilde{\mathrm{accT}}_{b_k}\right).$$

The remaining layers then run normally:

$$C_{l,t_0}^{\mathrm{seed}} = \mathcal N\!\left(T_{l,t_0}C_{l-1,t_0}^{\mathrm{seed}}\right), \qquad D_{t_0}^{\mathrm{seed}}=C_{L,t_0}^{\mathrm{seed}}.$$

After the seed has been consumed, the temporal accumulator is restarted from that newly finished depth carrier:

$$\mathrm{accT}_{t_0}=\mathcal N\!\left(D_{t_0}^{\mathrm{seed}}\right).$$

For later tokens before the next boundary,

$$\mathrm{accT}_t=\mathcal N(D_t\,\mathrm{accT}_{t-1}), \qquad t_0<t\le b_{k+1}.$$

This reset is intentional. accT̃_{b_k} already participates inside D_{t_0}^{seed} through the first-layer composition; multiplying the old endpoint into the temporal recurrence again would count it twice.

The complete boundary route is therefore

last-block carriers D_s ... D_b
          │
          └── streaming temporal composition ──► accT_b ──► PolARM ──► accT̃_b
                                                   │
                                                   ├── final-CA key/value
                                                   └── T_1,next @ accT̃_b
                                                         │
                                                         ├── compose through all layers
                                                         └── restart temporal state from D_next^seed

The full-sequence PyTorch reference also contains an associative segmented scan with doubling offsets. It implements the same matrix order and reset semantics, but the current chunked training path uses the endpoint recurrence above.

Final Tria readout

A fired temporal endpoint has shape [H,3,3]. The reader does not first allocate one k-dimensional representation per hidden neuron. It scores the nine raw slots, pools them over the hidden population, and only then applies the shared value projection.

Let x_{b,h}=vec(accT̃_{b,h}). A learned query qpool, an independent slot-key projection and a learned positive logit scale define

$$s_b= \mathrm{softplus}(\widehat\tau)\, \frac{W_{\mathrm{key}}^{\top}q_{\mathrm{pool}}} {\max\!\left(\left\|W_{\mathrm{key}}^{\top}q_{\mathrm{pool}}\right\|_2,\varepsilon\right)}.$$

The population weights are

$$\rho_{b,h} = \mathrm{softmax}_h\!\left( \langle s,x_{b,h}\rangle \right).$$

The pooled boundary vector is

$$\bar x_b=\sum_h\rho_{b,h}x_{b,h}, \qquad z_b=\mathrm{Up}\!\left(W_{\mathrm{reader}}\bar x_b+b_{\mathrm{reader}}\right).$$

Only fired boundaries become keys and values. The final cross-attention uses a shared projection for queries and keys and a separate value projection:

$$q_t=W_{qk}h_t, \qquad k_b=W_{qk}z_b, \qquad v_b=W_vz_b.$$

For boundaries allowed by causality and document masking,

$$\xi_{t,b} = \mathrm{softmax}_{b\le t} \left( \frac{q_tk_b^{\top}}{\sqrt{d}} \right),$$ $$h'_t=h_t+\eta\sum_{b\le t}\xi_{t,b}v_b, \qquad \eta=\eta_{\max}\tanh(\widehat\eta).$$

A boundary is readable at its own token position and at later allowed positions. If no boundary has fired, the path is an exact identity. Tria has no separate language-model head and no auxiliary target; it is trained only through its effect on the ordinary next-token loss.

Runtime state and VRAM

The counts below describe inference state for the actual loomchat.py path. The prompt is prefetched by repeated calls to Model.step, so the model never keeps a full [B,T,H,3,3] Tria tensor or a time-growing DepthAttn history in chat VRAM. Training activations, optimizer states, gradients, CUDA workspaces and autograd saves are separate costs.

DepthAttn

With the reference shared readout, DepthAttn owns

$$2N^2+N^2+2LN=3N^2+2LN$$

parameters: one shared K/V projection, one shared output projection and one query per sublayer. For N=768 and L=10, this is 1,784,832 parameters, about 1.58% of the 112,999,621-parameter reference model. Raw bf16 storage is about 3.4 MiB.

DepthAttn attends over network depth, not over token time. During one incremental token step it builds two temporary tensors

$$K_{\mathrm{depth}},V_{\mathrm{depth}} \in \mathbb{R}^{B\times 1\times 2L\times N},$$

so the scratch size is

$$4BLN$$

bf16 elements. For B=1, L=10, N=768, both tensors together occupy 60 KiB. This amount is fixed with respect to chat length: the tensors are rebuilt for the current token and discarded after the step.

A full-sequence training forward may materialize the same depth states for a token span T, giving 4BTLN elements inside that forward. That is an activation cost of the training/full-sequence path, not a persistent autoregressive cache.

Tria

In incremental inference Tria carries no time axis. At the largest point of one token step, the live operator state is exactly three carrier-sized tensors:

$$C_{L-1},\quad C_L,\quad \mathrm{accT} \in \mathbb{R}^{B\times H\times 3\times 3},$$

where C_{L-1} is the previous depth carrier, C_L is the current layer result, and accT is the temporal accumulator retained at the tail of the network. The raw bf16 working set is therefore

$$3\cdot 9BH$$

elements. For B=1, H=3072, this is 162 KiB. It does not grow with the number of chat tokens.

The model also retains one phase trace [B,H] per Paraplex layer. For ten layers at H=3072, all bf16 phase traces together occupy 60 KiB. The pending-fire flag is negligible. Thus the fixed Paraplex/Tria recurrent state is about 222 KiB per batch element, excluding temporary R/I/O values inside the current layer.

The [B,T,H,3,3] tensors used by chunked training are training activations. They are not stored across the autoregressive conversation and must not be counted as a chat-length-dependent Tria cache.

Chat caches and a 10k-token context

The ordinary GQA cache stores K and V for every layer:

$$2BLT N_{\mathrm{kv}}d_h$$

bf16 elements. With B=1, L=10, N_kv=2, and d_h=96, this is 7.5 KiB per cached token:

Configured context Ordinary GQA K/V
1,024 tokens 7.5 MiB
10,000 tokens 73.24 MiB

The incremental final Tria cross-attention cache currently preallocates two dense buffers

$$K_{\mathrm{CA}},V_{\mathrm{CA}} \in \mathbb{R}^{B\times \mathrm{seq\_len}\times N}.$$

Only fired boundary rows become valid, but the allocated storage follows the configured seq_len. At N=768, bf16 storage is:

Configured context Final-CA K/V allocation Fixed-grid valid rows at W=128
1,024 tokens 3.0 MiB at most 7 internal fires
10,000 tokens 29.30 MiB at most 78 internal fires

Therefore a model configured for a 10,000-token chat allocates approximately

$$73.24\ \mathrm{MiB}+29.30\ \mathrm{MiB}=102.54\ \mathrm{MiB}$$

for the two length-dependent K/V cache families at B=1 and bf16. Adding fixed DepthAttn scratch, the three live Tria carriers and ten phase traces changes this by less than 0.3 MiB. Model weights, allocator overhead, attention workspaces and logits are not included.

The current chat implementation has no rolling-cache wraparound. A 10k conversation therefore requires a checkpoint/configuration whose seq_len is at least 10,000; the published seq_len=1024 configuration cannot hold that conversation without rebuilding or changing the context policy.

KV placement and calibration

loomchat.py stores exact-prefix snapshots, so a new turn prefills only the suffix added after the longest cached prefix. --kvstorage controls where the persistent layer K/V lives:

Value Execution
same K/V and attention stay on the model GPU.
cpu K/V stays in pinned RAM and is streamed through a CUDA staging ring while online softmax consumes earlier chunks.
cuda:N K/V stays on GPU N; the complete parameter-free attention reduction runs there and returns the attention and attended-key contexts to the model GPU.

At startup, the chat runtime measures a real model forward over up to 2048 tokens, incremental Model.step throughput, transport time and online-attention consumption. It benchmarks candidate chunks from 128 tokens up to the calibrated context length, selects the fastest measured pipeline and derives a dynamic preload. The no-starvation estimate receives a two-chunk safety margin. CPU storage uses pinned buffers and separate copy/compute streams. Remote CUDA storage records whether peer access exists; ordinary CUDA transfers supply a host-staged route on non-peer topologies.

The same placement can be changed inside a session with /kvstorage same, /kvstorage cpu or /kvstorage cuda:N. Changing the compute device, dtype or KV placement invalidates the old snapshots and recalibrates transport.

CUDA kernels and replay

LoomFormer does not train the Paraplex and Tria paths as a long chain of generic PyTorch elementwise operations. The repository contains real CUDA sources under kernels/: device code lives in *_kernel.cuh, standalone translation units for PTX inspection live in *_kernel.cu, and ATen/PyBind launchers live in *_launcher.cu.

The current tree reports 17 kernel groups built into seven extensions:

Extension Fused work
loomformer_beta_space Compact W_imag projection over the open Q/K/C/U/D sectors and its backward pass.
loomformer_paraplex Phase recurrence, trace handling, amplitude, phase mixing, PvPowLU output, anchor update and parameter reductions.
loomformer_phase_sin Standalone bounded phase map and its custom gradient modes.
loomformer_pvpowlu Standalone PvPowLU forward and backward.
loomformer_depth_attn_online Online-softmax DepthAttn forward and backward over the fixed depth history.
loomformer_tria_carry Eleven Tria groups: initialization, seeded initialization, gated variants, depth steps, depth replay, slot mixing, population pooling, temporal carry, endpoint-only temporal carry and sparse final cross-attention.
loomformer_packed_gather One-launch document-major packing of temporal K/V history for varlen attention, including scatter-backward into source chunks.

The fused Paraplex path computes beta_space and then performs phase construction, temporal phase trace, amplitude, P=R+AI, PvPowLU and the required backward terms without materializing each algebraic subexpression as a separate Python-level tensor. Tria kernels keep the three jointly RMS-scaled relations, the nine entries of a local 3x3 operator, its matrix product and per-matrix RMS normalization in registers or local kernel state; intermediate local matrices are not persisted as separate model states.

Recompute and depth replay

The non-initial Tria depth steps use a custom replay tape during chunked training. A plain autograd implementation would save, for every layer, both the previous carrier

$$C_{L-1}\in\mathbb{R}^{B\times T\times H\times3\times3}$$

and its normalization scale. In replay mode the forward pass saves the local R/I/O values already required by the Paraplex path, plus the selector weights for a gated step, but drops C_{L-1} and the saved scale for that step.

During backward, the missing previous carrier is recovered in one of two ways:

  • for a live FP32 current carrier, the invertible local Tria factor permits an analytic reverse step;
  • for bf16/fp16, or when that current carrier is no longer live, the dedicated depth_replay kernel reconstructs the required carrier by replaying the recorded Tria factors from the segment seed.

The replay changes storage, not the forward equation. Gradients are still computed for R, I, O, the previous carrier and the nine-slot selector. The trade-off is additional backward arithmetic in exchange for not retaining a full carrier-sized activation at every depth step.

Endpoint-only temporal carry

Chunked training usually needs the final temporal state of a segment, not every normalized temporal prefix. The temporal_carry_endpoint kernel therefore returns only

$$\mathrm{accT}\in\mathbb{R}^{B\times H\times3\times3}$$

and a small FP32 endpoint copy used by backward. It does not store a second full trajectory of temporal accumulators. Backward walks the input depth carriers in reverse and reconstructs each preceding normalized accumulator analytically from the invertible local factor and the current accumulator. This is the temporal counterpart of replay: the input depth carriers remain available, while the additional [B,T,H,3,3] prefix history is avoided.

Other fused Tria kernels cover the nine-slot selector, population pooling and sparse final cross-attention inside compact CUDA operator graphs. Algebraic kernels keep their checked PyTorch reference paths. Packed attn_impl: auto on Ampere and newer requires a validated varlen forward/backward backend and the packed-gather extension. Pascal training selects compact SDPA explicitly.

Kernel extensions are built with torch.utils.cpp_extension.load and Ninja. Startup warms every extension required by the active graph, including packed_gather, before the architecture report. Source and included-header hashes are recorded in kernels/.hashes.json; unchanged modules are loaded from kernels/build/. TORCH_CUDA_ARCH_LIST may be supplied explicitly, KERNELS_VERBOSE=1 exposes the extension build log, and KERNELS_DUMP_PTX=1 emits standalone PTX dumps for inspection after a changed build.

Depth and connectivity

The reference checkpoint has ten named Transformer blocks. I also use an informal operational count when reasoning about the graph, but it is not a model-depth standard and it is not backend-invariant.

An earlier README draft reported ~124 operational layers by assigning ceil(log2(128))=7 stages to the temporal prefix. That number mixed the associative full-sequence reference scan with the active chunked execution path. In the current code:

  • the full-sequence PyTorch reference can resolve temporal carry with doubling offsets;
  • chunked training computes one streaming endpoint per segment;
  • token-by-token inference performs one temporal matrix recurrence per generated token;
  • a fired boundary adds one seed composition at the first Tria layer of the next segment.

The temporal dependency depth is therefore execution-dependent: up to the segment length in the streaming path, rather than universally seven stages. I no longer present 124 as a single exact depth of the current implementation.

The static per-token block stack still contains, under my informal convention, 60 block transformations, 10 Tria depth builds/compositions and 27 inter-layer selector/reduction/gate operations before temporal unrolling. Final aggregation, cross-attention, embeddings and the LM head add their own transformations when the corresponding boundary path is active.

The same issue applies to a single exact connection count. Causal attention edges depend on document masks and sequence length; temporal edges depend on explicit and fixed boundaries; sparse final cross-attention depends on which boundaries are valid. I therefore describe the connectivity by its axes instead of publishing an unverified scalar: token attention, depth-history attention, same-channel Tria depth recurrence, temporal matrix recurrence, boundary refeed and sparse boundary-to-token attention.

Training, SFT and inference

The repository contains complete paths for:

  • tokenizer training and raw-corpus tokenization;
  • prepared .bin token streams and streaming TXT/JSONL/Parquet/Arrow datasets;
  • single-device and self-launched multi-GPU DDP pretraining;
  • gradient accumulation, activation checkpointing and asynchronous evaluation;
  • full sequential evaluation with loss, bits/token and bits/byte reporting;
  • smart resume of weights, step, schedule and data position;
  • AdamW or ATOM optimization;
  • supervised fine-tuning with compact packed layouts, assistant-only loss masks and tool-call templates;
  • bounded-memory Parquet SFT rendering/tokenization, destructive validation splitting and deterministic full-split evaluation;
  • donor checkpoint inspection and structural transplantation;
  • portable .aio packaging and interactive terminal chat;
  • AOTInductor export for a self-contained inference package.

When temporal Tria is enabled, a full input passed to Model.forward is processed internally as temporal chunks. Token-by-token step carries the analogous attention KV caches, Paraplex phase traces, Tria endpoint and sparse final-cross-attention keys. The flat batched path is used when temporal Tria is disabled or ablated; it is not the active refeed path.

The reference PyTorch implementations define the semantics. Optional fused CUDA kernels accelerate phase-space projection, PvPowLU, DepthAttn, Tria depth composition, slot reduction, temporal carry and final sparse cross-attention.

Repository layout

loomformer.py   model, data pipeline, pretraining, evaluation and AOT export
tria.py         Tria operators, depth/temporal carry, readers and final cross-attention
loomsft.py      supervised fine-tuning
loomcloner.py   donor inspection and checkpoint transplantation
loompack.py     portable .aio pack / inspect / extract utility
loomchat.py     interactive terminal chat for .aio packages
setup.sh        CUDA/PyTorch environment installation, repository update and validation
tests/          synthetic PT/SFT integration matrix and numerical backend parity

Quick start

Install

Clone the repository and run the installer:

git clone https://github.com/srose69/Loomformer-Paraplex.git
cd Loomformer-Paraplex
./setup.sh

The installer probes every visible GPU. A machine containing Pascal or Volta selects the CUDA 12.6 / PyTorch cu126 profile. A Turing-or-newer machine selects CUDA 13.0 / PyTorch cu130. The toolkit and venv are installed under ~/loom by default. If the system Python lacks ensurepip, setup falls back to user-local virtualenv without requiring root; an incomplete environment is preserved under a timestamped .incomplete.* name before retrying. The menu also provides read-only environment inspection, package reinstall and repository update.

The current installer defaults to PyTorch 2.12.1. LOOM_CUDA_LINE, LOOM_TORCH_VERSION, LOOM_INSTALL_DIR, LOOM_REPO_DIR, LOOM_BUILD_JOBS and LOOM_NVCC_THREADS provide explicit profile, location and native-build overrides.

Repository update uses git pull --ff-only for a Git checkout. Tarball installations receive an upstream-file overlay. Checkpoints, datasets, logs, environments and local-only paths remain in place.

The manual Python environment remains useful when the CUDA toolchain is already managed externally:

python -m venv .venv
source .venv/bin/activate
pip install torch numpy pyyaml tokenizers pyarrow safetensors transformers jinja2 einops

Custom C++/CUDA extensions require Ninja, a matching CUDA toolkit and a C++ compiler. flash-attn is required by attn_impl: auto for bf16/fp16 packed training on Ampere and newer; a validated Transformer Engine varlen backend also satisfies this path.

Validation

python tests/run_matrix.py --setup

The matrix creates its tokenizer, PT streams and Parquet chat datasets in a temporary directory. It runs unit invariants, loads every fused extension, checks forward/backward parity on each visible GPU, performs PT and SFT optimizer steps, resumes both checkpoints, evaluates complete validation splits and launches PT/SFT DDP across all visible GPUs. Ampere-and-newer runs also compare the validated varlen backend with the compact reference path.

Small component smoke tests remain available:

python loomformer.py --smoke-test
python loomsft.py --smoke-test
python tria.py

Train a tokenizer

python loomformer.py \
  --config cfg/model.yaml \
  --train-tokenizer ./datasets/raw \
  --vocab 32000 \
  --tokenizer-out tokenizer.json

Pretrain

ATOM_META_SCALE=8 python -u loomformer.py \
  --train \
  --config cfg/model.yaml \
  --checkpoint ./loomformer.pt

If the dataset is not specified inside the YAML:

ATOM_META_SCALE=8 python -u loomformer.py \
  --train \
  --config cfg/model.yaml \
  --dataset ./datasets/train \
  --checkpoint ./loomformer.pt

Run on all visible CUDA devices:

python loomformer.py \
  --train \
  --device cudas \
  --config cfg/model.yaml \
  --checkpoint ./loomformer.pt

The built-in detached mode avoids a separate nohup wrapper:

ATOM_META_SCALE=8 python loomformer.py \
  --train \
  --config cfg/model.yaml \
  --checkpoint ./loomformer.pt \
  --quiet

Evaluate

python loomformer.py \
  --eval \
  --checkpoint ./loomformer.pt \
  --dataset ./datasets/val \
  --eval-data-cache ram

Minimal checkpoint inference

python loomformer.py \
  --infer \
  --checkpoint ./loomformer.pt \
  --prompt "The carrier matrix" \
  --max-new 128

Supervised fine-tuning

cfg/alt6_sft.yaml contains the dataset directory, pretrained initialization checkpoint, output checkpoint and DDP device list:

python loomformer.py --train --config cfg/alt6_sft.yaml

The generic CLI form is:

python loomsft.py \
  --config cfg/sft.yaml \
  --sft-dataset ./datasets/sft \
  --init-checkpoint ./loomformer.pt \
  --checkpoint ./loomformer-sft.pt

Package and chat

python loompack.py pack ./loomformer-sft.pt \
  --tokenizer tokenizer.json \
  --template chat_template.jinja \
  --quant bf16 \
  -o loomformer.aio

python loomchat.py loomformer.aio --device cuda:0

Store persistent KV snapshots in pinned RAM:

python loomchat.py loomformer.aio --device cuda:0 --kvstorage cpu

Run attention beside K/V stored on another GPU:

python loomchat.py loomformer.aio --device cuda:0 --kvstorage cuda:1

Inspect a donor model

python loomcloner.py \
  --scan ./donor-model \
  --out ./cfg/donor.yaml

Cloning is structural transplantation, not exact architectural conversion. Compatible tensors are remapped; LoomFormer-specific Paraplex, DepthAttn and Tria parameters keep their own initialization unless a mapping explicitly defines another destination.

Configuration

The model is configured from YAML. The main groups are:

Area Representative fields
Shape model_dim, n_q_heads, n_kv_heads, gqa_group_size, hidden, layers
Attention attn_impl, attn_layers, attn_token_stride, attn_token_schedule, attn_sdpa_compute_dtype, attn_sdpa_value_fusion, rope_*
DepthAttn depth_attn_readout, depth_attn_qkv_rms, residual_branch_rms_cap
Paraplex phase_sectors, activation, powlu_m, phase_grad_mode, phase_grad_floor, paraplex_gate_proj
Tria tria_carry_enabled, tria_temporal_enabled, tria_temporal_window, tria_carrier_alpha, calibration fields
Data train_dataset, val_dataset, auto_val_split_pct, dataset_format, dataset_cache, text_field, seq_len, batch_size, prefetch_batches
Training steps, lr, optimizer, weight_decay, grad_clip, grad_accum_steps, warmup_steps
Checkpoints init_checkpoint, checkpoint, resume, resume_data_stream, runpoints_path
Runtime device, amp_dtype, grad_checkpointing, compile, graph, save_graph, CUDA fast-path flags

Important shape invariants are checked when the configuration is applied:

  • model_dim must be divisible by the query-head count;
  • GQA head counts and group size must agree;
  • the Paraplex hidden width must be divisible by the query-head count;
  • temporal Tria requires a positive window and carrier coefficient;
  • the configured input length cannot exceed seq_len.

The startup Tria calibration path can select a temporal window and carrier coefficient from a candidate population using condition number, effective rank and population-pass thresholds.

For SFT, dataset_cache: otf accepts a Parquet file or a directory of top-level Parquet shards. Rows are partitioned by DDP rank, rendered and tokenized in bounded batches, then packed with compact document metadata. auto_val_split_pct cuts a validation tail into <train_dataset>/val/val_split.parquet once and reuses its manifest on subsequent runs. An epoch with zero trainable examples raises an error containing the active seq_len.

Requirements

Core requirements:

  • Python 3.10 or newer;
  • PyTorch;
  • NumPy;
  • PyYAML;
  • Hugging Face tokenizers.

Workflow-dependent packages:

  • PyArrow for Arrow and Parquet datasets;
  • Jinja2 for chat templates;
  • Safetensors and Transformers for donor workflows;
  • a C++/CUDA toolchain for fused kernels;
  • ATOM when optimizer: atom is selected.

CPU execution is supported by the reference paths. CUDA is required for practical training of the reference-sized models and for the fused custom operators.

The supported CUDA hardware floor is Pascal (SM 6.0). Pascal and Volta use the CUDA 12 profile and explicit compact SDPA attention. Ampere, Ada, Hopper and Blackwell use a validated FlashAttention or Transformer Engine varlen backend for attn_impl: auto. Backend startup probes execute forward and backward and report the captured import or kernel failure when validation fails.

Design constraints

LoomFormer deliberately keeps causal attention. Its full token-mixing path therefore remains quadratic in sequence length. Tria is not advertised as a replacement for attention; it adds an operator route across depth and selected temporal boundaries.

The architecture carries more state than a minimal decoder Transformer: KV caches, a depth history, per-layer phase traces, depth carriers, temporal carriers and sparse boundary keys. Parity must be checked between the active chunked forward transitions and token-by-token step; the separate flat path is used when temporal refeed is inactive.

The local Tria matrix is small, but it exists for every token and Paraplex hidden channel. Fused paths avoid several large intermediate materializations, yet Tria still adds compute and memory traffic. Its benefit has to be evaluated against that cost by ablation, held-out loss, generation behaviour and profiling.

The model's initial loss should not be compared step-for-step with a vanilla pre-norm Transformer without controlling for normalization placement, DepthAttn residuals, Paraplex activation, initialization and tokenizer. The reference run converged, but that single result does not establish scaling behaviour for every configuration.

No single operational-depth or connection-count scalar is treated as an invariant in this revision. The current execution graph depends on the temporal backend and on fired boundaries. The conventional architecture description remains: 10 decoder blocks with two depth reads, causal GQA, Paraplex FFN and optional Tria carry.

Status

LoomFormer currently trains, evaluates, fine-tunes, packages and generates text. The 113M run converged on the stated corpus and the full evaluation metrics are reported above. SFT and LoomChat are functional; LoomChat is still relatively VRAM-heavy because the current implementation prioritizes architectural equivalence and inspectability over a minimal inference runtime.

The synthetic installation matrix has completed on a GTX 1080 and two Tesla P4 GPUs, including bf16 fallback, PT/SFT optimizer steps, packed-attention parity, checkpoint resume, full OTF evaluation and three-rank DDP. Modern-GPU runs additionally execute the FlashAttention/Transformer Engine varlen parity cases.

The project is in pre-release. Configuration names, checkpoint migrations, CUDA kernels and package metadata are not yet a stable public API.

Citation

Until there is a paper or archived release, cite the repository and checkpoint collection:

@software{loomformer_paraplex,
  author = {srose69},
  title  = {LoomFormer-Paraplex},
  year   = {2026},
  url    = {https://github.com/srose69/LoomFormer-Paraplex}
}

Содержание


Что такое LoomFormer?

LoomFormer — decoder-only авторегрессионная языковая модель. В буквальном смысле это всё ещё Transformer: токены смешиваются каузальным self-attention, генерация идёт по одному токену, а инференс использует KV-cache. Архитектурный эксперимент начинается после этого.

Я построил LoomFormer вокруг трёх связанных изменений:

  1. Paraplex заменяет обычную скалярную FFN-активацию real-путём, ограниченным фазовым путём и фазово-обусловленной выходной нелинейностью.
  2. DepthAttn заменяет фиксированный источник residual на обучаемое softmax-чтение состояний, уже полученных раньше по глубине сети.
  3. Tria превращает внутренние координаты Paraplex в оператор 3×3, композирует его по слоям и времени и возвращает выбранные сводки в дальнейшее вычисление.

Tria - та часть LoomFormer, которую я могу описать механически и математически, но пока не могу семантически интерпретировать. Я знаю, как она строится, переносится и читается; абляции показывает, что обученная модель способна встроить её в своё вычисление настолько глубоко, что отключение Tria меняет авторегрессионные логиты и loss. При этом Tria не отображается напрямую в токены, понятия или устойчивый человекочитаемый латентный словарь: прямое декодирование и корреляционный анализ пока не дали надёжной интерпретации.

Базовая модель способна работать без Tria (по крайней мере, на ранних шагах в сравнении с БОЛЬШИМИ трейн ранами), поэтому я не представляю её как необходимое условие языкового моделирования. Скорее, это вспомогательный операторный путь, точная выученная роль которого остаётся открытым исследовательским вопросом. В духе Bitter Lesson я строю инструмент, даю ему задачу и позволяю обучению определить, как именно этот инструмент будет использован. Пробы показывают, что Tria не остаётся инертным архитектурным украшением: по мере обучения её состояния становятся всё более структурированными, а модель учится опираться на них как на часть собственного вычисления.

Я могу описать, как работает Tria, но пока не знаю, что она означает. Если вам нужна интерпретация, попробуйте спросить у самой обученной модели, зачем ей Tria: её ответ, возможно, будет не менее спекулятивным, чем мой :)

Реализация полностью работает на вещественных тензорах и формирует три внутренние координаты каждого FFN-нейрона:

  • R — real/preactivation-координата;
  • I — ограниченная фазовая координата;
  • O — активированный выход Paraplex до понижающей FFN-проекции.

Термины псевдокомплексный и псевдопаравекторный описывают структуру эффективного веса Paraplex. Реализация работает с вещественными тензорами, но каждый hidden-нейрон параметризован парой real- и imaginary-весов:

$$W_{\mathrm{eff}}=\left(W_{\mathrm{real}},W_{\mathrm{imag}} \right).$$

Это псевдокомплексная часть конструкции. W_real формирует real-координату, а W_imag формирует фазовый аргумент из того же FFN-входа вместе с attention- и depth-контекстом:

$$R=W_{\mathrm{real}}u+b_{\mathrm{real}},$$ $$\beta = W_{\mathrm{imag}} \begin{bmatrix} Q\\\ K_{\mathrm{ctx}}\\\ C\\\ u\\\ D \end{bmatrix} +b_{\mathrm{imag}}.$$

Полный сектор U внутри W_imag является второй проекцией того же model-stream входа u; остальные сектора обусловливают эту проекцию query, attended key, attention context и depth-context (метки секторов C и D обозначают attention value context V^ctx и depth context d). Поэтому обе части веса образуют единую псевдокомплексную параметризацию, а не две независимые ветви. Для такого сопряжения не требуются complex dtype и замкнутое комплексное умножение.

Сам W_imag имеет псевдопаравекторную структуру. Его собственный U-сектор играет роль скалярной части, а контекстные сектора образуют векторную часть:

$$W_{\mathrm{imag}}=\left(W^{I,U},\left[W^{I,Q},W^{I,K},W^{I,C},W^{I,D}\right]\right).$$

Полная вложенная структура имеет вид

$$W_{\mathrm{eff}}=\left(W_{\mathrm{real}},\left(W^{I,U},\mathbf W^{I,\mathrm{ctx}}\right)\right).$$

Я называю её псевдопаравекторной из-за организации «скаляр плюс вектор»; при этом реализация не заявляет полный Clifford product или полный базис клиффордовой алгебры.

Название LoomFormer относится к тому, как эти пути переплетаются: обычное каузальное attention, история по глубине, фазовый trace и операторный carry участвуют в одном forward. Суффикс -former оставлен потому, что каузальное attention никуда не исчезло; Tria не заменяет attention.

Референсный чекпойнт

Опубликованные чекпойнты находятся на Hugging Face:

Репозиторий чекпойнтов на Hugging Face

Референсный запуск, описанный в этом README, использовал:

Свойство Значение
Параметры 112,999,621
Блоки 10
Ширина модели 768
Query heads 8 × 96
GQA group size 4, следовательно 2 KV-heads
Hidden-width FFN 3072
Длина последовательности 1024
Temporal window Tria 128 токенов / 8 окон на последовательность
Точность bf16
Embeddings untied
Датасет FSS1STR
Железо обучения одна NVIDIA L40S
Наблюдаемая скорость примерно 27.3k tok/s

На шаге 150,000 мной был выполнен полный последовательный eval на 45,040,704 токенах:

Метрика Значение
Full evaluation loss 3.2584 nats/token
Bits per token 4.7009
Bits per byte 1.3288

Номинальный бюджет на 120,000 шагов составлял 4,608,000,000 токенов, или примерно 40.8 обучающих токена на параметр. Исходный датасет содержит около 4,684,230,241 токена, или 41.5 токена данных на параметр. Опубликованный запуск был продолжен после номинального бюджета до шага 150,000.

Эти числа относятся к одному датасету, токенизатору, конфигурации и железу. Я не выдаю их за межмодельный benchmark.

Архитектура в одном маршруте

Для блока l и токена t основной путь выглядит так:

h_l,t
  │
  ├── causal GQA ───────────────► attention output, q, attended-k, v-context
  │
  ├── DepthAttn по depth-history ─► attention skip
  │
  └── LayerNorm(skip + attention)
                    │
                    ▼
                  u_l,t
                    │
                    ├── второй DepthAttn ─► FFN skip и depth-context d_l,t
                    │
                    ├── Paraplex(u, q, attended-k, context, d, Tria gate)
                    │         └── R_l,t,h , I_l,t,h , O_l,t,h
                    │
                    ├── depth-композиция Tria ─► C_l,t,h и p_l,t,h
                    │
                    └── LayerNorm(FFN skip + W2 O)
                              │
                              ▼
                            h_l+1,t

После последнего блока каждый токен имеет законченный depth-carrier D_t формы [H,3,3]. Вне стека блоков эти carrier-матрицы композируются по токенам до фиксированной границы окна или явной границы <CARRY>. Сработавший endpoint используется двумя путями:

D_s, D_s+1, ... , D_b
          │
          └── temporal composition ─► accT_b
                                       ├── key/value для sparse final cross-attention
                                       └── seed для первого Tria-слоя следующего сегмента

Refeed-seed потребляется один раз в начале следующего сегмента. Затем temporal accumulator перезапускается от нового depth-composed токена, поэтому boundary carrier не умножается в тракт дважды.

Как это работает

Нотация

Символ Значение
u_t вход FFN из residual stream на токене t
Q, K^ctx, V^ctx query, attended-key и attention-value контексты каузального GQA
d_t depth context второго чтения DepthAttn; метка сектора D внутри W_imag
C (метка сектора) сектор V^ctx внутри W_imag
m_s pooled-чтение DepthAttn на sublayer s
R, I, O real-, фазовая и выходная координаты Paraplex
A положительная амплитуда PvPowLU softplus(g)
a, b, c совместно RMS-масштабированные и ограниченные попарные отношения из RI, RO, IO
K(a,b,c) кососимметричный генератор Tria (не путать с attention keys K)
T_{l,t,h} локальный оператор Tria
C_l depth carrier после слоя l; D_t = C_{L,t} — законченный depth carrier
accT_t temporal-аккумулятор Tria (в коде accT)
b_k позиция сработавшей temporal-границы
I_3, 𝒩(·) единичная матрица; локальная RMS-нормализация по всем девяти элементам

Каузальное GQA

Attention-путь — обычное каузальное grouped-query attention с YaRN-scaled rotary position embeddings. Несколько query-heads могут разделять один key/value-head.

Для одной query-head, без batch- и head-индексов:

$$A_t = \mathrm{softmax}\!\left(\frac{Q_tK_{\le t}^{\top}}{\sqrt{d_h}}\right), \qquad V^{\mathrm{ctx}}_t = A_tV_{\le t}, \qquad K^{\mathrm{ctx}}_t = A_tK_{\le t}.$$

LoomFormer сохраняет и attention context Vctx, и attended key context Kctx. Обычный attention-output проецируется обратно в residual stream, а Q, Kctx и Vctx одновременно передаются в Paraplex.

Реализация causal attention поддерживает flat, chunked и token-by-token режимы. Packed-строки SFT несут линейный layout segment_ids/cu_seqlens. При attn_impl: auto на Ampere и новее layout передаётся в проверенный varlen-backend FlashAttention или Transformer Engine. Компактный SDPA-путь на Pascal и в reference execution обрабатывает каждый документ отдельно. Temporal chunks используют заранее построенные gather-plans для K/V-history. Document-layout metadata имеет размер O(BT) и сохраняет изоляцию packed examples.

Глубина и токенная плотность attention задаются при сборке модели. attn_layers — отсортированный 1-based список; отсутствие поля или null оставляет attention во всех слоях. Неактивные слои не содержат QKV/output-параметров, не выделяют KV cache, дают нулевой attention residual и используют текущий conditioning context ближайшего нижнего активного слоя. Первый слой должен оставаться активным.

При attn_token_stride: 2 attention сохраняет и читает только половину document-relative позиций. Режим shared использует offset 0 во всех активных слоях, а staggered чередует offsets по ordinal активного слоя. Выбранные токены вычисляют Q/K/V и записывают компактный KV; gaps получают унаследованный context, а первый активный слой использует document-safe sample-and-hold. RoPE и Tria работают с логическими позициями, cache_pos считает физические KV-записи. Поэтому логический конфиг 16K выделяет примерно 8K KV-позиций на stride-2 active layer. Sparse schedule меняет обучаемую архитектуру; включать его только после dense-training нельзя.

Mixer слоя выбирается конструктором один раз; неактивные блоки не выполняют per-token runtime branch:

┌───────────────────────────────────────────┐
│               Decoder block               │
└─────────────────────┬─────────────────────┘
                      │ выбор конструктора
          ┌───────────┴───────────┐
          ▼                       ▼
┌───────────────────────┐  ┌───────────────────────────┐
│    AttentionMixer     │  │  InheritedContextMixer   │
│ активный слой         │  │ неактивный слой          │
│ QKV + O + RoPE        │  │ 0 attention-параметров   │
│ компактный physical KV│  │ 0 KV-памяти, passthrough │
└───────────────────────┘  └───────────────────────────┘

Для attn_layers: [1, 3, 5], stride 2 и staggered offset определяется ordinal среди активных attention-слоёв, а не абсолютным номером decoder-layer:

document-relative position:       0  1  2  3  4  5  6  7
config layer 1 (ordinal 0):       1  0  1  0  1  0  1  0
config layer 3 (ordinal 1):       0  1  0  1  0  1  0  1
config layer 5 (ordinal 2):       1  0  1  0  1  0  1  0
неактивные layers 2 и 4:          ·  ·  ·  ·  ·  ·  ·  ·

Каждый packed document перезапускает узор с logical position zero. Selection уплотняет только физический attention-путь:

logical abs_pos:       0  1  2  3  4  5  6  7
писать KV?:            1  0  1  0  1  0  1  0
physical cache_pos:    0  ·  1  ·  2  ·  3  ·
                       │     │     │     │
selected token ── gather ── QKV/RoPE ── compact causal attention ── scatter
gap token      ── inherited context / document-safe sample-and-hold
Tria + RoPE    ── сохраняют полную logical timeline 0…7

Типовые расписания:

# Только токенная sparsity; attention остаётся во всех decoder layers.
attn_layers: null
attn_token_stride: 2
attn_token_schedule: staggered

# Attention есть только в первых двух слоях; каждый хранит треть токенов.
attn_layers: [1, 2]
attn_token_stride: 3
attn_token_schedule: staggered

Attention selection никогда не меняет LM-targets или SFT loss mask. Dense, layer-sparse и checkerboard модели оцениваются на одном shifted target tensor; различаются только hidden states и, следовательно, logits.

Разрешённое расписание сохраняется в checkpoints и AIO config. Перед сборкой модели DDP проверяет, что все ranks получили один и тот же Config. Resume запрещает менять attention-архитектуру; для намеренной dense-to-sparse инициализации используется init_checkpoint.

DepthAttn

Обычный residual-блок всегда прибавляет непосредственно предыдущее hidden-state. LoomFormer хранит keys и values состояний, уже произведённых раньше по глубине сети.

Для sublayer s обучаемый запрос q_s читает эту историю:

$$\pi_{s,j} = \mathrm{softmax}_{j} \left(\frac{\langle q_s,k_j\rangle}{\sqrt{d_h}}\right), \qquad m_s = \sum_{j\le s}\pi_{s,j}v_j, \qquad \mathrm{skip}_s = W^{\mathrm{depth}}_{o,s}m_s.$$

Softmax идёт по оси глубины, а не по токенам. В каждом блоке выполняются два чтения:

  • первое даёт skip вокруг causal attention;
  • второе даёт FFN-skip и depth context, который поступает в Paraplex.

Readout-проекция может быть общей для всех sublayers или отдельной для каждой. При depth_attn_qkv_rms: true код независимо применяет к каждому вектору по последней размерности

$$\mathrm{FixRMS}_{\rho}(x) = x\frac{\rho}{\sqrt{\mathrm{mean}_i(x_i^2)+\varepsilon}}.$$

Целевые значения совпадают с масштабами инициализации:

$$q_s\leftarrow\mathrm{FixRMS}_{0.88/\sqrt{d_h}}(q_s), \qquad k_j\leftarrow\mathrm{FixRMS}_{0.88}(k_j), \qquad v_j\leftarrow\mathrm{FixRMS}_{0.88\beta_{\mathrm{init}}}(v_j),$$

где βinit=(8L)^(-1/4) для residual_init: beta и βinit=1 для fanin. Приведённая выше формула attention вычисляется после этих преобразований.

residual_branch_rms_cap: c применяет

$$\mathrm{CapRMS}_{c}(x) = x\min\!\left( 1,\frac{c}{\sqrt{\mathrm{mean}_i(x_i^2)+\varepsilon}} \right)$$

отдельно к depth skip и attention/FFN-ветви непосредственно перед каждым сложением и LayerNorm. Тихие ветви сохраняют свой масштаб. Flat-, chunked- и token-by-token пути используют одинаковый порядок.

Paraplex

Для token representation u и hidden-нейрона h обычная повышающая FFN-проекция сначала формирует зависящий от весов член

$$X_{t,h}=(W_{\mathrm{real}}u_t)_h.$$

Если в слой не входит сигнал Tria,

$$R_{t,h}=X_{t,h}+b_h.$$

При наличии Tria-gate масштабируется только X, но не bias:

$$R_{t,h}=X_{t,h}\left(1+\gamma_l p_{t,h}\right)+b_h, \qquad \gamma_l=0.25\tanh(\widehat\gamma_l).$$

В векторной форме

$$R_t= \mathrm{diag}\!\left(\mathbf 1+\gamma_l p_t\right) W_{\mathrm{real},l}u_t+b_l.$$

Хранимая матрица W_real не переписывается механизмом Tria. Carrier создаёт зависимый от токена диагональный gain на выходных строках проекции. RMS carrier по девяти элементам приблизительно равен единице, поэтому |vec(C)[j]| ≤ 3, а softmax-смесь слотов даёт |p[t,h]| ≤ 3. При фиксированном локальном ограничении |γ[l]| ≤ 0.25 множитель остаётся в [0.25, 1.75]. Bias сохраняется как несмасштабированное начало координат.

Для самого gate:

$$\frac{\partial R_{t,h}}{\partial X_{t,h}}=1+\gamma_l p_{t,h}, \qquad \frac{\partial R_{t,h}}{\partial b_h}=1.$$

Carrier меняет чувствительность к Wx, но не масштабирует производную по bias.

Фазовый аргумент является структурированной проекцией пяти источников:

$$\beta_{t,h}= W^{\mathrm{imag}}_h \begin{bmatrix} Q_t\\\ K^{\mathrm{ctx}}_t\\\ V^{\mathrm{ctx}}_t\\\ u_t\\\ d_t \end{bmatrix} +b^{\mathrm{imag}}_h.$$

Фазовые веса ограничены секторами. В режиме head группа hidden-нейронов читает Q/Kctx/Vctx/Depth-каналы своего query-head и полный model-state u. В режиме open Q остаётся head-local, а Kctx, Vctx, u и Depth являются полноширинными источниками с доступом через границы heads. Компактный параметр w1_imag хранит только живые секторные веса; dense-матрица reference GEMM является временным expansion, а не второй обучаемой матрицей.

Текущая фаза ограничивается насыщаемой синусоидальной картой:

$$I^{\mathrm{base}}_{t,h} = \sin\!\left( \frac{\pi}{2} \frac{\beta_{t,h}}{\sqrt{1+\beta_{t,h}^{2}}} \right).$$

Базовая фаза того же нейрона на предыдущем токене может входить через обучаемый trace-коэффициент:

$$z_{t,h}=I^{\mathrm{base}}_{t,h}+w^{\mathrm{trace}}_h I^{\mathrm{base}}_{t-1,h}, \qquad I_{t,h}=\frac{z_{t,h}}{\sqrt{1+z_{t,h}^{2}}}.$$

Границы документов сбрасывают trace. При full-sequence training вычисление остаётся параллельным: позиция t читает сдвинутый Ibase позиции t-1, а не уже обогащённый I_t.

Показанные фазовые формулы точно описывают forward. Backward настраивается отдельно: phase_grad_mode: floor ограничивает снизу cosine-множитель локальной производной, а secant вне малой окрестности использует секущую к EMA-якорю радиуса фазы.

PvPowLU

В референсном Paraplex/PvPowLU-пути положительная амплитуда строится как

$$A_{t,h}=\mathrm{softplus}(g_{t,h})>0.$$

По умолчанию gate самореферентный: g=R. Для transplant донорских моделей существует опциональный независимый gate_proj.

Real- и phase-координаты смешиваются до выходной активации:

$$P_{t,h}=R_{t,h}+A_{t,h}I_{t,h}.$$

При параметре степени m PvPowLU использует положительный PowLU-gate

$$G(A)=A^{\frac{m}{\sqrt{A}+1}}\sigma(A),$$

а выходная координата Paraplex равна

$$\begin{aligned} O_{t,h} &=P_{t,h}G(A_{t,h}) \\\ &=R_{t,h}G(A_{t,h})+I_{t,h}A_{t,h}G(A_{t,h}). \end{aligned}$$

FFN-ветвь, возвращаемая в model stream:

$$F_t=W_2O_t.$$

Это не обычный GLU. Gate управляет и внешним nonlinear gain, и количеством фазы, добавленным к real-координате до применения этого gain.

Эффективный псевдокомплексный вес

Для каждого Paraplex-нейрона W_real и W_imag участвуют в одном forward-преобразовании. Их общую по входу часть можно записать как

$$W^{(u)}_{\mathrm{eff},h}=\left(W^{R}_{h},W^{I,U}_{h}\right).$$

где

$$R_h=W^{R}_{h}u+b^{R}_{h},$$ $$\begin{aligned} \beta_h={}&W^{I,U}_{h}u+W^{I,Q}_{h}Q+W^{I,K}_{h}K_{\mathrm{ctx}} \\\ &+W^{I,C}_{h}C+W^{I,D}_{h}D+b^{I}_{h}. \end{aligned}$$

Фазовая карта превращает β_h в I_h, после чего PvPowLU объединяет R_h и I_h в выход нейрона. Таким образом, W_real является real-частью эффективного веса, а W_imag — его псевдопаравекторной imaginary-частью. Сектор U даёт imaginary-проекцию того же u, а остальные сектора задают её контекстно-зависимые векторные координаты.

Для самореферентного режима по умолчанию A(R)=softplus(R) и A'(R)=σ(R). Прямые производные Paraplex равны

$$\frac{\partial O}{\partial R} = \left(1+I\sigma(R)\right)G(A) + \left(R+AI\right)G'(A)\sigma(R),$$ $$\frac{\partial O}{\partial I}=A\,G(A).$$

Поэтому update строки real-проекции содержит фазозависимый коэффициент

$$\frac{\partial \mathcal L}{\partial W_{\mathrm{real},h}} = \frac{\partial \mathcal L}{\partial R_h} \left(1+\gamma_l p_h\right)u^{\top},$$

а фазовая проекция получает градиенты через I(β) и через все использования R, I, O в Tria. Обученное значение W_real тем самым коадаптируется с w_imag, хотя это разные parameter tensors. В этом точном смысле матрица W, позднее используемая в Wx+b, была обучена под влиянием фазового пути.

Есть и межслойный путь. w_imag,l меняет I_l, затем O_l, затем W_{2,l}O_l, то есть следующий residual stream. Real-проекция следующего блока умножает уже изменённый вход:

$$w^{\mathrm{imag}}_l \longrightarrow I_l \longrightarrow O_l \longrightarrow W_{2,l}O_l \longrightarrow u_{l+1} \longrightarrow W_{\mathrm{real},l+1}u_{l+1}.$$

Код также поддерживает GELU и ungated single-input PowLU. Формулы выше относятся к конфигурации Paraplex/PvPowLU.

Один нейрон от L1H1 к L2H1

Рассмотрим один токен t и один согласованный hidden-канал h=1. Ниже индексы t и h опущены.

В первый слой не входит Tria-gate:

$$R_1=w^{\mathrm{real}}_1u_1+b_1.$$

Его фаза и выход:

$$\beta_1=w^{\mathrm{imag}}_1 [Q_1,K^{\mathrm{ctx}}_1,V^{\mathrm{ctx}}_1,u_1,d_1]+b^{\mathrm{imag}}_1,$$ $$I_1=\mathrm{phase}(\beta_1,I^{\mathrm{base}}_{1,t-1}), \qquad A_1=\mathrm{softplus}(R_1),$$ $$P_1=R_1+A_1I_1, \qquad O_1=P_1G(A_1).$$

Обычный FFN-путь является dense по каналам:

$$F_1=W_{2,1}O_1, \qquad h_2=\mathrm{LN}_{\mathrm{ffn},1} \left(S^{\mathrm{ffn}}_1+F_1\right).$$

Следовательно, фаза hidden-нейрона H1 может влиять на множество координат model-width через W2. Второй блок строит собственный attention-output и depth-skip из h2:

$$u_2=\mathrm{LN}_{\mathrm{attn},2} \left(S^{\mathrm{attn}}_2+\mathrm{Attn}_2(h_2)\right).$$

Параллельно R1, I1, O1 создают первый carrier Tria C1. Первый слой имеет одно обучаемое распределение по девяти слотам carrier:

$$w^{\mathrm{slot}}_1=\mathrm{softmax}(\lambda_1), \qquad p_1=\left\langle w^{\mathrm{slot}}_1,\mathrm{vec}(C_1)\right\rangle.$$

Тот же hidden-index второго слоя получает этот скаляр через identity-anchored gate:

$$X_2=w^{\mathrm{real}}_2u_2, \qquad R_2=X_2\left(1+\gamma_2p_1\right)+b_2.$$

Затем слой строит собственную фазу и выход:

$$\beta_2=w^{\mathrm{imag}}_2 [Q_2,K^{\mathrm{ctx}}_2,V^{\mathrm{ctx}}_2,u_2,d_2]+b^{\mathrm{imag}}_2,$$ $$I_2=\mathrm{phase}(\beta_2,I^{\mathrm{base}}_{2,t-1}), \qquad A_2=\mathrm{softplus}(R_2),$$ $$P_2=R_2+A_2I_2, \qquad O_2=P_2G(A_2).$$

Конкретный маршрут L1H1 → L2H1 содержит два одновременных пути:

phase/output L1H1 ──► dense W2 ──► residual stream ──► attention/depth ──► u2 ──► Wreal,2
          │
          └─────────► C1[H1,3,3] ──► nine-slot selector ──► gain на X2[H1]

Непосредственный Tria-gate сохраняет hidden-index. Межканальное смешивание выполняют W2, следующий W_real, attention, DepthAttn и финальный population pool.

Tria

Приведённые ниже уравнения точно определяют механику Tria, но сами по себе не раскрывают её выученную семантическую роль. В обученных чекпойнтах carrier является каузально активным: его отключение меняет дальнейшее вычисление и авторегрессионные логиты. Однако для его состояний пока не удалось построить устойчивый токенный словарь или простую человекочитаемую интерпретацию. Возможно, полезная функция Tria выражается через распределённую геометрию популяции, композицию операторов и траектории по глубине и времени, а не через отдельно декодируемые координаты.

Tria получает координаты Paraplex R, обогащённую фазу I и активированный pre-W2 выход O для каждого токена и hidden-канала. Сначала строятся

$$x=(RI,RO,IO), \qquad \sigma_x=\sqrt{\frac{x_1^2+x_2^2+x_3^2}{3}+\varepsilon},$$

после чего три отношения совместно RMS-масштабируются и ограничиваются:

$$a=\tanh\!\left(\frac{RI}{\sigma_x}\right), \qquad b=\tanh\!\left(\frac{RO}{\sigma_x}\right), \qquad c=\tanh\!\left(\frac{IO}{\sigma_x}\right).$$

Общий положительный делитель сохраняет их относительные масштабы и знаки. За пределами epsilon-floor отображение инвариантно к общему масштабированию R, I, O. До совместного RMS и tanh произведения удовлетворяют

$$(RI)(RO)(IO)=R^2I^2O^2\ge 0.$$

Они инвариантны к одновременному перевороту знаков (R,I,O)→(-R,-I,-O). Поэтому Tria реагирует на относительные знаки и масштабы, а не на произвольную глобальную знаковую конвенцию.

Реализация помещает три ограниченных отношения в кососимметричный генератор, умножает на фиксированный осевой поворот и затем композирует полученный carrier. Девять слотов selector относятся к девяти элементам carrier после поворота и композиции.

Генератор имеет вид

$$K(a,b,c)= \begin{bmatrix} 0 & -c & b\\\ c & 0 & -a\\\ -b & a & 0 \end{bmatrix}, \qquad K^{\top}=-K.$$

Локальный оператор Tria:

$$T_{l,t,h}=\left(I_3+\alpha K_{l,t,h}\right)R_{\mathrm{axis}(l)},$$

где Raxis — фиксированный поворот на +90° вокруг одной координатной оси. Ось циклически меняется по глубине. α — небольшой коэффициент carrier, задаваемый конфигурацией или startup calibration.

Совместное ограничение даёт

$$\kappa_2(I_3+\alpha K) \le \sqrt{1+3\alpha^2},$$

а фиксированный поворот не меняет сингулярные значения.

Каждая нормализация локальна для одной матрицы [batch, token, hidden] и использует RMS всех девяти элементов:

$$\mathcal N(M)= \frac{M}{ \sqrt{\frac{1}{9}\sum_{i=1}^{3}\sum_{j=1}^{3}M_{ij}^{2}+\varepsilon} }.$$

Из конструкции следует полезный инвариант. Если R=I=O=0, то a=b=c=0, но

$$T=R_{\mathrm{axis}},$$

а не нулевая матрица. Нулевая локальная RIO-модуляция является базовым режимом переноса. Локальный Jacobian отображения (RI,RO,IO) равен нулю только в полном начале координат. Если один партнёр слабой компоненты ненулевой, соответствующее произведение даёт производную; если ненулевы оба партнёра, существуют два таких пути. Полная сеть дополнительно сохраняет фазовый путь, положительную амплитуду softplus, обычный FFN-путь и предыдущий carrier.

Один шаг Tria от L1 к L2

Для одного токена и hidden-канала первый слой инициализирует depth-carrier:

$$C_1=\mathcal N(T_1).$$

Второй слой слева композирует свой локальный оператор с предыдущим carrier:

$$C_2=\mathcal N(T_2C_1).$$

Общая depth-рекурсия:

$$C_l=\mathcal N(T_lC_{l-1}).$$

Для каждого не последнего блока девять элементов C_l flatten-ятся и сводятся обучаемым slot-распределением слоя:

$$p_l= \sum_{j=1}^{9} \mathrm{softmax}(\lambda_l)_j \mathrm{vec}(C_l)_j.$$

Следующий блок применяет это значение только к зависящему от весов real-члену:

$$X_{l+1}=W_{\mathrm{real},l+1}u_{l+1},$$ $$R_{l+1}=X_{l+1}\odot\left(\mathbf 1+\gamma_{l+1}p_l\right)+b_{l+1}.$$

Таким образом, Tria меняет per-neuron gain у Wx, не затрагивая b. Gate стартует строго как identity, поскольку его raw-коэффициент инициализируется нулём. У последнего блока нет selector: после него отсутствует следующий Paraplex-слой, а его carrier становится законченным depth-carrier D_t данного токена.

Temporal carry от границы до границы

Обозначим

$$D_t=C_{L,t}$$

законченный depth-composed carrier, полученный после стека блоков для токена t. Temporal Tria композирует эти готовые матрицы вне глубины сети.

Внутри сегмента без reset:

$$\mathrm{accT}_t=\mathcal N(D_t\,\mathrm{accT}_{t-1}).$$

На границе документа накопление начинается с локального depth-carrier:

$$\mathrm{accT}_t=\mathcal N(D_t).$$

Активный chunked training и token-by-token inference используют streaming endpoint recurrence. Фиксированная граница планируется не позднее чем через W=tria_temporal_window токенов; явный токен <CARRY> может сработать раньше. Hard boundary подавляется, если следующий токен начинает новый документ, поскольку seed для того же документа отсутствует.

Пусть валидная граница сработала на токене b_k. Endpoint

$$\mathrm{accT}_{b_k} = \mathcal N\!\left( D_{b_k}D_{b_k-1}\cdots D_{s_k} \right)$$

содержит depth-carriers, накопленные от начала текущего сегмента s_k, с локальной нормализацией после каждой реализованной композиции.

PolARM

На сработавшей границе PolARM выполняет одну Gram-коррекцию до сохранения carrier как key для final-CA и seed следующего сегмента:

$$G_b=\mathrm{accT}_{b_k}^{\top}\mathrm{accT}_{b_k}, \qquad s_b=\frac{\mathrm{tr}(G_b)}{3},$$ $$\widetilde{\mathrm{accT}}_{b_k} = \mathrm{accT}_{b_k} \left[ I_3-\frac{\beta_{\mathrm{P}}}{2} \left(\frac{G_b}{\max(s_b,\varepsilon)}-I_3\right) \right], \qquad 0\le\beta_{\mathrm{P}}<1.$$

Для первого токена следующего сегмента, t_0=b_k+1, boundary endpoint вводится в первый Tria-слой:

$$C_{1,t_0}^{\mathrm{seed}} = \mathcal N\!\left(T_{1,t_0}\,\widetilde{\mathrm{accT}}_{b_k}\right).$$

Остальные слои выполняют обычную depth-рекурсию:

$$C_{l,t_0}^{\mathrm{seed}} = \mathcal N\!\left(T_{l,t_0}C_{l-1,t_0}^{\mathrm{seed}}\right), \qquad D_{t_0}^{\mathrm{seed}}=C_{L,t_0}^{\mathrm{seed}}.$$

После потребления seed temporal accumulator перезапускается от нового законченного depth-carrier:

$$\mathrm{accT}_{t_0}=\mathcal N\!\left(D_{t_0}^{\mathrm{seed}}\right).$$

Для следующих токенов до очередной границы:

$$\mathrm{accT}_t=\mathcal N(D_t\,\mathrm{accT}_{t-1}), \qquad t_0<t\le b_{k+1}.$$

Reset сделан намеренно. accT̃_{b_k} уже входит в D_{t_0}^{seed} через композицию первого слоя; повторное умножение старого endpoint в temporal recurrence учло бы его дважды.

Полный boundary-маршрут:

last-block carriers D_s ... D_b
          │
          └── streaming temporal composition ──► accT_b ──► PolARM ──► accT̃_b
                                                   │
                                                   ├── key/value final-CA
                                                   └── T_1,next @ accT̃_b
                                                         │
                                                         ├── композиция по всем слоям
                                                         └── restart temporal state от D_next^seed

В full-sequence PyTorch reference также имеется ассоциативный segmented scan с удваивающимися offset. Он сохраняет тот же порядок матриц и reset-семантику, но текущий chunked training использует endpoint recurrence выше.

Финальное чтение Tria

Сработавший temporal endpoint имеет форму [H,3,3]. Reader не обязан сначала материализовать отдельное k-мерное представление каждого hidden-нейрона. Реализация оценивает девять сырых слотов, pooling-ует их по hidden-population и только после этого применяет общую value-проекцию.

Пусть x_{b,h}=vec(accT̃_{b,h}). Обучаемый query qpool, независимая slot-key-проекция и обучаемый положительный logit scale задают

$$s_b= \mathrm{softplus}(\widehat\tau)\, \frac{W_{\mathrm{key}}^{\top}q_{\mathrm{pool}}} {\max\!\left(\left\|W_{\mathrm{key}}^{\top}q_{\mathrm{pool}}\right\|_2,\varepsilon\right)}.$$

Population weights:

$$\rho_{b,h} = \mathrm{softmax}_h\!\left( \langle s,x_{b,h}\rangle \right).$$

Pooled boundary vector:

$$\bar x_b=\sum_h\rho_{b,h}x_{b,h}, \qquad z_b=\mathrm{Up}\!\left(W_{\mathrm{reader}}\bar x_b+b_{\mathrm{reader}}\right).$$

Key/value становятся только сработавшие границы. Final cross-attention использует общую проекцию для query и key и отдельную проекцию value:

$$q_t=W_{qk}h_t, \qquad k_b=W_{qk}z_b, \qquad v_b=W_vz_b.$$

Для границ, разрешённых causal- и document-mask:

$$\xi_{t,b} = \mathrm{softmax}_{b\le t} \left( \frac{q_tk_b^{\top}}{\sqrt{d}} \right),$$ $$h'_t=h_t+\eta\sum_{b\le t}\xi_{t,b}v_b, \qquad \eta=\eta_{\max}\tanh(\widehat\eta).$$

Boundary доступен для чтения на собственной позиции токена и на последующих разрешённых позициях. Если ни одна граница не сработала, путь является точным identity. У Tria нет отдельной LM-head и auxiliary target; она обучается только через влияние на обычный next-token loss.

Runtime-состояние и VRAM

Расчёты ниже относятся к фактическому inference-path в loomchat.py. Prompt прогоняется последовательными вызовами Model.step, поэтому модель не хранит в VRAM полный Tria-тензор [B,T,H,3,3] и не накапливает DepthAttn-history по времени. Training activations, optimizer states, gradients, CUDA workspaces и сохранения autograd считаются отдельно.

DepthAttn

В референсном режиме shared DepthAttn содержит

$$2N^2+N^2+2LN=3N^2+2LN$$

параметров: общую K/V-проекцию, общую output-проекцию и по одному query на sublayer. При N=768, L=10 это 1,784,832 параметра, около 1.58% от 112,999,621 параметров референсной модели. Сырые bf16-веса занимают около 3.4 MiB.

DepthAttn читает историю по глубине сетки, а не по времени токенов. На одном incremental-шаге создаются два временных тензора

$$K_{\mathrm{depth}},V_{\mathrm{depth}} \in \mathbb{R}^{B\times 1\times 2L\times N},$$

поэтому их суммарный размер равен

$$4BLN$$

bf16-элементам. При B=1, L=10, N=768 оба тензора вместе занимают 60 KiB. Этот объём не зависит от длины чата: тензоры строятся для текущего токена и освобождаются после шага.

Full-sequence training forward может материализовать такие же depth-состояния сразу для span длины T, то есть 4BTLN элементов. Это activation cost training/full-sequence path, а не постоянный autoregressive cache.

Tria

В incremental inference у Tria нет временной оси в хранимом состоянии. В наиболее ёмкой точке одного token-step одновременно существуют ровно три carrier-sized тензора:

$$C_{L-1},\quad C_L,\quad \mathrm{accT} \in \mathbb{R}^{B\times H\times 3\times 3},$$

где C_{L-1} — carrier предыдущего depth-слоя, C_L — результат текущего слоя, а accT — temporal accumulator на хвосте сетки. Сырой bf16 working set равен

$$3\cdot 9BH$$

элементам. При B=1, H=3072 это 162 KiB. Размер не растёт с числом токенов в чате.

Дополнительно модель хранит по одному phase trace [B,H] на каждый Paraplex-слой. Для десяти слоёв и H=3072 все bf16 phase traces вместе занимают 60 KiB; pending-fire flag пренебрежимо мал. Итого фиксированное рекуррентное состояние Paraplex/Tria составляет около 222 KiB на элемент batch, без временных R/I/O текущего слоя.

Тензоры [B,T,H,3,3], возникающие в chunked training, являются training activations. Они не сохраняются вдоль авторегрессионного диалога и не должны считаться Tria-cache, растущим с длиной чата.

Кэши чата и контекст на 10k токенов

Обычный GQA-cache хранит K и V каждого слоя:

$$2BLT N_{\mathrm{kv}}d_h$$

bf16-элементов. При B=1, L=10, N_kv=2, d_h=96 получается 7.5 KiB на каждый закэшированный токен:

Настроенный контекст Обычный GQA K/V
1,024 токена 7.5 MiB
10,000 токенов 73.24 MiB

Incremental cache финального Tria cross-attention сейчас заранее выделяет два плотных буфера

$$K_{\mathrm{CA}},V_{\mathrm{CA}} \in \mathbb{R}^{B\times \mathrm{seq\_len}\times N}.$$

Валидными становятся только строки сработавших границ, но выделенная память определяется полным seq_len. При N=768 и bf16:

Настроенный контекст Выделение final-CA K/V Валидные fixed-grid строки при W=128
1,024 токена 3.0 MiB максимум 7 внутренних fires
10,000 токенов 29.30 MiB максимум 78 внутренних fires

Следовательно, конфигурация для чата на 10,000 токенов при B=1 и bf16 выделяет примерно

$$73.24\ \mathrm{MiB}+29.30\ \mathrm{MiB}=102.54\ \mathrm{MiB}$$

на два семейства K/V-кэшей, зависящих от длины контекста. Фиксированные DepthAttn scratch, три живых Tria-carrier и десять phase traces добавляют менее 0.3 MiB. Веса модели, overhead аллокатора, attention workspaces и logits сюда не входят.

В текущем chat-path нет rolling-cache wraparound. Поэтому диалог на 10k токенов требует checkpoint/config с seq_len не меньше 10,000; опубликованная конфигурация seq_len=1024 такой диалог без смены context policy не удержит.

Размещение и калибровка KV

loomchat.py сохраняет snapshots точных префиксов, поэтому новый ход префиллит только suffix после самого длинного закэшированного префикса. Параметр --kvstorage задаёт место постоянного хранения layer K/V:

Значение Исполнение
same K/V и attention остаются на GPU модели.
cpu K/V остаются в pinned RAM и передаются через CUDA staging ring, пока online softmax обрабатывает предыдущие chunks.
cuda:N K/V остаются на GPU N; полная attention-редукция без параметров выполняется на этой GPU, а attention context и attended-key context возвращаются на GPU модели.

При запуске chat-runtime измеряет настоящий forward модели на последовательности до 2048 токенов, throughput инкрементального Model.step, время транспорта и скорость потребления online attention. Калибратор проверяет chunks от 128 токенов до длины калибровочного контекста, выбирает самый быстрый измеренный pipeline и рассчитывает динамический preload. К оценке, достаточной для непрерывного потребления, добавляются два safety-chunks. CPU-storage использует pinned buffers и раздельные copy/compute streams. Для remote CUDA фиксируется наличие peer access; обычные CUDA transfers обеспечивают host-staged маршрут, когда peer access отсутствует.

Размещение можно менять внутри сессии командами /kvstorage same, /kvstorage cpu и /kvstorage cuda:N. Смена compute device, dtype или KV placement инвалидирует старые snapshots и запускает повторную калибровку транспорта.

CUDA-кернели и replay

LoomFormer не обучает Paraplex- и Tria-пути как длинную цепочку обычных поэлементных операций PyTorch. В репозитории лежат полноценные CUDA-исходники в kernels/: device-код находится в *_kernel.cuh, отдельные translation units для просмотра PTX — в *_kernel.cu, а ATen/PyBind-launchers — в *_launcher.cu.

Текущее дерево сообщает о 17 группах кернелей, собранных в семь расширений:

Расширение Что выполняется fused-путём
loomformer_beta_space Компактная проекция W_imag по открытым секторам Q/K/C/U/D и её backward.
loomformer_paraplex Фазовая рекурсия, trace, amplitude, фазовое смешивание, выход PvPowLU, обновление anchor и редукции градиентов параметров.
loomformer_phase_sin Отдельная ограниченная фазовая карта и её custom gradient modes.
loomformer_pvpowlu Отдельные forward и backward для PvPowLU.
loomformer_depth_attn_online Online-softmax forward/backward DepthAttn по фиксированной истории глубины.
loomformer_tria_carry Одиннадцать групп Tria: инициализация, seeded initialization, gated-варианты, depth steps, depth replay, slot mixing, population pooling, temporal carry, endpoint-only temporal carry и sparse final cross-attention.
loomformer_packed_gather Document-major packing temporal K/V-history одним запуском для varlen attention и scatter-backward в исходные chunks.

Fused Paraplex-path сначала вычисляет beta_space, затем внутри CUDA выполняет построение фазы, temporal phase trace, amplitude, P=R+AI, PvPowLU и необходимые величины backward. Каждое алгебраическое подвыражение не материализуется отдельным Python-level тензором. Tria-кернели держат три совместно RMS-масштабированных отношения, девять элементов локального оператора 3x3, матричное произведение и локальную RMS-нормализацию матрицы в регистрах или локальном состоянии кернеля; промежуточные локальные матрицы не сохраняются как отдельное состояние модели.

Recompute и depth replay

Во время chunked training все неинициальные depth-шаги Tria используют custom replay tape. Обычный autograd-путь должен был бы сохранять на каждом слое предыдущий carrier

$$C_{L-1}\in\mathbb{R}^{B\times T\times H\times3\times3}$$

и его normalization scale. В replay-режиме forward сохраняет локальные R/I/O, которые уже нужны Paraplex-пути, а для gated-step также selector weights, но не сохраняет C_{L-1} и scale данного шага.

На backward отсутствующий предыдущий carrier восстанавливается двумя способами:

  • если текущий FP32-carrier ещё доступен, обратимый локальный Tria-фактор позволяет сделать аналитический reverse-step;
  • для bf16/fp16 или если текущий carrier уже освобождён отдельный кернел depth_replay восстанавливает нужное состояние, повторяя записанные Tria-факторы от seed текущего сегмента.

Replay не меняет forward-уравнение. Градиенты по-прежнему вычисляются для R, I, O, предыдущего carrier и девяти slot-selector weights. Цена экономии памяти — дополнительная арифметика на backward вместо хранения carrier-sized activation для каждого depth-step.

Endpoint-only temporal carry

В chunked training обычно требуется конечное temporal-состояние сегмента, а не каждый нормализованный temporal prefix. Поэтому temporal_carry_endpoint возвращает только

$$\mathrm{accT}\in\mathbb{R}^{B\times H\times3\times3}$$

и небольшую FP32-копию endpoint для backward. Вторая полная траектория temporal accumulators не сохраняется. Backward проходит входные depth-carriers в обратном порядке и аналитически восстанавливает предыдущий нормализованный accumulator из обратимого локального фактора и текущего accumulator. Это temporal-аналог replay: входные depth-carriers остаются доступны, но дополнительная prefix-history [B,T,H,3,3] не создаётся.

Отдельные fused Tria-кернели обслуживают девятислотовый selector, population pooling и sparse final cross-attention внутри компактных CUDA operator graphs. Алгебраические кернели сохраняют проверенные PyTorch reference paths. Packed attn_impl: auto на Ampere и новее требует проверенный varlen forward/backward backend и расширение packed_gather. Pascal-training явно выбирает compact SDPA.

Расширения собираются через torch.utils.cpp_extension.load и Ninja. Startup заранее загружает все расширения активного графа, включая packed_gather, до вывода архитектуры. Хэши исходников и подключённых headers записываются в kernels/.hashes.json; неизменившиеся модули загружаются из kernels/build/. Архитектуру CUDA можно явно задать через TORCH_CUDA_ARCH_LIST, KERNELS_VERBOSE=1 показывает build log, а KERNELS_DUMP_PTX=1 после изменившейся сборки создаёт отдельные PTX-файлы для анализа.

Глубина и связность

В референсном чекпойнте десять именованных Transformer-блоков. Дополнительно мной используется неформальный operational count для рассуждений о графе, но это не стандарт глубины модели и не backend-invariant величина.

В предыдущей версии README было указано ~124 operational layers: temporal prefix считался как ceil(log2(128))=7 стадий. Это число смешивало ассоциативный full-sequence reference scan с активным chunked execution path. В текущем коде:

  • full-sequence PyTorch reference может вычислять temporal carry удваивающимися offset;
  • chunked training вычисляет один streaming endpoint на сегмент;
  • token-by-token inference выполняет одну temporal matrix recurrence на сгенерированный токен;
  • сработавшая граница добавляет одну seed-композицию в первом Tria-слое следующего сегмента.

Поэтому temporal dependency depth зависит от execution path: в streaming-варианте она достигает длины сегмента, а не всегда равна семи стадиям. Я больше не указываю 124 как единственную точную глубину текущей реализации.

Статический per-token stack до temporal unroll по моей неформальной конвенции всё ещё содержит 60 block-transformations, 10 depth-build/composition Tria и 27 межслойных selector/reduction/gate операций. Final aggregation, cross-attention, embeddings и LM head добавляют собственные преобразования, когда соответствующий boundary path активен.

Та же проблема относится к единственному точному числу связей. Рёбра causal attention зависят от document masks и длины последовательности; temporal edges — от явных и фиксированных границ; sparse final cross-attention — от валидных boundary. Поэтому связность описывается по осям без непроверенного скаляра: token attention, depth-history attention, same-channel depth-рекурсия Tria, temporal matrix recurrence, boundary refeed и sparse boundary-to-token attention.

Обучение, SFT и инференс

В репозитории реализованы:

  • обучение токенизатора и токенизация сырых корпусов;
  • подготовленные .bin token streams и streaming TXT/JSONL/Parquet/Arrow datasets;
  • pretraining на одном устройстве и self-launched multi-GPU DDP;
  • gradient accumulation, activation checkpointing и асинхронный eval;
  • полный последовательный eval с loss, bits/token и bits/byte;
  • smart resume весов, шага, schedule и позиции данных;
  • оптимизаторы AdamW и ATOM;
  • supervised fine-tuning с компактными packed layouts, assistant-only loss masks и tool-call templates;
  • bounded-memory render/tokenize для Parquet SFT, destructive validation split и детерминированный full-split eval;
  • анализ донорских чекпойнтов и структурный transplant;
  • переносимая упаковка .aio и интерактивный терминальный чат;
  • AOTInductor export в автономный inference package.

При включённой temporal Tria полный вход Model.forward обрабатывается внутренними temporal chunks. Token-by-token step переносит аналогичные attention KV-caches, фазовые traces Paraplex, endpoint Tria и sparse final-cross-attention keys. Flat batched path используется при выключенной или ablated temporal Tria и не является активным refeed-путём.

Reference PyTorch-реализации задают семантику. Опциональные fused CUDA-kernels ускоряют phase-space projection, PvPowLU, DepthAttn, depth-композицию Tria, slot reduction, temporal carry и final sparse cross-attention.

Структура репозитория

loomformer.py   модель, данные, pretraining, evaluation и AOT export
tria.py         операторы Tria, depth/temporal carry, readers и final cross-attention
loomsft.py      supervised fine-tuning
loomcloner.py   анализ донора и transplant чекпойнта
loompack.py     pack / inspect / extract для переносимого .aio
loomchat.py     интерактивный терминальный чат для .aio
setup.sh        установка CUDA/PyTorch-окружения, обновление репозитория и валидация
tests/          синтетическая PT/SFT-матрица и численный parity backend-путей

Быстрый старт

Установка

Склонируйте репозиторий и запустите установщик:

git clone https://github.com/srose69/Loomformer-Paraplex.git
cd Loomformer-Paraplex
./setup.sh

Установщик проверяет compute capability каждой видимой GPU. Машина с Pascal или Volta получает профиль CUDA 12.6 / PyTorch cu126. Машина только с Turing и более новыми GPU получает CUDA 13.0 / PyTorch cu130. По умолчанию toolkit и venv устанавливаются в ~/loom. Если в системном Python отсутствует ensurepip, setup без root переключается на user-local virtualenv; незавершённое окружение перед повтором сохраняется под timestamped-именем .incomplete.*. В меню также доступны read-only проверка окружения, переустановка пакетов и обновление репозитория.

Текущая версия установщика по умолчанию использует PyTorch 2.12.1. Переменные LOOM_CUDA_LINE, LOOM_TORCH_VERSION, LOOM_INSTALL_DIR, LOOM_REPO_DIR, LOOM_BUILD_JOBS и LOOM_NVCC_THREADS задают профиль, пути и параметры нативной сборки.

Для Git-checkout обновление выполняется через git pull --ff-only. Tarball-установка получает overlay файлов нового upstream. Чекпойнты, датасеты, логи, окружения и local-only paths остаются на месте.

Ручное Python-окружение подходит для машин с уже настроенным CUDA toolchain:

python -m venv .venv
source .venv/bin/activate
pip install torch numpy pyyaml tokenizers pyarrow safetensors transformers jinja2 einops

Custom C++/CUDA extensions требуют Ninja, совпадающий CUDA toolkit и C++ compiler. attn_impl: auto для bf16/fp16 packed training на Ampere и новее требует flash-attn; проверенный varlen-backend Transformer Engine также обслуживает этот путь.

Валидация

python tests/run_matrix.py --setup

Матрица создаёт tokenizer, PT-streams и Parquet chat datasets во временной папке. Она запускает unit-инварианты, загружает все fused extensions, проверяет forward/backward parity на каждой видимой GPU, выполняет optimizer steps PT и SFT, продолжает оба чекпойнта, оценивает полные validation splits и запускает PT/SFT DDP на всех видимых GPU. На Ampere и новее varlen-backend также сравнивается с компактным reference path.

Малые component smoke tests доступны отдельно:

python loomformer.py --smoke-test
python loomsft.py --smoke-test
python tria.py

Обучение токенизатора

python loomformer.py \
  --config cfg/model.yaml \
  --train-tokenizer ./datasets/raw \
  --vocab 32000 \
  --tokenizer-out tokenizer.json

Pretraining

ATOM_META_SCALE=8 python -u loomformer.py \
  --train \
  --config cfg/model.yaml \
  --checkpoint ./loomformer.pt

Если датасет не указан внутри YAML:

ATOM_META_SCALE=8 python -u loomformer.py \
  --train \
  --config cfg/model.yaml \
  --dataset ./datasets/train \
  --checkpoint ./loomformer.pt

Запуск на всех видимых CUDA-устройствах:

python loomformer.py \
  --train \
  --device cudas \
  --config cfg/model.yaml \
  --checkpoint ./loomformer.pt

Встроенный detached-режим позволяет не оборачивать команду в отдельный nohup:

ATOM_META_SCALE=8 python loomformer.py \
  --train \
  --config cfg/model.yaml \
  --checkpoint ./loomformer.pt \
  --quiet

Оценка

python loomformer.py \
  --eval \
  --checkpoint ./loomformer.pt \
  --dataset ./datasets/val \
  --eval-data-cache ram

Минимальный инференс из чекпойнта

python loomformer.py \
  --infer \
  --checkpoint ./loomformer.pt \
  --prompt "The carrier matrix" \
  --max-new 128

Supervised fine-tuning

cfg/alt6_sft.yaml содержит dataset directory, pretrained init-checkpoint, выходной checkpoint и список DDP-устройств:

python loomformer.py --train --config cfg/alt6_sft.yaml

Общая CLI-форма:

python loomsft.py \
  --config cfg/sft.yaml \
  --sft-dataset ./datasets/sft \
  --init-checkpoint ./loomformer.pt \
  --checkpoint ./loomformer-sft.pt

Упаковка и чат

python loompack.py pack ./loomformer-sft.pt \
  --tokenizer tokenizer.json \
  --template chat_template.jinja \
  --quant bf16 \
  -o loomformer.aio

python loomchat.py loomformer.aio --device cuda:0

Хранение persistent KV snapshots в pinned RAM:

python loomchat.py loomformer.aio --device cuda:0 --kvstorage cpu

Attention рядом с K/V на другой GPU:

python loomchat.py loomformer.aio --device cuda:0 --kvstorage cuda:1

Анализ донорской модели

python loomcloner.py \
  --scan ./donor-model \
  --out ./cfg/donor.yaml

Клонирование является структурным transplant, а не точным превращением одной архитектуры в другую. Совместимые тензоры remap-ятся; специфичные для LoomFormer параметры Paraplex, DepthAttn и Tria сохраняют собственную инициализацию, если mapping явно не задаёт другое назначение.

Конфигурация

Модель настраивается через YAML. Основные группы:

Область Характерные поля
Форма model_dim, n_q_heads, n_kv_heads, gqa_group_size, hidden, layers
Attention attn_impl, attn_layers, attn_token_stride, attn_token_schedule, attn_sdpa_compute_dtype, attn_sdpa_value_fusion, rope_*
DepthAttn depth_attn_readout, depth_attn_qkv_rms, residual_branch_rms_cap
Paraplex phase_sectors, activation, powlu_m, phase_grad_mode, phase_grad_floor, paraplex_gate_proj
Tria tria_carry_enabled, tria_temporal_enabled, tria_temporal_window, tria_carrier_alpha, calibration fields
Данные train_dataset, val_dataset, auto_val_split_pct, dataset_format, dataset_cache, text_field, seq_len, batch_size, prefetch_batches
Обучение steps, lr, optimizer, weight_decay, grad_clip, grad_accum_steps, warmup_steps
Чекпойнты init_checkpoint, checkpoint, resume, resume_data_stream, runpoints_path
Runtime device, amp_dtype, grad_checkpointing, compile, graph, save_graph, CUDA fast-path flags

Основные shape-инварианты проверяются при применении конфигурации:

  • model_dim должен делиться на число query-heads;
  • число GQA-heads и group size должны быть согласованы;
  • hidden-width Paraplex должен делиться на число query-heads;
  • temporal Tria требует положительных window и carrier coefficient;
  • входная длина не может превышать seq_len.

Startup calibration Tria может выбрать temporal window и carrier coefficient из набора кандидатов по порогам condition number, effective rank и population pass.

Для SFT режим dataset_cache: otf принимает Parquet-файл или папку с top-level Parquet shards. Строки разделяются между DDP-ranks, render-ятся и токенизируются bounded batches, затем упаковываются вместе с компактной document metadata. auto_val_split_pct один раз отрезает validation tail в <train_dataset>/val/val_split.parquet и использует его manifest в следующих запусках. Эпоха без trainable examples завершается ошибкой с активным значением seq_len.

Требования

Основные зависимости:

  • Python 3.10 или новее;
  • PyTorch;
  • NumPy;
  • PyYAML;
  • Hugging Face tokenizers.

Зависимости отдельных workflow:

  • PyArrow для Arrow- и Parquet-датасетов;
  • Jinja2 для chat templates;
  • Safetensors и Transformers для donor workflows;
  • C++/CUDA toolchain для fused kernels;
  • ATOM при выборе optimizer: atom.

Reference paths поддерживают CPU. CUDA необходима для практического обучения моделей референсного размера и fused custom operators.

Нижняя граница поддерживаемого CUDA-железа — Pascal (SM 6.0). Pascal и Volta используют профиль CUDA 12 и явный compact SDPA attention. Ampere, Ada, Hopper и Blackwell используют проверенный varlen-backend FlashAttention или Transformer Engine для attn_impl: auto. Startup probes выполняют forward и backward; при ошибке выводится сохранённая причина импорта или запуска кернела.

Ограничения конструкции

LoomFormer намеренно сохраняет causal attention. Поэтому полный token-mixing путь остаётся квадратичным по длине последовательности. Tria не заявляется заменой attention; она добавляет operator-route по глубине и выбранным temporal boundaries.

Архитектура несёт больше состояния, чем минимальный decoder Transformer: KV-cache, depth history, per-layer phase traces, depth-carriers, temporal carriers и sparse boundary keys. Parity нужно проверять между переходами активного chunked forward и token-by-token step; отдельный flat path используется, когда temporal refeed неактивен.

Локальная матрица Tria мала, но существует для каждого токена и hidden-канала Paraplex. Fused paths устраняют несколько крупных промежуточных materializations, однако Tria всё равно добавляет вычисления и memory traffic. Её пользу нужно проверять ablation-экспериментами, held-out loss, поведением генерации и профилированием.

Начальный loss LoomFormer нельзя корректно сравнивать шаг-в-шаг с vanilla pre-norm Transformer без контроля normalization placement, residual-маршрута DepthAttn, активации Paraplex, инициализации и токенизатора. Референсный запуск сошёлся, но один запуск не доказывает scaling behaviour всех конфигураций.

В этой ревизии ни одно скалярное число operational depth или connections не считается инвариантом. Текущий execution graph зависит от temporal backend и от реально сработавших границ. Обычное описание архитектуры остаётся таким: 10 decoder-блоков, два depth-read на блок, causal GQA, Paraplex FFN и опциональный Tria carry.

Статус

LoomFormer обучается, оценивается, дообучается, упаковывается и генерирует текст. 113M-запуск сошёлся на указанном корпусе; результаты полного eval приведены выше. SFT и LoomChat работают. LoomChat пока относительно требователен к VRAM, потому что текущая реализация ставит эквивалентность архитектурных путей и возможность инспекции выше минимального inference-runtime.

Синтетическая installation matrix полностью прошла на GTX 1080 и двух Tesla P4: bf16 fallback, optimizer steps PT/SFT, packed-attention parity, resume чекпойнтов, полный OTF-eval и DDP на трёх ranks. На modern GPU дополнительно выполняются varlen parity-cases FlashAttention/Transformer Engine.

Проект находится в статусе pre-release. Имена конфигурации, миграции чекпойнтов, CUDA-kernels и metadata пакета пока не являются стабильным публичным API.

Цитирование

До появления статьи или архивного релиза можно ссылаться на репозиторий и коллекцию чекпойнтов:

@software{loomformer_paraplex,
  author = {srose69},
  title  = {LoomFormer-Paraplex},
  year   = {2026},
  url    = {https://github.com/srose69/LoomFormer-Paraplex}
}

About

LoomFormer: a Transformer-based LM built from Paraplex neurons. Paraplex = the pseudo-complex paravector neuron (scalar+vector, Clifford Cl(0,n) paravector, behaves like a complex number). "Loom" for the pseudo-complex & pseudo paravector numbers; "-former" for the... dunno. for the style.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages