| from tensorflow.keras import mixed_precision
|
|
|
|
|
| import math
|
| import tensorflow as tf
|
| import numpy as np
|
|
|
|
|
| def scaled_dot_product_attention(q, k, v, mask=None, attn_dropout=None, training=False):
|
| """
|
| q,k,v shape: (batch, num_heads, seq_len, depth)
|
| mask shape: (seq_len_q, seq_len_k) — 1 = mask out, 0 = keep
|
| """
|
| matmul_qk = tf.matmul(q, k, transpose_b=True)
|
|
|
| dk = tf.cast(tf.shape(k)[-1], tf.float32)
|
| scaled_attention_logits = tf.cast(matmul_qk, tf.float32) / tf.math.sqrt(dk)
|
|
|
| if mask is not None:
|
| scaled_attention_logits += (tf.cast(mask, tf.float32) * -1e9)
|
|
|
| attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1)
|
|
|
|
|
| if attn_dropout is not None:
|
| attention_weights = attn_dropout(attention_weights, training=training)
|
|
|
|
|
|
|
| attention_weights = tf.cast(attention_weights, v.dtype)
|
|
|
| output = tf.matmul(attention_weights, v)
|
| return output, attention_weights
|
|
|
|
|
|
|
| class MultiheadAttention(tf.keras.layers.Layer):
|
| def __init__(self, d_model, num_heads, num_layers, max_len, rate=0.1):
|
| super().__init__()
|
| self.supports_masking = True
|
|
|
| self.d_model = d_model
|
| self.num_heads = num_heads
|
| assert d_model % num_heads == 0
|
| self.depth = d_model // num_heads
|
| self.max_len = max_len
|
|
|
|
|
| std = 0.02
|
| out_std = 0.02 / math.sqrt(2 * num_layers)
|
| kinit = tf.keras.initializers.RandomNormal(stddev=std)
|
| oinit = tf.keras.initializers.RandomNormal(stddev=out_std)
|
|
|
| self.wq = tf.keras.layers.Dense(d_model, kernel_initializer=kinit, bias_initializer="zeros")
|
| self.wk = tf.keras.layers.Dense(d_model, kernel_initializer=kinit, bias_initializer="zeros")
|
| self.wv = tf.keras.layers.Dense(d_model, kernel_initializer=kinit, bias_initializer="zeros")
|
| self.dense = tf.keras.layers.Dense(d_model, kernel_initializer=oinit, bias_initializer="zeros")
|
|
|
| self.attn_dropout = tf.keras.layers.Dropout(rate)
|
|
|
|
|
| angles = build_rope_angles(max_len, self.depth, offset=0)
|
| self._rope_sin = tf.constant(tf.sin(angles), dtype=tf.float32)
|
| self._rope_cos = tf.constant(tf.cos(angles), dtype=tf.float32)
|
|
|
| def split_heads(self, x, batch_size):
|
| x = tf.reshape(x, (batch_size, -1, self.num_heads, self.depth))
|
| return tf.transpose(x, perm=[0, 2, 1, 3])
|
|
|
| def call(self, v, k, q, mask=None, cache=None, training=False):
|
| batch_size = tf.shape(q)[0]
|
|
|
| q = self.wq(q)
|
| k = self.wk(k)
|
| v = self.wv(v)
|
|
|
| q = self.split_heads(q, batch_size)
|
| k = self.split_heads(k, batch_size)
|
| v = self.split_heads(v, batch_size)
|
|
|
| if cache is not None and cache.get("k") is not None:
|
| position_offset = tf.shape(cache["k"])[2]
|
| else:
|
| position_offset = 0
|
|
|
| q, k = apply_rope(q, k,
|
| offset=position_offset,
|
| sin_table=self._rope_sin,
|
| cos_table=self._rope_cos)
|
|
|
| if cache is not None and cache.get("k") is not None:
|
| k = tf.concat([cache["k"], k], axis=2)
|
| v = tf.concat([cache["v"], v], axis=2)
|
|
|
| new_cache = {"k": k, "v": v}
|
|
|
| scaled_attention, _ = scaled_dot_product_attention(
|
| q, k, v, mask, attn_dropout=self.attn_dropout, training=training
|
| )
|
|
|
| scaled_attention = tf.transpose(scaled_attention, perm=[0, 2, 1, 3])
|
| concat_attention = tf.reshape(scaled_attention, (batch_size, -1, self.d_model))
|
|
|
| return self.dense(concat_attention), new_cache
|
|
|
|
|
| class PositionalEmbedding(tf.keras.layers.Layer):
|
| """
|
| Token embedding only — position is handled by RoPE inside attention.
|
| Kept under this name for backward compatibility.
|
| """
|
| def __init__(self, vocab_size, d_model, max_len, rate=0.1):
|
| super().__init__()
|
| self.token_embedding = tf.keras.layers.Embedding(
|
| vocab_size, d_model,
|
| embeddings_initializer=tf.keras.initializers.RandomNormal(stddev=0.02),
|
| )
|
| self.dropout = tf.keras.layers.Dropout(rate)
|
|
|
| def call(self, x, training=False):
|
| x = self.token_embedding(x)
|
| return self.dropout(x, training=training)
|
|
|
|
|
| class TransformerBlock(tf.keras.layers.Layer):
|
| def __init__(self, d_model, num_heads, dff, num_layers, max_len, rate=0.1):
|
| super().__init__()
|
| self.supports_masking = True
|
|
|
| self.mha = MultiheadAttention(d_model, num_heads, num_layers, max_len, rate=rate)
|
|
|
| std = 0.02
|
| out_std = 0.02 / math.sqrt(2 * num_layers)
|
| self.dense1 = tf.keras.layers.Dense(
|
| dff, activation="gelu",
|
| kernel_initializer=tf.keras.initializers.RandomNormal(stddev=std),
|
| bias_initializer="zeros",
|
| )
|
| self.dense2 = tf.keras.layers.Dense(
|
| d_model,
|
| kernel_initializer=tf.keras.initializers.RandomNormal(stddev=out_std),
|
| bias_initializer="zeros",
|
| )
|
|
|
| self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
|
| self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
|
|
|
| self.dropout1 = tf.keras.layers.Dropout(rate)
|
| self.dropout2 = tf.keras.layers.Dropout(rate)
|
|
|
| def call(self, x, mask=None, cache=None, training=False):
|
| n1 = self.layernorm1(x)
|
| attn_output, new_cache = self.mha(n1, n1, n1, mask=mask, cache=cache, training=training)
|
| attn_output = self.dropout1(attn_output, training=training)
|
| out1 = x + attn_output
|
|
|
| n2 = self.layernorm2(out1)
|
| ffn_output = self.dense1(n2)
|
| ffn_output = self.dense2(ffn_output)
|
| ffn_output = self.dropout2(ffn_output, training=training)
|
| return out1 + ffn_output, new_cache
|
|
|
|
|
| def create_causal_mask(seq_len_q, seq_len_k=None):
|
| """
|
| Returns a (seq_len_q, seq_len_k) mask where 1 = mask out (future), 0 = keep.
|
| For cached generation: seq_len_q=1, seq_len_k=cache_len+1 — mask is all zeros.
|
| """
|
| if seq_len_k is None:
|
| seq_len_k = seq_len_q
|
|
|
|
|
| i = tf.range(seq_len_q)[:, None] + (seq_len_k - seq_len_q)
|
| j = tf.range(seq_len_k)[None, :]
|
| mask = tf.cast(j > i, tf.float32)
|
| return mask
|
|
|
|
|
| def build_rope_angles(seq_len, head_dim, offset=0):
|
| position = tf.cast(tf.range(offset, offset + seq_len), dtype=tf.float32)
|
| dim = tf.cast(tf.range(head_dim), dtype=tf.float32)
|
| theta = 10000.0 ** (-2 * (dim // 2) / tf.cast(head_dim, tf.float32))
|
| angles = tf.einsum("i,j->ij", position, theta)
|
| return tf.cast(angles, tf.float32)
|
|
|
|
|
| def rotate_half(x):
|
| """
|
| Splits the last dim into even/odd pairs and rotates each pair 90 degrees.
|
| """
|
| x1 = x[..., ::2]
|
| x2 = x[..., 1::2]
|
| paired = tf.stack([-x2, x1], axis=-1)
|
| return tf.reshape(paired, tf.shape(x))
|
|
|
|
|
| def apply_rope(q, k, offset=0, sin_table=None, cos_table=None):
|
| """
|
| q, k: (batch, num_heads, seq_len, head_dim)
|
| Uses precomputed sin/cos tables when provided.
|
| """
|
| seq_len = tf.shape(q)[2]
|
| head_dim = tf.shape(q)[3]
|
|
|
| if sin_table is None or cos_table is None:
|
| angles = build_rope_angles(seq_len, head_dim, offset=offset)
|
| sin = tf.sin(angles)
|
| cos = tf.cos(angles)
|
| else:
|
| sin = sin_table[offset:offset + seq_len]
|
| cos = cos_table[offset:offset + seq_len]
|
|
|
| sin = sin[None, None, :, :]
|
| cos = cos[None, None, :, :]
|
|
|
|
|
|
|
| sin = tf.cast(sin, q.dtype)
|
| cos = tf.cast(cos, q.dtype)
|
|
|
| q_rot = q * cos + rotate_half(q) * sin
|
| k_rot = k * cos + rotate_half(k) * sin
|
| return q_rot, k_rot
|
|
|