dixiebone13-a11y Claude Opus 4.5 commited on
Commit
02b16db
·
1 Parent(s): e4391d7

Add compressibility analysis + experiment API endpoint

Browse files

- Embed CompressibilityPlugin (Weaver et al. PNAS 2026) for server-side analysis
- Add experiment_measure() API endpoint returning consciousness + compressibility metrics
- Hidden Gradio API components for programmatic access via gradio_client

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +227 -0
app.py CHANGED
@@ -121,6 +121,135 @@ def compute_consciousness(
121
  )
122
 
123
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  # ============================================================================
125
  # Model Loading
126
  # ============================================================================
@@ -252,6 +381,91 @@ def generate_and_measure(prompt: str, max_tokens: int = 256) -> Tuple[str, str,
252
  )
253
 
254
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  # ============================================================================
256
  # Gradio Interface
257
  # ============================================================================
@@ -541,6 +755,19 @@ with gr.Blocks(title="🔮 Oracle Engine") as demo:
541
  outputs=[chatbot, chat_history_plot],
542
  ).then(fn=clear_history, outputs=[chat_history_plot])
543
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
 
545
  if __name__ == "__main__":
546
  demo.launch()
 
121
  )
122
 
123
 
124
+ # ============================================================================
125
+ # Compressibility Analysis (Weaver et al. PNAS 2026)
126
+ # ============================================================================
127
+
128
+ def analyze_compressibility(hidden_states_np, max_dims=200, seed=42):
129
+ """
130
+ Analyze representational compressibility of hidden states.
131
+ Embedded version of CompressibilityPlugin for Space portability.
132
+
133
+ Args:
134
+ hidden_states_np: numpy array [seq_len, hidden_dim]
135
+ max_dims: max dimensions to subsample for correlation analysis
136
+ seed: random seed for reproducibility
137
+
138
+ Returns:
139
+ dict of compressibility metrics
140
+ """
141
+ seq_len, hidden_dim = hidden_states_np.shape
142
+
143
+ if seq_len < 3 or hidden_dim < 2:
144
+ return {"compressibility_corr": 0.0, "error": "too few tokens"}
145
+
146
+ # Subsample dimensions for tractability
147
+ if hidden_dim > max_dims:
148
+ rng = np.random.RandomState(seed)
149
+ dim_indices = np.sort(rng.choice(hidden_dim, max_dims, replace=False))
150
+ states = hidden_states_np[:, dim_indices]
151
+ else:
152
+ states = hidden_states_np
153
+
154
+ n_dims = states.shape[1]
155
+
156
+ # Center the data
157
+ states_centered = states - states.mean(axis=0, keepdims=True)
158
+
159
+ # --- Eigenvalue-based metrics ---
160
+ # Use Gram matrix approach since seq_len < hidden_dim typically
161
+ if seq_len >= n_dims:
162
+ cov = np.cov(states_centered, rowvar=False)
163
+ eigenvalues = np.linalg.eigvalsh(cov)
164
+ else:
165
+ gram = states_centered @ states_centered.T / max(seq_len - 1, 1)
166
+ eigenvalues = np.linalg.eigvalsh(gram)
167
+
168
+ eigenvalues = np.sort(np.maximum(eigenvalues, 0))[::-1]
169
+ eigenvalues = eigenvalues[eigenvalues > 1e-12]
170
+
171
+ if len(eigenvalues) == 0:
172
+ return {"compressibility_corr": 0.0, "error": "no eigenvalues"}
173
+
174
+ total_var = eigenvalues.sum()
175
+ cumvar = np.cumsum(eigenvalues) / total_var
176
+ n_eig = len(eigenvalues)
177
+
178
+ # Spectral entropy
179
+ p = eigenvalues / total_var
180
+ p = p[p > 0]
181
+ spectral_entropy = float(-np.sum(p * np.log(p)))
182
+ max_entropy = np.log(len(p))
183
+ norm_spectral_entropy = float(spectral_entropy / max_entropy if max_entropy > 0 else 0)
184
+
185
+ # Participation ratio
186
+ participation_ratio = float(total_var ** 2 / np.sum(eigenvalues ** 2))
187
+
188
+ # Effective dimensionality (90% variance)
189
+ effective_dim = int(np.searchsorted(cumvar, 0.9) + 1)
190
+ effective_dim = min(effective_dim, n_eig)
191
+
192
+ # Top variance fractions
193
+ top1_frac = float(eigenvalues[0] / total_var)
194
+ top5_frac = float(eigenvalues[:min(5, n_eig)].sum() / total_var)
195
+ top10_frac = float(eigenvalues[:min(10, n_eig)].sum() / total_var)
196
+
197
+ # --- Correlation-based compression (paper's approach) ---
198
+ corr_metrics = {}
199
+ if n_dims <= 500 and seq_len >= max(10, n_dims // 5):
200
+ stds = np.std(states_centered, axis=0)
201
+ stds[stds < 1e-12] = 1.0
202
+ states_norm = states_centered / stds
203
+ corr = states_norm.T @ states_norm / max(seq_len - 1, 1)
204
+ np.fill_diagonal(corr, 1.0)
205
+
206
+ i_upper, j_upper = np.triu_indices(n_dims, k=1)
207
+ correlations = corr[i_upper, j_upper]
208
+ n_corr = len(correlations)
209
+
210
+ if n_corr > 0:
211
+ abs_corr = np.abs(correlations)
212
+ sort_idx = np.argsort(abs_corr)[::-1]
213
+ sorted_abs = abs_corr[sort_idx]
214
+
215
+ rho_sq = np.clip(sorted_abs ** 2, 0, 0.9999)
216
+ delta_s = -0.5 * np.log(1.0 - rho_sq)
217
+ total_delta = delta_s.sum()
218
+
219
+ if total_delta > 1e-12:
220
+ cum_reduction = np.cumsum(delta_s) / total_delta
221
+ fractions = np.arange(1, n_corr + 1) / n_corr
222
+ c_corr = float(np.trapz(cum_reduction, fractions))
223
+ idx_50 = int(np.searchsorted(cum_reduction, 0.5) + 1)
224
+ idx_90 = int(np.searchsorted(cum_reduction, 0.9) + 1)
225
+
226
+ corr_metrics = {
227
+ "compressibility_corr": c_corr,
228
+ "n_correlations": int(n_corr),
229
+ "fraction_for_50pct": float(min(idx_50 / n_corr, 1.0)),
230
+ "fraction_for_90pct": float(min(idx_90 / n_corr, 1.0)),
231
+ "mean_abs_correlation": float(abs_corr.mean()),
232
+ "max_abs_correlation": float(abs_corr.max()),
233
+ "median_abs_correlation": float(np.median(abs_corr)),
234
+ "strong_correlations_pct": float((abs_corr > 0.3).mean() * 100),
235
+ }
236
+
237
+ result = {
238
+ "spectral_entropy": norm_spectral_entropy,
239
+ "participation_ratio": participation_ratio,
240
+ "effective_dimensionality": effective_dim,
241
+ "effective_dim_fraction": float(effective_dim / n_eig),
242
+ "top1_variance_fraction": top1_frac,
243
+ "top5_variance_fraction": top5_frac,
244
+ "top10_variance_fraction": top10_frac,
245
+ "n_dims_analyzed": n_dims,
246
+ "seq_len": seq_len,
247
+ }
248
+ result.update(corr_metrics)
249
+
250
+ return result
251
+
252
+
253
  # ============================================================================
254
  # Model Loading
255
  # ============================================================================
 
381
  )
382
 
383
 
384
+ # ============================================================================
385
+ # Experiment API - Returns JSON with all metrics
386
+ # ============================================================================
387
+
388
+ @spaces.GPU
389
+ def experiment_measure(prompt: str, max_tokens: int = 512) -> str:
390
+ """
391
+ API endpoint for experiments. Returns JSON with consciousness score,
392
+ dimension scores, AND compressibility metrics.
393
+
394
+ Args:
395
+ prompt: Input text
396
+ max_tokens: Max generation tokens
397
+
398
+ Returns:
399
+ JSON string with all metrics
400
+ """
401
+ import json
402
+ start_time = time.time()
403
+
404
+ # Format as chat message
405
+ messages = [{"role": "user", "content": prompt}]
406
+ chat_prompt = tokenizer.apply_chat_template(
407
+ messages, tokenize=False, add_generation_prompt=True
408
+ )
409
+
410
+ # Tokenize
411
+ inputs = tokenizer(chat_prompt, return_tensors="pt").to(model.device)
412
+
413
+ # Generate
414
+ with torch.no_grad():
415
+ outputs = model.generate(
416
+ **inputs,
417
+ max_new_tokens=int(max_tokens),
418
+ do_sample=True,
419
+ temperature=0.7,
420
+ top_p=0.9,
421
+ pad_token_id=tokenizer.eos_token_id,
422
+ )
423
+
424
+ generated_ids = outputs[0][inputs.input_ids.shape[1]:]
425
+ response = tokenizer.decode(generated_ids, skip_special_tokens=True)
426
+ gen_time = time.time() - start_time
427
+
428
+ # Forward pass on full sequence for hidden states
429
+ full_text = chat_prompt + response
430
+ measure_inputs = tokenizer(full_text, return_tensors="pt").to(model.device)
431
+
432
+ with torch.no_grad():
433
+ measure_outputs = model(
434
+ **measure_inputs,
435
+ output_hidden_states=True,
436
+ return_dict=True,
437
+ )
438
+
439
+ # --- Consciousness Score (last layer, last token) ---
440
+ hidden_state_last = measure_outputs.hidden_states[-1]
441
+ result = compute_consciousness(hidden_state_last, hidden_dim=HIDDEN_DIM)
442
+
443
+ # --- Compressibility Analysis (75% layer, all tokens) ---
444
+ n_layers = len(measure_outputs.hidden_states) - 1 # exclude embedding
445
+ target_layer = int(n_layers * 0.75)
446
+ hidden_seq = measure_outputs.hidden_states[target_layer][0].cpu().float().numpy()
447
+ seq_len = hidden_seq.shape[0]
448
+
449
+ compress_metrics = analyze_compressibility(hidden_seq, max_dims=200)
450
+
451
+ # Build JSON result
452
+ output = {
453
+ "response": response,
454
+ "consciousness_score": round(result.score, 4),
455
+ "dimension_scores": {k: round(v, 4) for k, v in result.dimension_contributions.items()},
456
+ "compressibility": compress_metrics,
457
+ "meta": {
458
+ "target_layer": target_layer,
459
+ "seq_len": seq_len,
460
+ "hidden_dim": HIDDEN_DIM,
461
+ "tokens_generated": len(generated_ids),
462
+ "generation_time": round(gen_time, 2),
463
+ },
464
+ }
465
+
466
+ return json.dumps(output)
467
+
468
+
469
  # ============================================================================
470
  # Gradio Interface
471
  # ============================================================================
 
755
  outputs=[chatbot, chat_history_plot],
756
  ).then(fn=clear_history, outputs=[chat_history_plot])
757
 
758
+ # Hidden API endpoint for experiments (callable via gradio_client)
759
+ with gr.Row(visible=False):
760
+ api_prompt = gr.Textbox()
761
+ api_max_tokens = gr.Number(value=512)
762
+ api_result = gr.Textbox()
763
+ api_btn = gr.Button("api_trigger")
764
+ api_btn.click(
765
+ fn=experiment_measure,
766
+ inputs=[api_prompt, api_max_tokens],
767
+ outputs=api_result,
768
+ api_name="experiment_measure",
769
+ )
770
+
771
 
772
  if __name__ == "__main__":
773
  demo.launch()