Apply simulated self-attention: for each vector, blend with mean of top-K nearest neighbors. This approximates contextual composition that real attention provides. vectors: (N, D) float32 unit-normalized Returns: (N, D) float32 unit-normalized
(vectors: np.ndarray, k: int, iterations: int,
alpha: float)
| 109 | # ── Simulated attention ────────────────────────────────────────────── |
| 110 | |
| 111 | def simulated_attention(vectors: np.ndarray, k: int, iterations: int, |
| 112 | alpha: float) -> np.ndarray: |
| 113 | """ |
| 114 | Apply simulated self-attention: for each vector, blend with mean of |
| 115 | top-K nearest neighbors. This approximates contextual composition |
| 116 | that real attention provides. |
| 117 | |
| 118 | vectors: (N, D) float32 unit-normalized |
| 119 | Returns: (N, D) float32 unit-normalized |
| 120 | """ |
| 121 | n, d = vectors.shape |
| 122 | result = vectors.copy() |
| 123 | |
| 124 | for iteration in range(iterations): |
| 125 | t0 = time.time() |
| 126 | # Compute cosine similarity matrix in chunks to avoid OOM |
| 127 | # For 40K vectors × 768d, full matrix = 40K² × 4 bytes = 6.4GB |
| 128 | # Process in chunks of 2048 |
| 129 | chunk_size = 2048 |
| 130 | new_result = np.zeros_like(result) |
| 131 | |
| 132 | for i in range(0, n, chunk_size): |
| 133 | end = min(i + chunk_size, n) |
| 134 | chunk = result[i:end] # (chunk, D) |
| 135 | |
| 136 | # Cosine similarity: chunk × all^T |
| 137 | sims = chunk @ result.T # (chunk, N) |
| 138 | |
| 139 | # For each vector in chunk, find top-K neighbors (excluding self) |
| 140 | for j in range(end - i): |
| 141 | global_idx = i + j |
| 142 | sim_row = sims[j].copy() |
| 143 | sim_row[global_idx] = -1.0 # Exclude self |
| 144 | |
| 145 | # Top-K indices |
| 146 | if k < n - 1: |
| 147 | top_k_idx = np.argpartition(sim_row, -k)[-k:] |
| 148 | else: |
| 149 | top_k_idx = np.arange(n) |
| 150 | top_k_idx = top_k_idx[top_k_idx != global_idx] |
| 151 | |
| 152 | neighbor_mean = result[top_k_idx].mean(axis=0) |
| 153 | |
| 154 | # Blend |
| 155 | blended = (1 - alpha) * result[global_idx] + alpha * neighbor_mean |
| 156 | # Re-normalize |
| 157 | norm = np.linalg.norm(blended) |
| 158 | if norm > 1e-8: |
| 159 | blended /= norm |
| 160 | new_result[global_idx] = blended |
| 161 | |
| 162 | result = new_result |
| 163 | elapsed = time.time() - t0 |
| 164 | print(f" sim-attention iter {iteration + 1}/{iterations}: {elapsed:.1f}s") |
| 165 | |
| 166 | return result |
| 167 | |
| 168 |