File size: 5,795 Bytes
0ab2566 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | """
Sim-CoT Information Load Simulator
Since Sim-CoT uses rigid one-to-one segmentation (evenly dividing N_c CoT
tokens into N_l segments, each mapped to one latent token), we can simulate
its information load distribution without an actual Sim-CoT checkpoint.
This script reads CoT lengths from PMPS results and computes what the
Sim-CoT distribution would look like.
Usage:
python analysis/simulate_simcot_distribution.py \
--pmps_dir analysis/results/pmps \
--num_latent 6 \
--output_dir analysis/results/simcot_simulated
"""
import os
import json
import argparse
import numpy as np
def simulate_simcot_info_load(cot_lengths, num_latent):
"""
Simulate Sim-CoT's rigid segmentation information load.
Sim-CoT divides the CoT into num_latent equal segments.
Each latent token gets floor(N_c / N_l) tokens, with the last
latent getting the remainder.
However, for samples where N_c < N_l, some latents get 0 tokens
(the core information bottleneck problem).
Returns:
info_loads: list of (num_latent,) arrays
"""
info_loads = []
for N_c in cot_lengths:
load = np.zeros(num_latent)
if N_c <= 0:
info_loads.append(load.tolist())
continue
if N_c >= num_latent:
# Normal case: divide evenly
base_size = N_c // num_latent
remainder = N_c % num_latent
for i in range(num_latent):
# First `remainder` latents get one extra token
load[i] = base_size + (1 if i < remainder else 0)
else:
# N_c < N_l: some latents get nothing (surplus latents)
for i in range(N_c):
load[i] = 1
# Remaining latents get 0 → unsupervised
info_loads.append(load.tolist())
return info_loads
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--pmps_dir', type=str, required=True,
help='PMPS results directory (to read CoT lengths)')
parser.add_argument('--num_latent', type=int, default=6)
parser.add_argument('--output_dir', type=str, default='analysis/results/simcot_simulated')
args = parser.parse_args()
os.makedirs(args.output_dir, exist_ok=True)
# Read CoT lengths from PMPS results
cot_lengths_path = os.path.join(args.pmps_dir, 'cot_lengths.json')
if os.path.exists(cot_lengths_path):
with open(cot_lengths_path, 'r') as f:
cot_lengths = json.load(f)
else:
# Fallback: try to read from distribution metrics
dist_path = os.path.join(args.pmps_dir, 'distribution_metrics.json')
with open(dist_path, 'r') as f:
dist_metrics = json.load(f)
# Estimate CoT length from info load sums
cot_lengths = [int(sum(m['info_load_per_latent'])) for m in dist_metrics]
print(f'Loaded {len(cot_lengths)} CoT lengths')
print(f' Mean CoT length: {np.mean(cot_lengths):.1f}')
print(f' Min/Max: {np.min(cot_lengths)}/{np.max(cot_lengths)}')
# Simulate Sim-CoT info loads
info_loads = simulate_simcot_info_load(cot_lengths, args.num_latent)
# Compute distribution metrics
dist_metrics = []
for i, (loads, cot_len) in enumerate(zip(info_loads, cot_lengths)):
loads_arr = np.array(loads)
cv = float(np.std(loads_arr) / (np.mean(loads_arr) + 1e-8))
dist_metrics.append({
'sample_idx': i,
'info_load_per_latent': loads,
'info_load_cv': cv,
'cot_length': cot_len,
# Sim-CoT has no prototype concept
'proto_usage_latent': [1.0 / args.num_latent] * args.num_latent,
'proto_entropy_latent': float(np.log(args.num_latent)),
'proto_usage_cot': [1.0 / args.num_latent] * args.num_latent,
'proto_entropy_cot': float(np.log(args.num_latent)),
'max_entropy': float(np.log(args.num_latent)),
'avg_match_entropy': 0.0, # rigid 1-to-1 → entropy = 0
'match_entropies': [0.0] * cot_len,
})
# Also create a fake metrics.json for the collapse analysis pipeline
# (we need cos_sim_matrix and effective_rank — use zeros as placeholders
# unless real Sim-CoT checkpoint data is available)
collapse_metrics = []
for i in range(len(info_loads)):
N_l = args.num_latent
collapse_metrics.append({
'sample_idx': i,
'cos_sim_matrix': np.eye(N_l).tolist(), # placeholder
'avg_off_diag_sim': 0.0,
'std_off_diag_sim': 0.0,
'singular_values': [1.0] * N_l,
'singular_values_normalized': [1.0 / N_l] * N_l,
'effective_rank': float(N_l),
'explained_variance': [1.0 / N_l] * N_l,
'cumulative_variance': [i / N_l for i in range(1, N_l + 1)],
})
# Save
with open(os.path.join(args.output_dir, 'distribution_metrics.json'), 'w') as f:
json.dump(dist_metrics, f, indent=2)
with open(os.path.join(args.output_dir, 'metrics.json'), 'w') as f:
json.dump(collapse_metrics, f, indent=2)
# Save simulated latent hiddens (zeros placeholder)
D = 768 # placeholder hidden dim
fake_hiddens = np.zeros((len(info_loads), args.num_latent, D))
np.save(os.path.join(args.output_dir, 'latent_hiddens.npy'), fake_hiddens)
# Summary
all_cvs = [m['info_load_cv'] for m in dist_metrics]
print(f'\nSimulated Sim-CoT distribution:')
print(f' Mean Info Load CV: {np.mean(all_cvs):.4f} (higher = less uniform)')
print(f' Samples with surplus latents (N_c < N_l): '
f'{sum(1 for l in cot_lengths if l < args.num_latent)}')
print(f'\nSaved to: {args.output_dir}')
if __name__ == '__main__':
main()
|