deepbox/nn
Attention & Transformer
MultiheadAttention, positional encoding, transformer encoder and decoder layers and stacks, and causalMask.
- Transformer
Overview
- Inputs are
[batch, seqLen, dModel], or[seqLen, dModel]for one sequence. The model dimension must be divisible by the number of heads.mha.forward(query, key?, value?, attnMask?, options?)takes an additive float mask, where a large negative number blocks a position, or a boolean mask wheretrueblocks it.causalMask(seqLen)builds the mask for autoregressive attention. - The options object takes
needWeights(returns[output, weights]),averageAttnWeightsandkeyPaddingMask. Transformer layers takeactivation("relu"or"gelu") andnormFirst. - Dropout is 0.1 in the transformer layers and 0 in
MultiheadAttention. Callmodel.eval()for inference, and wrap it innoGradto stop recording. - Defaults: the feed-forward width is 2048,
FullTransformerhas 6 encoder and 6 decoder layers withdModel: 512, and it has no finalLayerNormunless you passfinalNorm: true, where PyTorch'snn.Transformerhas one. The decoder uses a causal mask when you give notgtMask.
MultiheadAttentionForwardOptions
typeOptions of MultiheadAttention.forward, passed as the last argument.
readonly needWeights?: booleanAlso return the attention weights, as
[output, weights](default: false).readonly averageAttnWeights?: booleanWith
needWeights, average the weights over the heads (default: true).readonly keyPaddingMask?: AnyTensorMarks the key positions to ignore, shape
(batch, seqLenK)(or(seqLenK)for inputs without a batch axis).
TransformerActivation
typeActivation of the feed-forward network inside a Transformer layer: "relu" or the exact (erf) "gelu".
type TransformerActivation = "relu" | "gelu";FullTransformer
extends ModuleFull Transformer model with encoder and decoder stacks.
Post-norm layers with ReLU feed-forward networks by default (see the normFirst and activation options). Unlike PyTorch's nn.Transformer, the encoder and decoder stacks have no final LayerNorm unless you ask for it with options.finalNorm. Parameters follow the dtype option (default: the global default dtype, float32 unless changed).
import { FullTransformer } from 'deepbox/nn';import { randn } from 'deepbox/ndarray'; const transformer = new FullTransformer(64, 8, 2, 2, 256);const src = randn([2, 10, 64]); // (batch, srcLen, dModel)const tgt = randn([2, 7, 64]); // (batch, tgtLen, dModel)const output = transformer.forward(src, tgt); // shape [2, 7, 64]Members (5)
constructor(dModel?: number, nHead?: number, numEncoderLayers?: number, numDecoderLayers?: number, dFF?: number, options?: { readonly dropout?: number; readonly eps?: number; readonly dtype?: LayerDType; readonly finalNorm?: boolean; readonly activation?: TransformerActivation; readonly normFirst?: boolean; })forward(src: GradTensor, tgt?: AnyTensor, srcMask?: AnyTensor, tgtMask?: AnyTensor, memoryMask?: AnyTensor): GradTensorForward pass of the full Transformer.
forward(src: Tensor, tgt?: AnyTensor, srcMask?: AnyTensor, tgtMask?: AnyTensor, memoryMask?: AnyTensor): AnyTensorforward(src: AnyTensor, tgt?: AnyTensor, srcMask?: AnyTensor, tgtMask?: AnyTensor, memoryMask?: AnyTensor): AnyTensortoString(): string
MultiheadAttention
extends ModuleMulti-Head Attention mechanism.
Allows the model to jointly attend to information from different representation subspaces at different positions. This is the core building block of Transformers.
Mathematical Formulation:
Attention(Q, K, V) = softmax(Q * K^T / sqrt(d_k)) * VMultiHead(Q, K, V) = Concat(head_1, ..., head_h) * W_Owhere head_i = Attention(Q * W_Q^i, K * W_K^i, V * W_V^i)Parameters are float32 unless the dtype option (or the global default dtype) says otherwise. The layer computes in the parameter dtype and casts its inputs to it. A GradTensor input gives a GradTensor; plain tensors give a GradTensor that tracks the weights while they require grad and gradient tracking is on, and a plain Tensor otherwise (inside noGrad() or with frozen weights).
import { MultiheadAttention, causalMask } from 'deepbox/nn';import { randn } from 'deepbox/ndarray'; const mha = new MultiheadAttention(64, 8);const x = randn([2, 10, 64]); // (batch, seqLen, embedDim)const out = mha.forward(x, x, x); // shape [2, 10, 64]const masked = mha.forward(x, x, x, causalMask(10)); // each step sees only earlier stepsMembers (8)
constructor(embedDim: number, numHeads: number, options?: { readonly bias?: boolean; readonly dropout?: number; readonly dtype?: LayerDType; })Create a new MultiheadAttention layer.
forward<Q extends AnyTensor, O extends MultiheadAttentionForwardOptions>(query: Q, key: AnyTensor | undefined, value: AnyTensor | undefined, attnMask: AnyTensor | undefined, options: O): ForwardResult<Q, O>Forward pass of multi-head attention.
forward<Q extends AnyTensor, O extends MultiheadAttentionForwardOptions>(query: Q, key: AnyTensor | undefined, value: AnyTensor | undefined, options: O): ForwardResult<Q, O>forward<Q extends AnyTensor, O extends MultiheadAttentionForwardOptions>(query: Q, options: O): ForwardResult<Q, O>forward(query: GradTensor, ...rest: AnyTensor[]): GradTensorforward(query: Tensor, ...rest: AnyTensor[]): AnyTensorforward(...inputs: AnyTensor[]): AnyTensortoString(): string
PositionalEncoding
extends ModuleSinusoidal Positional Encoding.
Adds sinusoidal positional information to input embeddings using the formula from "Attention Is All You Need":
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))For an odd dModel the last column pairs with the same frequency as the one before it. The table is stored as the pe buffer and cast to the input's float dtype on each call; integer inputs are cast to float32.
import { PositionalEncoding } from 'deepbox/nn';import { randn } from 'deepbox/ndarray'; const pe = new PositionalEncoding(64, { maxLen: 500 });const x = randn([2, 10, 64]); // (batch, seqLen, dModel)const output = pe.forward(x); // x + positional encoding, then dropout (training mode)Members (5)
constructor(dModel: number, options?: { readonly dropout?: number; readonly maxLen?: number; })forward(x: GradTensor): GradTensorforward(x: Tensor): Tensorforward(x: AnyTensor): AnyTensortoString(): string
TransformerDecoder
extends ModuleTransformer Decoder, a stack of N decoder layers.
The first layer is the decoderLayer you pass in; the other N - 1 layers are independent copies of it (see TransformerDecoderLayer.clone).
import { TransformerDecoder, TransformerDecoderLayer } from 'deepbox/nn';import { randn } from 'deepbox/ndarray'; const decoderLayer = new TransformerDecoderLayer(64, 8, 256);const decoder = new TransformerDecoder(decoderLayer, 6);const output = decoder.forward(randn([2, 7, 64]), randn([2, 10, 64])); // shape [2, 7, 64]Members (5)
constructor(decoderLayer: TransformerDecoderLayer, numLayers: number, options?: { readonly norm?: LayerNorm; })forward(tgt: GradTensor, memory?: AnyTensor, tgtMask?: AnyTensor, memoryMask?: AnyTensor): GradTensorforward(tgt: Tensor, memory?: AnyTensor, tgtMask?: AnyTensor, memoryMask?: AnyTensor): AnyTensorforward(tgt: AnyTensor, memory?: AnyTensor, tgtMask?: AnyTensor, memoryMask?: AnyTensor): AnyTensortoString(): string
TransformerDecoderLayer
extends ModuleTransformer Decoder Layer.
A single layer of the Transformer decoder, consisting of: 1. Masked multi-head self-attention 2. Add & Norm 3. Multi-head cross-attention (attending to encoder output) 4. Add & Norm 5. Feed-forward network 6. Add & Norm
By default the self-attention is causal: position i only attends to positions up to i. Pass tgtMask to forward to use a different mask. The layout is post-norm with a ReLU feed-forward network unless normFirst and activation say otherwise.
Parameters are float32 unless the dtype option (or the global default dtype) says otherwise; inputs of other dtypes are cast to the parameter dtype. Gradient tracking follows the rule of MultiheadAttention.
import { TransformerDecoderLayer } from 'deepbox/nn';import { randn } from 'deepbox/ndarray'; const layer = new TransformerDecoderLayer(64, 8, 256);const tgt = randn([2, 7, 64]); // (batch, tgtLen, dModel)const memory = randn([2, 10, 64]); // encoder output (batch, srcLen, dModel)const output = layer.forward(tgt, memory); // shape [2, 7, 64]Members (6)
constructor(dModel: number, nHead: number, dFF?: number, options?: { readonly dropout?: number; readonly eps?: number; readonly dtype?: LayerDType; readonly activation?: TransformerActivation; readonly normFirst?: boolean; })clone(): TransformerDecoderLayerCreate an independent deep copy: same architecture, copied weights, buffers, trainability and train/eval mode.
forward(tgt: GradTensor, memory?: AnyTensor, tgtMask?: AnyTensor, memoryMask?: AnyTensor): GradTensorForward pass of the Transformer decoder layer.
forward(tgt: Tensor, memory?: AnyTensor, tgtMask?: AnyTensor, memoryMask?: AnyTensor): AnyTensorforward(tgt: AnyTensor, memory?: AnyTensor, tgtMask?: AnyTensor, memoryMask?: AnyTensor): AnyTensortoString(): string
TransformerEncoder
extends ModuleTransformer Encoder, a stack of N encoder layers.
The first layer is the encoderLayer you pass in; the other N - 1 layers are independent copies of it (see TransformerEncoderLayer.clone), so every layer starts from the same weights and then trains separately.
import { TransformerEncoder, TransformerEncoderLayer } from 'deepbox/nn';import { randn } from 'deepbox/ndarray'; const encoderLayer = new TransformerEncoderLayer(64, 8, 256);const encoder = new TransformerEncoder(encoderLayer, 6);const output = encoder.forward(randn([2, 10, 64])); // shape [2, 10, 64]Members (5)
constructor(encoderLayer: TransformerEncoderLayer, numLayers: number, options?: { readonly norm?: LayerNorm; })forward(src: GradTensor, mask?: AnyTensor): GradTensorforward(src: Tensor, mask?: AnyTensor): AnyTensorforward(src: AnyTensor, mask?: AnyTensor): AnyTensortoString(): string
TransformerEncoderLayer
extends ModuleTransformer Encoder Layer.
A single layer of the Transformer encoder, consisting of: 1. Multi-head self-attention 2. Add & Norm (residual connection + layer normalization) 3. Feed-forward network (FFN) 4. Add & Norm
Uses the post-norm layout and a ReLU feed-forward network by default, like PyTorch. Pass activation: 'gelu' and normFirst: true for a GELU network and the pre-norm layout. Parameters are float32 unless the dtype option (or the global default dtype) says otherwise; inputs of other dtypes are cast to the parameter dtype. Gradient tracking follows the rule of MultiheadAttention.
import { TransformerEncoderLayer } from 'deepbox/nn';import { randn } from 'deepbox/ndarray'; const layer = new TransformerEncoderLayer(64, 8, 256);const x = randn([2, 10, 64]); // (batch, seqLen, dModel)const output = layer.forward(x); // shape [2, 10, 64]Members (6)
constructor(dModelOrOpts: number | { readonly dModel: number; readonly nHead: number; readonly dimFeedforward?: number; readonly dFF?: number; readonly dropout?: number; readonly eps?: number; readonly dtype?: LayerDType; readonly activation?: TransformerActivation; readonly normFirst?: boolean; }, nHead?: number, dFF…clone(): TransformerEncoderLayerCreate an independent deep copy: same architecture, copied weights, buffers, trainability and train/eval mode.
forward(src: GradTensor, mask?: AnyTensor): GradTensorForward pass of the Transformer encoder layer.
forward(src: Tensor, mask?: AnyTensor): AnyTensorforward(src: AnyTensor, mask?: AnyTensor): AnyTensortoString(): string
causalMask
function causalMask(seqLen: number): Tensor;Build an additive causal (autoregressive) attention mask of shape [L, L]: 0 on and below the diagonal, -1e9 above it. Adding this to attention scores before softmax prevents each position from attending to future positions.
Parameters
seqLennumber- Sequence length L (non-negative integer)
Returns
Tensorfloat64 tensor of shape [seqLen, seqLen]
Throws
InvalidParameterErrorIfseqLenis not a non-negative integer
import { causalMask } from 'deepbox/nn'; causalMask(3).toArray();// [[0, -1e9, -1e9], [0, 0, -1e9], [0, 0, 0]]import { randn } from "deepbox/ndarray";import { FullTransformer, MultiheadAttention, PositionalEncoding, TransformerEncoderLayer, causalMask,} from "deepbox/nn"; const x = randn([2, 10, 64]); // [batch, seqLen, dModel] const attention = new MultiheadAttention(64, 8);const [out, weights] = attention.forward(x, x, x, causalMask(10), { needWeights: true });console.log(out.shape, weights.shape); // [2, 10, 64] [2, 10, 10] const encoder = new TransformerEncoderLayer(64, 8, 128, { activation: "gelu" });console.log(encoder.forward(x).shape); console.log(new PositionalEncoding(64).forward(x).shape); const transformer = new FullTransformer(64, 8, 2, 2, 128);console.log(transformer.forward(x, randn([2, 7, 64])).shape); // [2, 7, 64]Found a mistake? Open an issue.