jasonfan commited on
Commit
8df9e61
·
verified ·
1 Parent(s): 79f98a8

Upload folder using huggingface_hub

Browse files
README.md ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DPA Research Pipeline — 从 Idea 到代码的完整记录
2
+
3
+ **Decision Point Attention: Strategic Full Attention Routing for Efficient Agent Reasoning with Linear Attention**
4
+
5
+ Target: NeurIPS 2026
6
+
7
+ ## 这个 Repo 包含什么
8
+
9
+ ### 1. `conversation/` — Claude 对话完整记录
10
+ - 4天的研究对话(3/20-3/24)
11
+ - 从文献调研 → 20个idea → 选定DPA → 搭建项目的全过程
12
+ - 包含: ICL+Long Doc 方向分析、Linear Attention+RAG 10个idea、Agent+ICL+Linear Attention 10个idea
13
+
14
+ ### 2. `agentlab-pipeline/` — AgentLaboratory 自动研究 Pipeline
15
+ - `decision_point_attention.yaml` — 研究配置(topic、notes、参数)
16
+ - `inference_patched.py` — 修改后的推理模块(接入 LLMBox 网关)
17
+ - `tools_patched.py` — 修改后的工具模块(PDF下载重试)
18
+ - `ai_lab_repo_patched.py` — 修改后的主程序
19
+ - `agentlab_run.log` — 运行日志(Literature Review 结果、论文分析)
20
+
21
+ ### 3. `dpa-project/` — DPA 完整项目代码
22
+ ```
23
+ src/
24
+ models/
25
+ router.py — Decision Point Router (learned + fixed + supervised loss)
26
+ dpa_model.py — DPA 架构 (LinearAttn + FullAttn + Router + Wrapper)
27
+ baselines.py — Full Transformer / Pure Linear / Uniform Hybrid
28
+ data/
29
+ agent_trajectory.py — Agent trajectory 生成器 + decision point 标注
30
+ datasets.py — HotpotQA / GSM8K / ToolBench 数据加载
31
+ eval/
32
+ benchmark.py — 统一评估 (simulate / train / finetune)
33
+ metrics.py — FLOPs / latency / KV cache / perplexity
34
+ visualize.py — 出版级图表生成
35
+ configs/ — 本地simulation + Merlin 8xH100 训练配置
36
+ scripts/ — 运行脚本
37
+ data/ — 2000条标注 agent trajectories
38
+ results/ — Simulation 结果 (13 model variants)
39
+ ```
40
+
41
+ ### 4. `figures/` — 生成的论文图表
42
+ - `accuracy_vs_flops.pdf` — Quality vs Compute tradeoff
43
+ - `ratio_ablation.pdf` — Decision Point Ratio 消融实验
44
+ - `trajectory_analysis.pdf` — Agent Trajectory 中 decision point 分布
45
+
46
+ ## 复现步骤
47
+
48
+ ```bash
49
+ # 1. 本地运行 simulation (无需 GPU)
50
+ cd dpa-project
51
+ pip install -r requirements.txt
52
+ python src/data/agent_trajectory.py # 生成数据
53
+ python src/eval/benchmark.py --mode simulate # 跑 simulation
54
+ python src/eval/visualize.py results/simulation_results.json # 生成图表
55
+
56
+ # 2. 用 AgentLaboratory 自动研究 (需要 LLM API)
57
+ cd agentlab-pipeline
58
+ # 先 clone AgentLaboratory:
59
+ # git clone https://github.com/SamuelSchmidgall/AgentLaboratory.git
60
+ # 复制 patched 文件覆盖原文件, 然后:
61
+ python ai_lab_repo_patched.py --yaml-location decision_point_attention.yaml
62
+
63
+ # 3. 在 Merlin 上训练 (8xH100)
64
+ cd dpa-project && bash scripts/run_dpa.sh
65
+ ```
66
+
67
+ ## Key Findings from AgentLab Literature Review
68
+
69
+ AgentLaboratory 自动分析了以下论文与 DPA 的关联:
70
+
71
+ 1. **Routing Transformer (Roy et al., 2020)** — Content-based sparse routing 先驱,证明 selective global attention 优于 uniform sparsity
72
+ 2. **HAG (Roffo et al., 2024)** — Hard gating + gradient routing 训练策略,可用于 DPA router 训练
73
+
74
+ ## LLMBox 网关配置 (字节内部免费)
75
+
76
+ AgentLaboratory 通过 LLMBox 网关调用 LLM, 无需 OpenAI API Key:
77
+ - Base URL: `https://llmbox.bytedance.net/v1`
78
+ - 在 `inference_patched.py` 中添加了 `llmbox/` 模型前缀支持
79
+ - 修复了 LLMBox 返回 list (而非 string) 的兼容性问题
agentlab-pipeline/agentlab_run.log ADDED
@@ -0,0 +1,791 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ BertModel LOAD REPORT from: sentence-transformers/all-MiniLM-L6-v2
3
+ Key | Status | |
4
+ ------------------------+------------+--+-
5
+ embeddings.position_ids | UNEXPECTED | |
6
+
7
+ Notes:
8
+ - UNEXPECTED :can be ignored when loading from different task/architecture; not ok if you expect identical arch.
9
+ /Users/bytedance/AgentLaboratory/utils.py:7: FutureWarning:
10
+
11
+ All support for the `google.generativeai` package has ended. It will no longer be receiving
12
+ updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
13
+ See README for more details:
14
+
15
+ https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
16
+
17
+ import google.generativeai as genai
18
+ Directory decision_point_attention_research removed successfully.
19
+ **************************************************
20
+ Beginning phase: literature review
21
+ **************************************************
22
+ &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
23
+ Beginning subtask: literature review
24
+ &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
25
+ Current experiment cost = $0.002765, ** Approximate values, may not reflect true cost
26
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
27
+ ```SUMMARY
28
+ token routing attention
29
+ ``````SUMMARY
30
+ token routing attention
31
+ ```
32
+ ~~~~~~~~~~~
33
+ @@ Lab #1 Paper #0 @@
34
+ Current experiment cost = $0.008950000000000001, ** Approximate values, may not reflect true cost
35
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
36
+ ```FULL_TEXT
37
+ 2003.05997v5
38
+ ```
39
+ ~~~~~~~~~~~
40
+ @@ Lab #1 Paper #0 @@
41
+ Current experiment cost = $0.055130000000000005, ** Approximate values, may not reflect true cost
42
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
43
+ ```ADD_PAPER
44
+ 2003.05997v5
45
+ Routing Transformer (Roy et al., 2020) proposes content-based sparse self-attention via token “routing” using online spherical k-means clustering over queries/keys. Tokens are assigned to clusters (balanced top-k per centroid for efficiency), and each query attends only to keys in its cluster (plus typically some local attention heads), yielding native sparsity without materializing the full n×n attention matrix. Complexity drops from O(n^2 d) to ~O(n^1.5 d) with k≈√n. This is conceptually relevant to Decision Point Attention (DPA) because it demonstrates learned, token-dependent routing decisions that allocate expensive attention computation to a subset of token pairs based on content similarity—an early precursor to token-level conditional attention compute.
46
+
47
+ Experimental results (key takeaways):
48
+ - CIFAR-10 ablations (12 layers, 8 heads, seq len 3072): Local attention is a strong baseline (3.009 bits/dim vs full attention 2.983). Adding a small number of routing heads/layers closes the gap and can slightly outperform full attention: best reported is 2.950 bits/dim with attention window 1024, 4 routing heads, 2 routing layers; and 2.958 bits/dim with 4 routing heads, 4 routing layers. Importantly, replacing routing with random sparse connections hurts (Random Transformer 3.076 bits/dim), supporting that learned routing (not sparsity alone) drives gains. Overusing routing (e.g., 8 routing heads and many routing layers) degrades quality (up to 3.400 bits/dim), suggesting that mixing local + selective global routing is beneficial—aligned with DPA’s “default cheap, selective expensive” philosophy.
49
+ - Wikitext-103 word-level LM: Routing Transformer achieves 15.8 test perplexity (10 layers, 16 heads), improving over Transformer-XL’s 18.3 (18 layers) and Adaptive Input’s 18.7, showing routing can improve long-context modeling without recurrence.
50
+ - ImageNet-64 unconditional generation: 3.43 bits/dim (24 layers, 16 heads) vs Sparse Transformer 3.44 and scaled local ImageTransformer 3.48, indicating routing improves global consistency for long sequences.
51
+ - PG-19 long-context LM (seq len 8192): 33.2 test perplexity (22 layers, 8 heads) beating Compressive Transformer 33.6 and Transformer-XL 36.3, demonstrating routing’s effectiveness at very long contexts. The paper also reports that local attention remains a strong baseline (39.3 ppl for 24-layer local), but routing substantially closes the gap to SOTA.
52
+
53
+ Relevance to DPA: While Routing Transformer routes by content similarity (cluster membership) rather than “decision-point” semantics, it provides strong evidence that (i) local/cheap attention can handle much of the workload, (ii) selectively adding global/content-based attention yields large quality gains, and (iii) learned routing decisions outperform random or uniform sparse patterns. These findings motivate DPA’s token-level router that sends only critical agent “decision point” tokens through full softmax attention while keeping routine tokens on linear attention backbones.
54
+ ```
55
+ ~~~~~~~~~~~
56
+ @@ Lab #1 Paper #0 @@
57
+ Current experiment cost = $0.09809000000000001, ** Approximate values, may not reflect true cost
58
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
59
+ ```SUMMARY
60
+ hybrid attention routing
61
+ ```
62
+ ~~~~~~~~~~~
63
+ @@ Lab #1 Paper #0 @@
64
+ Current experiment cost = $0.142815, ** Approximate values, may not reflect true cost
65
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66
+ ```FULL_TEXT
67
+ 2407.04400v1
68
+ ```
69
+ ~~~~~~~~~~~
70
+ @@ Lab #1 Paper #0 @@
71
+ Current experiment cost = $0.22742500000000002, ** Approximate values, may not reflect true cost
72
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
73
+ ```ADD_PAPER
74
+ 2407.04400v1
75
+ Hard-Attention Gates with Gradient Routing (Roffo et al., 2024) introduces a lightweight “hard-attention” style gating module (HAG / Feature-Selection Gate) that multiplicatively reweights features (channels/embeddings) with independent sigmoid gates (not a softmax distribution), plus a two-stage training procedure called Gradient Routing (GR) that updates gate parameters separately from the main network via dual forward passes and distinct gradient clipping thresholds. While not an attention-routing paper for LLMs, it is relevant to Decision Point Attention (DPA) as a concrete example of (i) learned binary-ish gating to induce sparsity/conditional computation and (ii) decoupled optimization of router/gates vs backbone—both design patterns DPA may adopt for token routers.
76
+
77
+ Experimental results (emphasis on reported metrics):
78
+ - CIFAR-100 classification (Exp. 1): Adding HAG improves ResNet-18 accuracy from 73.9% to 75.2% (+1.3%). For ViT-Tiny, HAG boosts accuracy from 78.0% to 83.8% (+5.8%), a large gain for a small architectural change; the paper claims minimal added parameters/FLOPs (ViT-T remains 5.6M params, 4.7G FLOPs).
79
+ - Endoscopic polyp sizing (232 polyps, 372,040 frames; 6-fold CV) bias–variance analysis (Exp. 2, Table II):
80
+ * ViT-Tiny (RGB) improves Balanced Accuracy 51.3%→54.9% (+3.6), Avg Sens–Spec 55.7%→59.5% (+3.8), F1 75.6%→79.1% (+3.5), raising “global average of metrics” 60.87%→64.50% (+3.63).
81
+ * ResNet-18 (DPT) sees large F1 gain 66.2%→76.5% (+10.3) and Avg Sens–Spec 46.8%→50.1% (+3.3), with global average 53.30%→57.80% (+4.5).
82
+ * MultiStream-R18 (LOC+DPT) improves to BA 53.5%, F1 79.5%, global average 63.0% (vs 61.1% baseline).
83
+ - Consolidated fold evaluation for classification (Exp. 3, Table III):
84
+ * Binary task (<10mm vs ≥10mm): best is HAG MultiStream-R18 (LOC+DPT) with BA 59.96%, F1 87.84%, Sens–Spec 69.23%, average 72.34%.
85
+ * Triclass task (≤5mm / 5–10mm / ≥10mm): HAG ViT-T (RGB) achieves BA 48.93%, F1 76.47%, Sens–Spec 55.62%, average 60.34%. The paper highlights efficiency: ViT-T (5.6M params, 4.7G FLOPs) vs MultiStream-R18 (~22.5M params, 5.3G FLOPs).
86
+
87
+ Relevance to DPA: HAG+GR provides empirical evidence that (a) sparse/near-binary gating can improve generalization and performance with minimal overhead, and (b) training the gating mechanism with separate optimization/clipping can stabilize learning—both directly applicable to training a token-level “decision point” router that must learn sharp routing boundaries without destabilizing the main model.
88
+ ```
89
+ ~~~~~~~~~~~
90
+ @@ Lab #1 Paper #0 @@
91
+ Current experiment cost = $0.27334500000000006, ** Approximate values, may not reflect true cost
92
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
93
+ ```SUMMARY
94
+ hybrid attention linear softmax
95
+ ```
96
+ ~~~~~~~~~~~
97
+ @@ Lab #1 Paper #0 @@
98
+ Current experiment cost = $0.32093, ** Approximate values, may not reflect true cost
99
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
100
+ ```FULL_TEXT
101
+ 2402.04347v1
102
+ ```
103
+ ~~~~~~~~~~~
104
+ @@ Lab #1 Paper #0 @@
105
+ Current experiment cost = $0.41193250000000003, ** Approximate values, may not reflect true cost
106
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
107
+ ```ADD_PAPER
108
+ 2402.04347v1
109
+ Zhang et al. (2024) “The Hedgehog & the Porcupine: Expressive Linear Attentions with Softmax Mimicry” proposes Hedgehog, a learnable linear attention mechanism that closes much of the quality gap to softmax attention by explicitly training the linear attention feature map to mimic softmax attention weights. The paper identifies two properties that correlate with softmax attention quality but are typically missing in linear attention: (1) low-entropy (“spiky”) attention distributions and (2) monotonicity of attention weights w.r.t. query–key dot products. Hedgehog uses per-layer, per-head 1-layer MLP feature maps (with elementwise exp / softmax-normalized variants for stability) and trains them with an attention-weight distillation loss (cross-entropy between softmax attention weights and linear attention weights). This is directly relevant to DPA because it (i) strengthens the “linear backbone” option by making linear attention more softmax-like at decision points, and (ii) provides concrete evidence that matching softmax’s spikiness/monotonicity is key for reasoning/recall—exactly the failure mode motivating routing some tokens to full softmax.
110
+
111
+ Key experimental results (with emphasis on reported metrics):
112
+ - Diagnosing why linear attention underperforms:
113
+ * Associative Recall (AR) proxy task: performance strongly tracks attention entropy. Softmax solves AR (100% acc) while common linear attentions (1+ELU, Performer, cosFormer) are ~17% acc and have much higher-entropy (less spiky) weights (Fig. 4/Table 2).
114
+ * Finetuned-conversion stress test (BERT-base finetuned on CoLA): swapping in prior linear attentions fails badly (Matthew’s corr drops from 58.8 to 24.7–45.9 depending on method; Table 1), aligning with observed non-monotonic attention-vs-dot-product behavior (Fig. 3).
115
+ - Hedgehog expressivity + efficiency:
116
+ * Matches both AR and BERT conversion while keeping O(n d^2) complexity: AR 100% and CoLA 59.2 vs softmax 58.8 (Table 3). A Taylor-exp approximation also matches quality but is O(n d^3), making it impractical.
117
+ * Real-world efficiency: for a single 12-head layer (head dim 64) up to 32K tokens, Hedgehog shows near-linear scaling and is reported ~6× faster inference than FlashAttention (quadratic time) with similar memory scaling (Fig. 6), while Taylor-exp is slower/more memory-hungry due to expanded feature dimension.
118
+ - Training-from-scratch benchmarks:
119
+ * Long Range Arena (LRA): Hedgehog achieves best average accuracy among competitive efficient Transformers reported in the table (59.66% avg), comparable to top kernel/Nyström variants (Table 6).
120
+ * WikiText-103 (125M causal decoder, 1024 tokens): perplexity improves from typical linear baselines (e.g., Performer 26.8, AFT 28.2) to 20.8 with Hedgehog, substantially closing the gap to softmax Transformer at 18.6 (Table 7). The paper frames this as closing ~68.6% of the gap.
121
+ - Finetuned-conversion (quality recovery after swapping attentions):
122
+ * GLUE (BERT-base): Hedgehog recovers 99.3% of the original finetuned BERT average, with per-task examples: CoLA 59.2 vs 58.8 baseline; SST-2 92.6 vs 93.2; MRPC 90.1 vs 90.2; MNLI 82.6 vs 84.7; RTE 69.3 vs 68.2 (Table 8). Prior conversion baseline T2R averages 88.9% recovery.
123
+ * Vision (ViT-B/16 ImageNet-1K): Top-1 drops from 80.3 to 79.5 with Hedgehog (vs 77.0 for T2R-HH), i.e., ~99% accuracy retention (Table 9).
124
+ - Pretrained-conversion:
125
+ * GPT-2 125M → linearized + finetuned on WT-103: Hedgehog reaches 16.7 perplexity, outperforming other subquadratic baselines listed (Hybrid/H3 18.5, Hyena 19.4) (Table 10).
126
+ * Llama-2 7B conversion + LoRA on SAMSum: Hedgehog-LoRA achieves ROUGE-1/2/L = 47.4/23.4/39.1 vs T2R-Lo
127
+ ~~~~~~~~~~~
128
+ @@ Lab #1 Paper #0 @@
129
+ Current experiment cost = $0.47284000000000004, ** Approximate values, may not reflect true cost
130
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
131
+ ```ADD_PAPER
132
+ 2402.04347
133
+ Hedgehog (Zhang et al., 2024) introduces a learnable linear attention that explicitly distills softmax attention weights to close the quality gap between linear and softmax attention. The paper diagnoses two properties that correlate with softmax attention quality but are typically missing in linear attention: (i) low-entropy (“spiky”) attention distributions and (ii) monotonicity of attention weights with respect to query–key dot products. Hedgehog uses per-layer/per-head 1-layer MLP feature maps (with exp/softmax-style normalization variants for stability) and trains them with an attention-weight distillation objective (cross-entropy between softmax and linear attention weights). This is relevant to Decision Point Attention (DPA) because it provides concrete evidence that linear attention failures on recall/reasoning are tied to missing spikiness/monotonicity—supporting DPA’s premise that only certain “decision point” tokens need softmax-like behavior.
134
+
135
+ Experimental results (key metrics):
136
+ - Failure analysis of prior linear attention:
137
+ • Associative Recall (AR): softmax achieves 100% accuracy, while common linear attentions (1+ELU / Performer / cosFormer) are ~17% accuracy; AR accuracy tracks attention entropy (spikiness) (Fig. 4, Tables 1–2).
138
+ • BERT finetuned-conversion on CoLA: replacing attention with prior linear variants severely degrades Matthew’s correlation from 58.8 (softmax) to 24.7–45.9 depending on method, consistent with observed non-monotonic attention-vs-dot-product behavior (Fig. 3, Table 1).
139
+ - Hedgehog quality vs efficiency:
140
+ • Matches softmax-level performance with O(n d^2) complexity: AR 100% and CoLA 59.2 vs softmax 58.8 (Table 3). A Taylor-exp approximation also matches quality but is O(n d^3) and impractical (Table 2).
141
+ • Efficiency benchmark: for a 12-head layer (head dim 64) up to 32K tokens, Hedgehog shows near-linear scaling and is reported ~6× faster inference than FlashAttention (quadratic time) with similar memory scaling; Taylor-exp is slower/more memory-heavy due to expanded feature dimension (Fig. 6).
142
+ - Training-from-scratch:
143
+ • Long Range Arena (LRA): Hedgehog achieves best average accuracy among the efficient Transformer baselines listed (59.66% avg; Table 6).
144
+ • WikiText-103 (125M decoder, 1024 tokens): perplexity improves from typical linear baselines (Performer 26.8; AFT 28.2) to 20.8 with Hedgehog, substantially closing the gap to softmax Transformer at 18.6 (Table 7).
145
+ - Finetuned-conversion:
146
+ • GLUE (BERT-base): Hedgehog recovers 99.3% of original finetuned BERT average; e.g., CoLA 59.2 vs 58.8, SST-2 92.6 vs 93.2, MRPC 90.1 vs 90.2, MNLI 82.6 vs 84.7, RTE 69.3 vs 68.2 (Table 8). T2R baseline recovers 88.9%.
147
+ • Vision (ViT-B/16 ImageNet-1K): top-1 accuracy 79.5 vs 80.3 for original ViT (~99% retention), outperforming T2R-HH at 77.0 (Table 9).
148
+ - Pretrained-conversion:
149
+ • GPT-2 125M → linearized + finetuned on WT-103: Hedgehog reaches 16.7 perplexity, outperforming listed subquadratic baselines (Hybrid/H3 18.5; Hyena 19.4) (Table 10).
150
+ • Llama-2 7B conversion + LoRA on SAMSum: ROUGE-1/2/L = 47.4/23.4/39.1 vs T2R-LoRA 2.8/0.0/2.6, showing prior linear conversions can collapse while Hedgehog remains usable (Table 11).
151
+ ```
152
+ ~~~~~~~~~~~
153
+ @@ Lab #1 Paper #0 @@
154
+ Current experiment cost = $0.5269425000000001, ** Approximate values, may not reflect true cost
155
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
156
+ ```SUMMARY
157
+ token routing softmax linear attention
158
+ ```
159
+ ~~~~~~~~~~~
160
+ @@ Lab #1 Paper #0 @@
161
+ Current experiment cost = $0.5826775000000001, ** Approximate values, may not reflect true cost
162
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
163
+ ```FULL_TEXT
164
+ 2310.11685v2
165
+ ```
166
+ ~~~~~~~~~~~
167
+ @@ Lab #1 Paper #0 @@
168
+ Current experiment cost = $0.6569950000000001, ** Approximate values, may not reflect true cost
169
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
170
+ ```ADD_PAPER
171
+ 2310.11685
172
+ Deng, Song, Yuan, and Zhou (“Why Softmax Attention Outperforms Linear Attention”, arXiv:2310.11685v2) provides a theory-driven explanation for why linear attention can systematically fail on certain sequence discrimination problems that softmax attention solves, supporting DPA’s motivation that only a small subset of “decision-point” tokens may require softmax’s selective, high-contrast weighting. The paper constructs explicit binary classification datasets where a 4-layer network with softmax attention + ReLU separates the classes with high probability, while an otherwise matched network using linear attention (replacing exp with a linear map in attention weights) provably cannot distinguish them (both for self-attention and cross-attention settings). The core phenomenon is that softmax normalization plus exponentiation can amplify a single “salient” token (a log-scale outlier) into a dominant attention mass, whereas linear attention spreads mass too evenly, washing out the signal.
173
+
174
+ Experimental results (simulation evidence for the theory):
175
+ - Softmax-regression toy model (Section 7.1, Fig. 3): The authors repeatedly sample random sign vectors and measure a “success event” where softmax-based model outputs >0 on D1 and 0 on D0 while the linear model outputs 0 on both. As sequence length n increases from ~100 to 2000 (with m=log n), the success ratio rises toward ~1.0, indicating the separation becomes reliably observable at larger n. Holding n=1000 and varying the width m from 1 to 100, the success ratio increases with m and approaches ~1.0, matching the proof requirement m=O(log(n/δ)).
176
+ - Self-attention simulations (Section 7.2, Figs. 4–5): For two different parameterizations of QK^T (they report cases like X=vec(I_d) and X=1), the success ratio (softmax separates; linear fails) is near 1.0 across broad ranges of n (200–1000) and improves with larger m. Sensitivity sweeps show the success ratio depends on dataset parameters controlling the “outlier strength” (a1) and background mass (c): increasing a1 generally increases success, while varying c changes the threshold τ=(c+0.1)√log n used in the construction; the plots show wide regions where success stays close to 1.0, suggesting robustness of the separation phenomenon rather than a knife-edge setting.
177
+ - Overall, the experiments are not downstream NLP benchmarks; they are controlled numerical validations that the theoretical separation manifests in practice and becomes more reliable as n and m scale as predicted.
178
+
179
+ Relevance to DPA: The constructed datasets formalize a “single critical token dominates” regime where softmax’s ability to concentrate attention mass is essential, while linear attention provably fails. This aligns with DPA’s hypothesis that agentic failures of linear attention may be driven by sparse, high-stakes decision tokens (tool boundaries, plan revisions, error strings) that require sharp global selection; routing only those tokens through softmax layers could recover the needed selectivity without paying quadratic cost everywhere.
180
+ ```
181
+ ~~~~~~~~~~~
182
+ @@ Lab #1 Paper #0 @@
183
+ Current experiment cost = $0.72818, ** Approximate values, may not reflect true cost
184
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
185
+ ```SUMMARY
186
+ token-level routing softmax linear
187
+ ```
188
+ ~~~~~~~~~~~
189
+ @@ Lab #1 Paper #0 @@
190
+ Current experiment cost = $0.8004025, ** Approximate values, may not reflect true cost
191
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
192
+ ```FULL_TEXT
193
+ 1805.02867v2
194
+ ```
195
+ ~~~~~~~~~~~
196
+ @@ Lab #1 Paper #0 @@
197
+ PDF download retry 1/3: HTTP Error 429: Unknown Error
198
+ Current experiment cost = $0.8962525, ** Approximate values, may not reflect true cost
199
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
200
+ ```ADD_PAPER
201
+ 1805.02867
202
+ Milakov & Gimelshein (“Online normalizer calculation for softmax”, arXiv:1805.02867v2) proposes an implementation-level optimization for *exact* (numerically safe) softmax that reduces memory traffic by computing the running max and normalization term in a single pass (“Online Softmax”), and further shows large speedups when fusing Softmax with TopK. While not about hybrid attention per se, it is directly relevant to DPA because DPA’s routed decision-point tokens still require full softmax; this paper quantifies how much that softmax cost can be reduced on GPU, improving the end-to-end compute/latency tradeoff of any design that selectively invokes softmax.
203
+
204
+ Key idea / method:
205
+ - Standard “safe softmax” uses 3 passes over logits (max, sum exp, write outputs), i.e., ~4 memory accesses per element.
206
+ - Online Softmax (Alg. 3) maintains a running maximum m_j and running normalizer d_j with a recurrence: d_j = d_{j-1} * exp(m_{j-1}-m_j) + exp(x_j - m_j), enabling safe softmax with 2 passes total (one pass to compute m_V and d_V, one pass to write outputs), i.e., ~3 memory accesses per element.
207
+ - Defines an associative/commutative binary operator ⊕ over pairs (m,d) to enable parallel reduction for GPUs.
208
+ - Extends to a fused Softmax+TopK (Alg. 4) that keeps partial TopK during the single pass, so Softmax+TopK can be done with ~1 memory access per element (read once), avoiding writing the full probability vector.
209
+
210
+ Experimental results (Tesla V100, fp32, CUDA/CUB; Section 5):
211
+ - Softmax alone:
212
+ - Large batch (4000 vectors): Online vs Safe achieves up to ~1.3× speedup for large vocab sizes (notably V ≥ ~4000), closely matching the theoretical 4→3 memory-access reduction (≈1.33×). For V < ~1000, all methods similar (cache fits).
213
+ - Small batch (10 vectors): Online vs Safe gives ~1.15× speedup at large V; gains are smaller because the GPU is underutilized and latency dominates.
214
+ - Softmax + TopK (K=5):
215
+ - Large batch (4000 vectors): Online fused vs Safe unfused improves from ~1.5× at smaller V up to ~5× at V≈25k, aligning with the claimed ~5× reduction in memory accesses (fusion benefit ~2.5× plus Online Softmax’s ~2× vs safe in that pipeline).
216
+ - Small batch (10 vectors): Online fused vs Safe unfused yields ~1.5×–2.5×; still beneficial despite latency limits.
217
+ - Sensitivity to K: speedup drops as K increases because maintaining TopK dominates runtime (reported ~3.5× at K=10, ~2× at K=15, ~1.4× at K=30, worse beyond).
218
+
219
+ Relevance to DPA:
220
+ - DPA’s “decision-point” routing concentrates softmax compute on a small token subset; this paper shows that the remaining softmax kernels themselves can be materially faster (especially when paired with TopK-like operations common in decoding/tool selection), strengthening the practical case that selective softmax can be made even cheaper in wall-clock time, not just asymptotic FLOPs.
221
+ ```
222
+ ~~~~~~~~~~~
223
+ @@ Lab #1 Paper #0 @@
224
+ Current experiment cost = $0.9387900000000001, ** Approximate values, may not reflect true cost
225
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
226
+ ```SUMMARY
227
+ token routing attention hybrid
228
+ ```
229
+ ~~~~~~~~~~~
230
+ @@ Lab #1 Paper #0 @@
231
+ Current experiment cost = $0.9828325000000001, ** Approximate values, may not reflect true cost
232
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
233
+ ```SUMMARY
234
+ mixture of attention
235
+ ```
236
+ ~~~~~~~~~~~
237
+ @@ Lab #1 Paper #0 @@
238
+ Current experiment cost = $1.0307600000000001, ** Approximate values, may not reflect true cost
239
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
240
+ ```FULL_TEXT
241
+ 2601.15305v1
242
+ ```
243
+ ~~~~~~~~~~~
244
+ @@ Lab #1 Paper #0 @@
245
+ Current experiment cost = $1.0896075, ** Approximate values, may not reflect true cost
246
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
247
+ ```ADD_PAPER
248
+ 2601.15305v1
249
+ Gated Sparse Attention (GSA) proposes a long-context attention layer that combines (i) sparse token selection (via a DeepSeek-style “lightning indexer”) with (ii) sigmoid gating (G1 output gating + G2 value gating) to improve training stability and mitigate attention sinks, while retaining sub-quadratic compute. This is directly relevant to Decision Point Attention (DPA): GSA is another “route/allocate expensive attention only where needed” design, but it selects top-k tokens per query (content-based sparsity) rather than routing a labeled subset of “decision point” tokens to full softmax. Its empirical evidence on reasoning and long-context retrieval provides a strong baseline and suggests that gating + selective compute can recover quality while keeping efficiency—useful for motivating DPA’s token-level router and for ablations (e.g., DPA router vs top-k indexer; adding G1/G2 gates to DPA to avoid attention sinks when most tokens use linear attention).
250
+
251
+ Key experimental results (extensively, with metrics):
252
+ - Training setup: 1.7B-parameter models trained from scratch on 400B tokens (SlimPajama), 24 layers, d=2048, 16 Q heads / 4 KV heads; trained at 4K context and evaluated up to 128K using YaRN interpolation. Compared baselines: (a) Standard dense attention, (b) Sparse-only (DSA-like indexer + fixed k), (c) Gated-only (full attention + G1+G2), (d) GSA (sparse + gated).
253
+ - Language modeling perplexity (Table 4; lower is better):
254
+ • WikiText-103: Standard 6.03; Sparse-only 6.02; Gated-only 5.76; GSA 5.70 (best).
255
+ • C4: Standard 7.82; Sparse-only 7.79; Gated-only 7.45; GSA 7.38 (best).
256
+ Interpretation: sparsity alone barely changes PPL, gating gives most gains, and combining gating+sparsity yields additional improvement—suggesting “where to spend attention” plus “stabilize attention outputs” are complementary.
257
+ - Downstream reasoning/knowledge benchmarks (Table 5; accuracy %):
258
+ • MMLU: 58.8 (Std) → 61.4 (GSA), +2.6 over Std and +0.6 over Gated-only (60.8).
259
+ • GSM8K: 52.9 (Std) → 56.0 (GSA), +3.1 over Std and +0.7 over Gated-only (55.3).
260
+ • HumanEval: 28.7 (Std) → 30.5 (GSA), +1.8.
261
+ • HellaSwag: 73.1 (Std) → 74.9 (GSA), +1.8.
262
+ • C-Eval: 60.3 (Std) → 62.9 (GSA), +2.6.
263
+ These gains—especially GSM8K—are pertinent to DPA’s focus on multi-step reasoning: selective attention mechanisms can improve reasoning quality, not just efficiency.
264
+ - Long-context retrieval/reasoning (RULER; Table 6; higher is better), evaluated at 4K/8K/16K/32K and extended 64K/128K:
265
+ • At 128K: Standard 31.7; Sparse-only 36.8; Gated-only 58.8; GSA 62.2.
266
+ • At 64K: Standard 37.5; Sparse-only 42.4; Gated-only 66.6; GSA 69.5.
267
+ Takeaway: dense attention collapses at very long contexts; gating dramatically improves long-context behavior; sparsity adds further gains when combined with gating. For DPA, this suggests adding gating to the “cheap path” (linear attention) may be important for long-horizon agent trajectories.
268
+ - Attention sink mitigation + activation control (Table 7):
269
+ • First-token attention mass: 46.7% (Std) → 38.2% (Sparse-only) → 4.8% (Gated-only) → 3.9% (GSA).
270
+ • Mean gate value: ~0.108 (GSA), indicating strong sparsity in gating activations.
271
+ • Max activation magnitude: 1053 (Std) → 892 (Sparse-only) → 94 (Gated-only) → 87 (GSA).
272
+ This is highly relevant to DPA: if most tokens are routed away from full softmax, sink behavior and instability can worsen;
273
+ ~~~~~~~~~~~
274
+ @@ Lab #1 Paper #0 @@
275
+ Current experiment cost = $1.153475, ** Approximate values, may not reflect true cost
276
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
277
+ ```ADD_PAPER
278
+ 2601.15305v1
279
+ Gated Sparse Attention (GSA) introduces a long-context attention layer that fuses (a) content-based sparse token selection (a “lightning indexer” that scores all tokens in a low-dimensional space and then attends only to top‑k) with (b) sigmoid gating applied to values (G2) and to the attention output (G1). The goal is to keep attention sub-quadratic while improving training stability and eliminating “attention sinks” (excess mass on early tokens). This is relevant to Decision Point Attention (DPA) because it is a strong alternative “selective compute” baseline: instead of routing a labeled subset of decision tokens to full attention, GSA selects tokens per query via an indexer; its results suggest that (i) gating is crucial for long-context robustness and (ii) sparsity alone is insufficient for quality—insights directly useful for DPA design/ablations (e.g., adding G1/G2-style gates to the linear path; comparing DPA’s supervised router vs top‑k indexer selection).
280
+
281
+ Experimental results (with key metrics):
282
+ - Training/eval setup: 1.7B-parameter models trained from scratch on 400B SlimPajama tokens; 24 layers, d=2048, 16 Q heads / 4 KV heads. Train at 4K context; evaluate up to 128K using YaRN interpolation. Baselines: Standard dense attention; Sparse-only (DSA-style indexer + fixed k); Gated-only (full attention + G1+G2); GSA (sparse + gated).
283
+ - Language modeling perplexity (Table 4, lower is better):
284
+ • WikiText-103: Standard 6.03; Sparse-only 6.02; Gated-only 5.76; GSA 5.70 (best).
285
+ • C4: Standard 7.82; Sparse-only 7.79; Gated-only 7.45; GSA 7.38 (best).
286
+ Conclusion: gating drives most of the quality gain; sparsity alone barely helps PPL, but sparsity + gating improves further.
287
+ - Downstream benchmarks (Table 5, accuracy %, higher is better):
288
+ • MMLU: 58.8 (Std) → 61.4 (GSA).
289
+ • GSM8K: 52.9 (Std) → 56.0 (GSA).
290
+ • HumanEval: 28.7 (Std) → 30.5 (GSA).
291
+ • HellaSwag: 73.1 (Std) → 74.9 (GSA).
292
+ • C‑Eval: 60.3 (Std) → 62.9 (GSA).
293
+ These gains (notably GSM8K) support the idea that selective attention mechanisms can improve multi-step reasoning quality, not just efficiency.
294
+ - Long-context retrieval/reasoning (RULER; Table 6, higher is better):
295
+ • 64K: Standard 37.5; Sparse-only 42.4; Gated-only 66.6; GSA 69.5.
296
+ • 128K: Standard 31.7; Sparse-only 36.8; Gated-only 58.8; GSA 62.2.
297
+ Takeaway: dense attention collapses at very long contexts; gating largely fixes this; adding sparsity on top yields additional gains.
298
+ - Attention sinks + activation magnitudes (Table 7):
299
+ • First-token attention mass: 46.7% (Std) → 38.2% (Sparse-only) → 4.8% (Gated-only) → 3.9% (GSA).
300
+ • Mean gate value: 0.108 (GSA), indicating strong gating sparsity.
301
+ • Max activation: 1053 (Std) → 892 (Sparse-only) → 94 (Gated-only) → 87 (GSA).
302
+ Implication for DPA: if most tokens use a “cheap path,” explicit sink-mitigation (gating) may be necessary to avoid pathological attention allocation and instability.
303
+ - Training stability (Table 8):
304
+ • Loss spikes per 100K steps: 12.3 (Std) vs 0.3 (GSA).
305
+ • Max stable LR: 4e‑3 (Std) vs 8e‑3 (GSA).
306
+ This suggests gating can materially improve optimization robustness—important for training hybrid/routed attention systems.
307
+ - Efficiency at 128K (Table 9, relative to standard):
308
+ • Prefill: 0.09× (GSA) vs 1.00× (Std).
309
+ • Decode: 0.13× (GSA) vs
310
+ ~~~~~~~~~~~
311
+ @@ Lab #1 Paper #0 @@
312
+ Current experiment cost = $1.216595, ** Approximate values, may not reflect true cost
313
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
314
+ ```ADD_PAPER
315
+ 2601.15305v1
316
+ Gated Sparse Attention (GSA, arXiv:2601.15305v1, 2026) proposes a hybrid attention layer for long-context LMs that combines (1) content-based sparse attention via a low-dim “lightning indexer” that selects top‑k tokens per query, and (2) sigmoid gating applied both to values (G2) and to the attention output (G1) to improve stability and eliminate attention sinks. This is relevant to Decision Point Attention (DPA) because it is a strong alternative selective-compute baseline: instead of routing a supervised subset of “decision tokens” to softmax, GSA routes attention computation by top‑k selection and shows that gating is crucial for long-context robustness—an insight likely transferable to DPA’s “cheap path” (linear attention) to avoid sink/pathology when most tokens bypass full softmax.
317
+
318
+ Experimental results (detailed):
319
+ - Setup: 1.7B-parameter models trained from scratch on 400B SlimPajama tokens; 24 layers, d=2048, 16 query heads / 4 KV heads. Train with 4K context; evaluate up to 128K using YaRN interpolation. Baselines: Standard dense attention; Sparse-only (DSA-style indexer + fixed k); Gated-only (full attention + G1+G2); GSA (sparse + gated).
320
+ - Language modeling perplexity (Table 4, lower is better):
321
+ • WikiText-103: Standard 6.03; Sparse-only 6.02; Gated-only 5.76; GSA 5.70 (best).
322
+ • C4: Standard 7.82; Sparse-only 7.79; Gated-only 7.45; GSA 7.38 (best).
323
+ Interpretation: sparsity alone barely changes PPL; gating yields most gains; combining gating+sparsity yields additional improvement.
324
+ - Downstream tasks (Table 5, accuracy %, higher is better):
325
+ • MMLU: 58.8 (Std) → 61.4 (GSA), +2.6.
326
+ • GSM8K: 52.9 (Std) → 56.0 (GSA), +3.1.
327
+ • HumanEval: 28.7 (Std) → 30.5 (GSA), +1.8.
328
+ • HellaSwag: 73.1 (Std) → 74.9 (GSA), +1.8.
329
+ • C‑Eval: 60.3 (Std) → 62.9 (GSA), +2.6.
330
+ Notably, GSM8K improves substantially, aligning with DPA’s focus on multi-step reasoning.
331
+ - Long-context retrieval/reasoning (RULER; Table 6, higher is better):
332
+ • 64K: Standard 37.5; Sparse-only 42.4; Gated-only 66.6; GSA 69.5.
333
+ • 128K: Standard 31.7; Sparse-only 36.8; Gated-only 58.8; GSA 62.2.
334
+ Takeaway: dense attention collapses at very long contexts; gating largely fixes this; sparsity adds further gains when combined with gating.
335
+ - Attention sinks + activation control (Table 7):
336
+ • First-token attention mass: 46.7% (Std) vs 3.9% (GSA).
337
+ • Mean gate value: 0.108 (GSA), indicating strong sparsity in gating activations.
338
+ • Max activation magnitude: 1053 (Std) vs 87 (GSA), ~12× reduction.
339
+ Implication for DPA: when most tokens are routed away from full softmax, explicit sink-mitigation/stabilization (e.g., gating) may be necessary.
340
+ - Training stability (Table 8):
341
+ • Loss spikes per 100K steps: 12.3 (Std) vs 0.3 (GSA), ~98% reduction.
342
+ • Max stable learning rate: 4e‑3 (Std) vs 8e‑3 (GSA), 2× higher.
343
+ - Efficiency at 128K context (Table 9, relative to standard):
344
+ • Prefill: 0.09× (GSA) and Decode: 0.13× (GSA), i.e., ~11× and ~8× faster respectively, while memory stays ~0.97× of standard.
345
+ - Ablations supporting design choices:
346
+ • Gate placement (Table 10): No gating PPL 6.02; G2-only 5.82; G1-only 5.79; G1+
347
+ ~~~~~~~~~~~
348
+ @@ Lab #1 Paper #0 @@
349
+ Current experiment cost = $1.2801825000000002, ** Approximate values, may not reflect true cost
350
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
351
+ ```ADD_PAPER
352
+ 2601.15305v1
353
+ Gated Sparse Attention (GSA) is a long-context LM attention layer that combines (i) content-based sparse attention via a low-dimensional “lightning indexer” that selects top‑k keys per query, with (ii) sigmoid gating on values (G2) and on the attention output (G1) to reduce attention sinks and stabilize training. This is relevant to Decision Point Attention (DPA) as a strong selective-compute baseline: GSA routes *which tokens are attended to* (top‑k per query) rather than routing *which tokens get softmax*, and its results suggest that adding gating/regularization to the “cheap path” is critical when most tokens do not use full dense attention.
354
+
355
+ Experimental results (key numbers):
356
+ - Scale/setup: 1.7B models trained from scratch on 400B SlimPajama tokens; train at 4K context, evaluate up to 128K with YaRN. Baselines: Standard dense attention; Sparse-only (indexer + fixed k); Gated-only (dense + G1+G2); GSA (sparse + gates).
357
+ - Language modeling perplexity (Table 4, lower better):
358
+ • WikiText-103: Standard 6.03; Sparse-only 6.02; Gated-only 5.76; GSA 5.70.
359
+ • C4: Standard 7.82; Sparse-only 7.79; Gated-only 7.45; GSA 7.38.
360
+ Takeaway: sparsity alone barely helps PPL; gating helps a lot; combining both is best.
361
+ - Downstream accuracy (Table 5, higher better):
362
+ • MMLU: 58.8 → 61.4 (GSA).
363
+ • GSM8K: 52.9 → 56.0.
364
+ • HumanEval: 28.7 → 30.5.
365
+ • HellaSwag: 73.1 → 74.9.
366
+ • C‑Eval: 60.3 → 62.9.
367
+ Notably improves multi-step reasoning (GSM8K) vs standard and vs gated-only.
368
+ - Long-context retrieval/reasoning (RULER; Table 6, higher better):
369
+ • 64K: Standard 37.5; Sparse-only 42.4; Gated-only 66.6; GSA 69.5.
370
+ • 128K: Standard 31.7; Sparse-only 36.8; Gated-only 58.8; GSA 62.2.
371
+ Dense attention collapses at long context; gating largely fixes it; sparsity adds further gains.
372
+ - Attention sinks + activation magnitudes (Table 7):
373
+ • First-token attention mass: 46.7% (Standard) vs 3.9% (GSA).
374
+ • Max activation: 1053 (Standard) vs 87 (GSA).
375
+ Suggests explicit sink-mitigation is important for hybrid/routed attention designs like DPA.
376
+ - Training stability (Table 8):
377
+ • Loss spikes / 100K steps: 12.3 (Standard) vs 0.3 (GSA).
378
+ • Max stable LR: 4e‑3 (Standard) vs 8e‑3 (GSA).
379
+ - Efficiency at 128K (Table 9, relative to Standard):
380
+ • Prefill 0.09×; Decode 0.13×; Memory 0.97×.
381
+ - Ablation (Table 10): output gating (G1) contributes most of the gains; G1+G2 is best (PPL 5.70, MMLU 61.4, “Excellent” stability).
382
+ ```
383
+ ~~~~~~~~~~~
384
+ @@ Lab #1 Paper #0 @@
385
+ Current experiment cost = $1.3158050000000001, ** Approximate values, may not reflect true cost
386
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
387
+ ```SUMMARY
388
+ token routing attention
389
+ ```
390
+ ~~~~~~~~~~~
391
+ @@ Lab #1 Paper #0 @@
392
+ Current experiment cost = $1.3495150000000002, ** Approximate values, may not reflect true cost
393
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
394
+ ```FULL_TEXT
395
+ 2003.05997v5
396
+ ```
397
+ ~~~~~~~~~~~
398
+ @@ Lab #1 Paper #0 @@
399
+ Current experiment cost = $1.4263675, ** Approximate values, may not reflect true cost
400
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
401
+ ```ADD_PAPER
402
+ 2003.05997v5
403
+ Routing Transformer (Roy et al., 2020) proposes content-based *token routing* for sparse softmax attention using online spherical k-means clustering: each query attends only to keys assigned to the same learned centroid, yielding native sparsity without instantiating a full attention matrix. Complexity drops from O(n^2 d) to O(n^1.5 d) by choosing k≈√n, and the model mixes routing heads with local attention heads (local for fluency + routing for global consistency). This is directly relevant to Decision Point Attention (DPA) because it is an early, well-studied example of *learned routing decisions over tokens* to allocate expensive attention computation selectively; DPA can be viewed as a different routing granularity (binary route-to-softmax vs stay-in-linear) and can borrow evaluation/ablation patterns (how many routed heads/layers, where to place them, and what happens when routing is random).
404
+
405
+ Experimental results (with key metrics and comparisons):
406
+ - Wikitext-103 word-level LM (Table 2): Routing Transformer achieves **15.8 test perplexity** with **10 layers / 16 heads**, outperforming strong efficient-attention baselines:
407
+ • Transformer-XL: **18.3** (18 layers / 16 heads)
408
+ • Adaptive Input: **18.7** (16 layers / 16 heads)
409
+ • Adaptive Transformer: **20.6** (36 layers / 8 heads)
410
+ • Local Transformer baseline: **19.8** (16 layers / 16 heads)
411
+ This shows that adding a *small amount of global, content-routed attention* can substantially improve long-range language modeling vs purely local sparse attention.
412
+ - PG-19 long-context LM (Table 5): Routing Transformer reaches **33.2 test perplexity** (22 layers / 8 heads, seq len 8192), improving over:
413
+ • Compressive Transformer: **33.6** (36 layers)
414
+ • Transformer-XL: **36.3** (36 layers)
415
+ • Local Transformer: **39.3** (24 layers)
416
+ Notably, for PG-19 they use only **2 routing heads** and place routing in the **last two layers**, supporting the design hypothesis that *global attention is most valuable late in the network*—a placement insight highly relevant to DPA’s “decision-point” softmax layers.
417
+ - ImageNet-64 autoregressive image generation (Table 4): Routing Transformer achieves **3.43 bits/dim**, slightly improving over Sparse Transformer **3.44** and outperforming a scaled local ImageTransformer **3.48** (Reformer reported **3.65**). This indicates routing-based global attention can help beyond text.
418
+ - enwik-8 character LM (Table 3): Routing Transformer achieves **0.99 bits/byte**, matching Transformer-XL and Sparse Transformer (0.99) but not surpassing Adaptive Transformer (0.98). This suggests routing helps, but gains can be task-dependent.
419
+ - CIFAR-10 ablations (Table 1): With 12 layers / 8 heads, routing improves over local-only and random routing:
420
+ • Full attention Transformer: **2.983 bits/dim** (5.608 steps/s)
421
+ • Local Transformer (window 512): **3.009** (**9.023 steps/s**)
422
+ • Random “routing” (same budget): **3.076** (5.448 steps/s)
423
+ • Best routing configs reach as low as **2.950–2.958 bits/dim** at window 1024 (e.g., 4 routing heads, 2–4 routing layers), showing (i) routing must be *content-based* (random hurts), and (ii) a *mixture* of local + routed heads is important (too many routing layers/heads degrades, e.g., 8 routing heads & many routing layers gets much worse).
424
+ - Analysis of attention behavior (Table 6): Jensen–Shannon divergence between local vs routing attention distributions is often near the upper bound (e.g., ~0.60–0.67 in many layers), empirically supporting that routing heads learn qualitatively non-local patterns complementary to local attention—consistent with DPA’s premise that only some tokens/steps need expensive global interactions.
425
+ - Efficiency tradeoff (Table 7, PG-19): Routing Transformer trains slower than local attention on TPU due to sparse-kernel limitations: **0.724 steps/s** (routing) vs **1.231 steps/s** (local), ~**1.7×** slower. This is a useful caution for DPA: routing mechanisms can introduce overhead unless implemented with hardware-friendly kernels; DPA’s binary routing
426
+ ~~~~~~~~~~~
427
+ @@ Lab #1 Paper #0 @@
428
+ Current experiment cost = $1.5038975, ** Approximate values, may not reflect true cost
429
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
430
+ ```ADD_PAPER
431
+ 2003.05997v5
432
+ Routing Transformer (Roy et al., 2020) introduces content-based token routing for sparse *softmax* attention using online spherical k-means: each query attends only to keys in the same learned cluster, giving native sparsity without forming the full n×n attention matrix. With k≈√n, attention cost becomes O(n^1.5 d). The architecture typically mixes local attention heads (for short-range fluency) with routing heads (for global, content-selected interactions). This is relevant to Decision Point Attention (DPA) as a canonical token-routing mechanism that selectively allocates expensive attention computation; DPA differs by routing tokens between linear vs full-softmax paths, but can borrow Routing Transformer’s placement/ratio ablations and “routing must be content-based” evidence.
433
+
434
+ Experimental results (emphasis on numbers and comparisons):
435
+ - Wikitext-103 (Table 2, test perplexity ↓): Routing Transformer achieves **15.8** (10 layers, 16 heads), outperforming:
436
+ • Transformer-XL **18.3** (18 layers, 16 heads)
437
+ • Adaptive Input **18.7** (16 layers, 16 heads)
438
+ • Local Transformer baseline **19.8** (16 layers, 16 heads)
439
+ • Adaptive Transformer **20.6** (36 layers, 8 heads)
440
+ This shows that adding a limited amount of global, content-routed attention can substantially beat purely local sparse attention.
441
+ - PG-19 long-context LM (Table 5, test perplexity ↓, seq len 8192): Routing Transformer reaches **33.2** (22 layers, 8 heads), improving over:
442
+ • Compressive Transformer **33.6** (36 layers)
443
+ • Transformer-XL **36.3** (36 layers)
444
+ • Local Transformer **39.3** (24 layers)
445
+ Notably, for PG-19 they use only **2 routing heads** and only in the **last two layers**, supporting the design insight that “global attention is most valuable late”—directly analogous to DPA’s idea that only certain tokens/steps need expensive processing.
446
+ - ImageNet-64 autoregressive generation (Table 4, bits/dim ↓): Routing Transformer achieves **3.43**, slightly better than Sparse Transformer **3.44** and better than a scaled local ImageTransformer **3.48** (Reformer reported **3.65** in the table).
447
+ - enwik-8 (Table 3, bits/byte ↓): Routing Transformer gets **0.99**, matching Transformer-XL and Sparse Transformer (**0.99**) but not surpassing Adaptive Transformer (**0.98**), suggesting routing gains can be dataset-dependent.
448
+ - CIFAR-10 ablations (Table 1, bits/dim ↓; speed in steps/sec ↑): with 12 layers / 8 heads:
449
+ • Full attention Transformer: **2.983** (5.608 steps/s)
450
+ • Local Transformer (window 512): **3.009** (9.023 steps/s)
451
+ • Random routing (same budget): **3.076** (5.448 steps/s)
452
+ • Best routing configs reach **~2.950–2.958** at window 1024 (e.g., 4 routing heads with 2–4 routing layers), showing (i) routing must be content-based (random hurts), and (ii) mixing local+routed heads works better than “all routing” (too many routing heads/layers degrades badly, e.g., 8 routing heads with many routing layers gives **3.131–3.400**).
453
+ - Attention-pattern analysis (Table 6): Jensen–Shannon divergence between local vs routing heads is often near the upper bound (~0.60–0.67 in many layers), indicating routing heads learn qualitatively non-local patterns complementary to local heads—consistent with DPA’s premise that only some tokens need global interactions.
454
+ - Efficiency tradeoff on TPU (Table 7, PG-19): Routing Transformer trains slower than local attention due to sparse-kernel overhead: **0.724 steps/s** (routing) vs **1.231 steps/s** (local), ~**1.7×** slower—important caution for DPA implementations (routing overhead can erase compute savings without optimized kernels).
455
+ ```
456
+ ~~~~~~~~~~~
457
+ @@ Lab #1 Paper #0 @@
458
+ Current experiment cost = $1.5721775000000002, ** Approximate values, may not reflect true cost
459
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
460
+ ```SUMMARY
461
+ token routing softmax linear hybrid
462
+ ```
463
+ ~~~~~~~~~~~
464
+ @@ Lab #1 Paper #0 @@
465
+ Current experiment cost = $1.6408600000000002, ** Approximate values, may not reflect true cost
466
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
467
+ ```FULL_TEXT
468
+ 2601.11164v1
469
+ ```
470
+ ~~~~~~~~~~~
471
+ @@ Lab #1 Paper #0 @@
472
+ PDF download retry 1/3: HTTP Error 429: Unknown Error
473
+ PDF download retry 2/3: HTTP Error 429: Unknown Error
474
+ PDF download retry 3/3: HTTP Error 429: Unknown Error
475
+ Current experiment cost = $1.6738375, ** Approximate values, may not reflect true cost
476
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
477
+ ```SUMMARY
478
+ SoLA-Vision 2601.11164
479
+ ```
480
+ ~~~~~~~~~~~
481
+ @@ Lab #1 Paper #0 @@
482
+ Current experiment cost = $1.7037775000000002, ** Approximate values, may not reflect true cost
483
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
484
+ ```FULL_TEXT
485
+ 2601.11164v1
486
+ ```
487
+ ~~~~~~~~~~~
488
+ @@ Lab #1 Paper #0 @@
489
+ PDF download retry 1/3: HTTP Error 429: Unknown Error
490
+ PDF download retry 2/3: HTTP Error 429: Unknown Error
491
+ PDF download retry 3/3: HTTP Error 429: Unknown Error
492
+ Current experiment cost = $1.7337225000000003, ** Approximate values, may not reflect true cost
493
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
494
+ ```FULL_TEXT
495
+ 2503.03213v1
496
+ ```
497
+ ~~~~~~~~~~~
498
+ @@ Lab #1 Paper #0 @@
499
+ Current experiment cost = $1.8086975, ** Approximate values, may not reflect true cost
500
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ literature review ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
501
+ ```ADD_PAPER
502
+ 2503.03213v1
503
+ Nguyen & Rinaldo (2025) study *softmax-gated mixture-of-experts (MoE)* and variants (dense-to-sparse temperature gating; hierarchical MoE) through a statistical convergence lens, showing how *gating parameterization can create intrinsic parameter-interaction pathologies* that dramatically slow expert/parameter estimation. While not an attention paper per se, it is directly relevant to Decision Point Attention (DPA) because DPA’s learned binary router is a conditional-computation gate; this paper provides theory-grounded warnings about router/expert interactions (expressed as PDE constraints) and motivates designing the router so it is “algebraically independent” from the expert/backbone to avoid slow/unstable learning.
504
+
505
+ Key theoretical results (what they imply for routing designs):
506
+ - They analyze least-squares estimation of MoE regression functions and show the *regression function* can be estimated at a parametric rate: ||f_{Ĝ}-f_{G*}||_{L2} = O_P((log n / n)^{1/2}) (Prop. 1; similarly Prop. 2/3 for variants). However, *parameter/expert estimation* can be much slower depending on identifiability.
507
+ - For standard softmax-gating MoE, they define a “strong identifiability” condition for experts (Def. 1; roughly: linear independence of expert derivatives up to 2nd order). Under this condition, they prove a lower bound linking function error to a Voronoi-style parameter loss, yielding parameter/expert estimation rates:
508
+ • exactly-specified components: O_P((log n / n)^{1/2})
509
+ • over-specified components: O_P((log n / n)^{1/4}) (Thm. 1)
510
+ Practical takeaway for DPA: if the “expert” (here, the backbone path chosen by the router) is sufficiently expressive/nonlinear and identifiable, routing can be learned sample-efficiently.
511
+ - For *linear experts*, they show a PDE-based interaction between gating and expert parameters (Eq. 9) that breaks strong identifiability and yields *slower-than-any-polynomial* minimax rates for estimating parameters/experts (Thm. 2). They interpret this as potentially as slow as O_P(1 / log^λ n).
512
+ Takeaway for DPA: if the router is too entangled with the backbone representation (e.g., effectively linear in the same features), learning the router/backbone split can be statistically ill-conditioned.
513
+ - For *dense-to-sparse (temperature) gating* with a *linear router*, they show an unavoidable interaction between temperature τ and gating weights ω (Eq. 15), which again forces slower-than-polynomial estimation rates *regardless of expert structure* (Thm. 3).
514
+ Takeaway for DPA: annealing/temperature-style sparsification of routing (common in MoE) can introduce hard-to-learn couplings; if DPA uses any temperature/relaxation for binary routing, the parameterization matters.
515
+ - They propose using a *general (nonlinear) router* and introduce an “algebraic independence” condition between router π(x,ω) and expert E(x,η) (Def. 2). Under this condition, they recover fast rates (Thm. 4): Voronoi loss O_P((log n / n)^{1/2}) (with the same exact vs over-specified split as above).
516
+ Takeaway for DPA: design the decision-token router to be sufficiently expressive and not a trivial reparameterization of the backbone’s expert computations; otherwise routing may be fundamentally hard to fit.
517
+ - For *hierarchical MoE*, they show analogous behavior: strongly identifiable experts admit polynomial rates (Thm. 5), while linear experts again suffer slow rates due to multi-level parameter interactions (Eq. 28; Thm. 6).
518
+ Takeaway for DPA extensions with multi-level routing (e.g., token-level + layer-level): hierarchical gating can amplify identifiability issues unless router/expert are well-separated.
519
+
520
+ Relevance to DPA experiments/ablations:
521
+ - Suggests a concrete ablation axis: router parameterization (linear vs nonlinear; shared vs separate features) and whether adding temperature/annealing harms learning.
522
+ - Provides a theoretical rationale for supervising the router (as DPA proposes) and/or architecturally separating router features from backbone state to avoid “interaction” pathologies.
523
+ - Motivates measuring router learning stability/sample-efficiency (how quickly the router reaches high precision/recall on decision tokens) as a first-class metric, not just downstream task accuracy.
524
+ ```
525
+ ~~~~~~~~~~~
526
+ @@ Lab #1 Paper #0 @@
527
+
528
+ Subtask 'literature review' completed in 933.73 seconds.
529
+ **************************************************
530
+ Beginning phase: plan formulation
531
+ **************************************************
532
+ &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
533
+ Beginning subtask: plan formulation
534
+ &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
535
+ @@ Lab #1 Paper #0 @@
536
+ Current experiment cost = $1.8380150000000002, ** Approximate values, may not reflect true cost
537
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ plan formulation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
538
+ Postdoc: ```PLAN
539
+ Title: Decision Point Attention (DPA) as “Selective Softmax for Agent Turns” — a minimal simulation experiment
540
+
541
+ Core hypothesis to validate
542
+ - In agent trajectories, only a small fraction of tokens (~5–15%) require softmax’s “spiky/monotone” selectivity (per Hedgehog + Deng et al. theory); routing only those tokens through softmax recovers most agentic reasoning quality at near-linear-attention cost.
543
+ - Learned/semantic routing should beat random sparsity (Routing Transformer lesson), and sharp gating is trainable if router optimization is decoupled (HAG+Gradient Routing) and sink-mitigated (GSA).
544
+
545
+ Experiment design (keep it simple, no training from scratch)
546
+ We will NOT build a full new LLM. We will run a controlled “attention masking” simulation on an existing small Transformer and compare:
547
+ (1) full softmax attention
548
+ (2) “linearized / cheap attention everywhere” proxy
549
+ (3) uniform hybrid (every Nth layer full attention, like Jamba/Kimi ratios)
550
+ (4) DPA: full attention only for labeled decision-point tokens; routine tokens use cheap attention
551
+
552
+ A. Model choices (use pretrained, minimal engineering)
553
+ - Base model: a small open Transformer with FlashAttention support, e.g. GPT-2 small (124M) or Qwen2.5-0.5B-Instruct.
554
+ - Implementation trick: we simulate “cheap linear attention” using a *restricted attention pattern* that removes global selectivity:
555
+ - Routine tokens: local sliding-window attention (e.g., window 128 or 256) + optionally 1–2 global “memory” tokens.
556
+ - Decision tokens: full causal attention over the entire prefix.
557
+ This is easy to implement with attention masks; avoids implementing GLA/DeltaNet kernels yet still tests the decision-point routing idea.
558
+ - Optional second phase (if time): swap local attention with an actual linear attention block (Hedgehog or Performer) for routine tokens; keep softmax for decision tokens.
559
+
560
+ B. Data: agent trajectories with explicit decision points
561
+ Search / use datasets with tool-call markup and errors:
562
+ - ToolQA / API-Bank / ToolBench / OpenAI function-calling style logs (any dataset where tool calls are explicit JSON/function tags).
563
+ - ReAct-style traces (HotpotQA-ReAct, ALFWorld trajectories, WebShop trajectories) where “Action:” / “Observation:” delimiters exist.
564
+ Labeling heuristic (supervised router labels):
565
+ - Decision-point tokens are those in spans matching:
566
+ 1) tool call boundary: tokens in “Action:” line + the function name/args JSON
567
+ 2) post-observation parse: first K tokens after “Observation:” (K=16) (often determines next action)
568
+ 3) explicit plan revision markers: “Plan:”, “I will now”, “New plan”, “Let’s revise”
569
+ 4) error strings: “Error”, “Exception”, “Invalid”, “Traceback”, HTTP codes
570
+ Everything else is routine.
571
+ Goal: empirically measure decision-point token fraction; expect <15% (report per dataset).
572
+
573
+ C. Tasks / evaluation (choose 1–2 agentic benchmarks)
574
+ Pick one tool-using and one multi-step reasoning benchmark:
575
+ - WebShop (success rate) OR ALFWorld (success rate) — agent must interleave action/observation.
576
+ - GSM8K with tool-style scratchpad wrapper (e.g., forced “Plan/Action/Observation” format via prompt) — measure exact match.
577
+ If tool environments are heavy, use an offline “next-action prediction” proxy:
578
+ - Train/evaluate on predicting the next “Action:” line given history (log-likelihood / exact match of tool name).
579
+ This keeps evaluation cheap and deterministic.
580
+
581
+ D. Methods compared (clear baselines)
582
+ All methods run on the same pretrained Transformer; only attention masks differ.
583
+
584
+ 1) Full Softmax (upper bound)
585
+ - Standard causal full attention for all tokens.
586
+
587
+ 2) Cheap Everywhere (proxy for linear attention failure)
588
+ - Local attention for all tokens (window W). This intentionally removes global selectivity; should degrade on long-horizon / tool choice.
589
+
590
+ 3) Uniform Hybrid (Jamba/Kimi-like)
591
+ - Every Nth layer uses full attention for all tokens; other layers local-only.
592
+ - Match compute budget to DPA by choosing N so that full-attn FLOPs roughly equal DPA’s decision-token budget.
593
+
594
+ 4) DPA (ours; oracle router first)
595
+ - In every layer (or last L layers), apply:
596
+ - Routine tokens attend locally (window W).
597
+ - Decision tokens attend globally (full causal).
598
+ - Additionally allow routine tokens to attend to decision tokens (cheap-to-expensive cross) via a “decision memory” set (keys/values from decision tokens always visible). This tests the key DPA idea:
599
+ ~~~~~~~~~~~
600
+ Subtask 'plan formulation' completed in 106.51 seconds.
601
+ **************************************************
602
+ Beginning phase: experimentation
603
+ **************************************************
604
+ &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
605
+ Beginning subtask: data preparation
606
+ &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
607
+
608
+ @@ Lab #1 Paper #0 @@
609
+ Current experiment cost = $1.8669725000000001, ** Approximate values, may not reflect true cost
610
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ data preparation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
611
+
612
+ BertModel LOAD REPORT from: sentence-transformers/all-MiniLM-L6-v2
613
+ Key | Status | |
614
+ ------------------------+------------+--+-
615
+ embeddings.position_ids | UNEXPECTED | |
616
+
617
+ Notes:
618
+ - UNEXPECTED :can be ignored when loading from different task/architecture; not ok if you expect identical arch.
619
+ /Users/bytedance/AgentLaboratory/utils.py:7: FutureWarning:
620
+
621
+ All support for the `google.generativeai` package has ended. It will no longer be receiving
622
+ updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
623
+ See README for more details:
624
+
625
+ https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
626
+
627
+ import google.generativeai as genai
628
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
629
+ CODE RESPONSE: [CODE EXECUTION ERROR]: Code execution exceeded the timeout limit of 60 seconds. You must reduce the time complexity of your code.
630
+ Current experiment cost = $1.8863275000000002, ** Approximate values, may not reflect true cost
631
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ data preparation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
632
+ @@ Lab #1 Paper #0 @@
633
+ Current experiment cost = $1.9258450000000003, ** Approximate values, may not reflect true cost
634
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ data preparation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
635
+
636
+ BertModel LOAD REPORT from: sentence-transformers/all-MiniLM-L6-v2
637
+ Key | Status | |
638
+ ------------------------+------------+--+-
639
+ embeddings.position_ids | UNEXPECTED | |
640
+
641
+ Notes:
642
+ - UNEXPECTED :can be ignored when loading from different task/architecture; not ok if you expect identical arch.
643
+ /Users/bytedance/AgentLaboratory/utils.py:7: FutureWarning:
644
+
645
+ All support for the `google.generativeai` package has ended. It will no longer be receiving
646
+ updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
647
+ See README for more details:
648
+
649
+ https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
650
+
651
+ import google.generativeai as genai
652
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
653
+ CODE RESPONSE: [CODE EXECUTION ERROR]: Code execution exceeded the timeout limit of 60 seconds. You must reduce the time complexity of your code.
654
+ Current experiment cost = $1.96041, ** Approximate values, may not reflect true cost
655
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ data preparation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
656
+
657
+ BertModel LOAD REPORT from: sentence-transformers/all-MiniLM-L6-v2
658
+ Key | Status | |
659
+ ------------------------+------------+--+-
660
+ embeddings.position_ids | UNEXPECTED | |
661
+
662
+ Notes:
663
+ - UNEXPECTED :can be ignored when loading from different task/architecture; not ok if you expect identical arch.
664
+ /Users/bytedance/AgentLaboratory/utils.py:7: FutureWarning:
665
+
666
+ All support for the `google.generativeai` package has ended. It will no longer be receiving
667
+ updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
668
+ See README for more details:
669
+
670
+ https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
671
+
672
+ import google.generativeai as genai
673
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
674
+ CODE RESPONSE:
675
+ @@ Lab #1 Paper #0 @@
676
+ Current experiment cost = $2.002695, ** Approximate values, may not reflect true cost
677
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ data preparation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
678
+
679
+ BertModel LOAD REPORT from: sentence-transformers/all-MiniLM-L6-v2
680
+ Key | Status | |
681
+ ------------------------+------------+--+-
682
+ embeddings.position_ids | UNEXPECTED | |
683
+
684
+ Notes:
685
+ - UNEXPECTED :can be ignored when loading from different task/architecture; not ok if you expect identical arch.
686
+ /Users/bytedance/AgentLaboratory/utils.py:7: FutureWarning:
687
+
688
+ All support for the `google.generativeai` package has ended. It will no longer be receiving
689
+ updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
690
+ See README for more details:
691
+
692
+ https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
693
+
694
+ import google.generativeai as genai
695
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
696
+ CODE RESPONSE:
697
+ Data successfully saved to ././decision_point_attention_research/research_dir_0_lab_1/src/load_data.py
698
+ Subtask 'data preparation' completed in 399.40 seconds.
699
+ &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
700
+ Beginning subtask: running experiments
701
+ &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
702
+ Current experiment cost = $2.0313875, ** Approximate values, may not reflect true cost
703
+
704
+ BertModel LOAD REPORT from: sentence-transformers/all-MiniLM-L6-v2
705
+ Key | Status | |
706
+ ------------------------+------------+--+-
707
+ embeddings.position_ids | UNEXPECTED | |
708
+
709
+ Notes:
710
+ - UNEXPECTED :can be ignored when loading from different task/architecture; not ok if you expect identical arch.
711
+ /Users/bytedance/AgentLaboratory/utils.py:7: FutureWarning:
712
+
713
+ All support for the `google.generativeai` package has ended. It will no longer be receiving
714
+ updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
715
+ See README for more details:
716
+
717
+ https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
718
+
719
+ import google.generativeai as genai
720
+ Current experiment cost = $2.0318575, ** Approximate values, may not reflect true cost
721
+ Current experiment cost = $2.03694, ** Approximate values, may not reflect true cost
722
+ * Attempting repair // try 0*
723
+
724
+ BertModel LOAD REPORT from: sentence-transformers/all-MiniLM-L6-v2
725
+ Key | Status | |
726
+ ------------------------+------------+--+-
727
+ embeddings.position_ids | UNEXPECTED | |
728
+
729
+ Notes:
730
+ - UNEXPECTED :can be ignored when loading from different task/architecture; not ok if you expect identical arch.
731
+ /Users/bytedance/AgentLaboratory/utils.py:7: FutureWarning:
732
+
733
+ All support for the `google.generativeai` package has ended. It will no longer be receiving
734
+ updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
735
+ See README for more details:
736
+
737
+ https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
738
+
739
+ import google.generativeai as genai
740
+ Current experiment cost = $2.0385825000000004, ** Approximate values, may not reflect true cost
741
+ Current experiment cost = $2.0433575000000004, ** Approximate values, may not reflect true cost
742
+ * Attempting repair // try 1*
743
+ $$$$ CODE REPLACE (failed)
744
+ @@@ INIT ATTEMPT: Command Exec // Attempt 0: Code replacement FAILED due to the following error: Return from executing code: | Return from executing code on real test set could not convert string to float: ''. Code was reverted back to original state before edits.
745
+ $$$ Score: None
746
+ Current experiment cost = $2.0747600000000004, ** Approximate values, may not reflect true cost
747
+
748
+ BertModel LOAD REPORT from: sentence-transformers/all-MiniLM-L6-v2
749
+ Key | Status | |
750
+ ------------------------+------------+--+-
751
+ embeddings.position_ids | UNEXPECTED | |
752
+
753
+ Notes:
754
+ - UNEXPECTED :can be ignored when loading from different task/architecture; not ok if you expect identical arch.
755
+ /Users/bytedance/AgentLaboratory/utils.py:7: FutureWarning:
756
+
757
+ All support for the `google.generativeai` package has ended. It will no longer be receiving
758
+ updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
759
+ See README for more details:
760
+
761
+ https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
762
+
763
+ import google.generativeai as genai
764
+ Current experiment cost = $2.0752300000000004, ** Approximate values, may not reflect true cost
765
+ Current experiment cost = $2.0795525, ** Approximate values, may not reflect true cost
766
+ * Attempting repair // try 0*
767
+
768
+ BertModel LOAD REPORT from: sentence-transformers/all-MiniLM-L6-v2
769
+ Key | Status | |
770
+ ------------------------+------------+--+-
771
+ embeddings.position_ids | UNEXPECTED | |
772
+
773
+ Notes:
774
+ - UNEXPECTED :can be ignored when loading from different task/architecture; not ok if you expect identical arch.
775
+ /Users/bytedance/AgentLaboratory/utils.py:7: FutureWarning:
776
+
777
+ All support for the `google.generativeai` package has ended. It will no longer be receiving
778
+ updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
779
+ See README for more details:
780
+
781
+ https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
782
+
783
+ import google.generativeai as genai
784
+ Current experiment cost = $2.081005, ** Approximate values, may not reflect true cost
785
+ Current experiment cost = $2.0876475, ** Approximate values, may not reflect true cost
786
+ * Attempting repair // try 1*
787
+ $$$$ CODE REPLACE (failed)
788
+ @@@ INIT ATTEMPT: Command Exec // Attempt 1: Code replacement FAILED due to the following error: Return from executing code: | Return from executing code on real test set could not convert string to float: ''. Code was reverted back to original state before edits.
789
+ $$$ Score: None
790
+ /Users/bytedance/miniconda3/lib/python3.13/multiprocessing/resource_tracker.py:400: UserWarning: resource_tracker: There appear to be 3 leaked semaphore objects to clean up at shutdown: {'/loky-94693-vraa0l4y', '/loky-89172-gf4b5lrk', '/loky-95284-4h3ut9sz'}
791
+ warnings.warn(
agentlab-pipeline/ai_lab_repo_patched.py ADDED
@@ -0,0 +1,891 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import PyPDF2
2
+ import threading
3
+ from app import *
4
+ from agents import *
5
+ from copy import copy
6
+ from pathlib import Path
7
+ from datetime import date
8
+ from common_imports import *
9
+ from mlesolver import MLESolver
10
+ import argparse, pickle, yaml
11
+
12
+ GLOBAL_AGENTRXIV = None
13
+ DEFAULT_LLM_BACKBONE = "llmbox/gpt-5.2"
14
+ RESEARCH_DIR_PATH = "decision_point_attention_research"
15
+
16
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
17
+
18
+
19
+ class LaboratoryWorkflow:
20
+ def __init__(self, research_topic, openai_api_key, max_steps=100, num_papers_lit_review=5, agent_model_backbone=f"{DEFAULT_LLM_BACKBONE}", notes=list(), human_in_loop_flag=None, compile_pdf=True, mlesolver_max_steps=3, papersolver_max_steps=5, paper_index=0, except_if_fail=False, parallelized=False, lab_dir=None, lab_index=0, agentRxiv=False, agentrxiv_papers=5):
21
+ """
22
+ Initialize laboratory workflow
23
+ @param research_topic: (str) description of research idea to explore
24
+ @param max_steps: (int) max number of steps for each phase, i.e. compute tolerance budget
25
+ @param num_papers_lit_review: (int) number of papers to include in the lit review
26
+ @param agent_model_backbone: (str or dict) model backbone to use for agents
27
+ @param notes: (list) notes for agent to follow during tasks
28
+ """
29
+ self.agentRxiv = agentRxiv
30
+ self.max_prev_papers = 10
31
+ self.parallelized = parallelized
32
+ self.notes = notes
33
+ self.lab_dir = lab_dir
34
+ self.lab_index = lab_index
35
+ self.max_steps = max_steps
36
+ self.compile_pdf = compile_pdf
37
+ self.paper_index = paper_index
38
+ self.openai_api_key = openai_api_key
39
+ self.except_if_fail = except_if_fail
40
+ self.research_topic = research_topic
41
+ self.model_backbone = agent_model_backbone
42
+ self.num_papers_lit_review = num_papers_lit_review
43
+
44
+ self.print_cost = True
45
+ self.review_override = True # should review be overridden?
46
+ self.review_ovrd_steps = 0 # review steps so far
47
+ self.arxiv_paper_exp_time = 3
48
+ self.reference_papers = list()
49
+
50
+ ##########################################
51
+ ####### COMPUTE BUDGET PARAMETERS ########
52
+ ##########################################
53
+ self.num_ref_papers = 1
54
+ self.review_total_steps = 0 # num steps to take if overridden
55
+ self.arxiv_num_summaries = 5
56
+ self.num_agentrxiv_papers = agentrxiv_papers
57
+ self.mlesolver_max_steps = mlesolver_max_steps
58
+ self.papersolver_max_steps = papersolver_max_steps
59
+
60
+ self.phases = [
61
+ ("literature review", ["literature review"]),
62
+ ("plan formulation", ["plan formulation"]),
63
+ ("experimentation", ["data preparation", "running experiments"]),
64
+ ("results interpretation", ["results interpretation", "report writing", "report refinement"]),
65
+ ]
66
+ self.phase_status = dict()
67
+ for phase, subtasks in self.phases:
68
+ for subtask in subtasks:
69
+ self.phase_status[subtask] = False
70
+
71
+ self.phase_models = dict()
72
+ if type(agent_model_backbone) == str:
73
+ for phase, subtasks in self.phases:
74
+ for subtask in subtasks:
75
+ self.phase_models[subtask] = agent_model_backbone
76
+ elif type(agent_model_backbone) == dict:
77
+ # todo: check if valid
78
+ self.phase_models = agent_model_backbone
79
+
80
+ self.human_in_loop_flag = human_in_loop_flag
81
+
82
+ self.statistics_per_phase = {
83
+ "literature review": {"time": 0.0, "steps": 0.0,},
84
+ "plan formulation": {"time": 0.0, "steps": 0.0,},
85
+ "data preparation": {"time": 0.0, "steps": 0.0,},
86
+ "running experiments": {"time": 0.0, "steps": 0.0,},
87
+ "results interpretation": {"time": 0.0, "steps": 0.0,},
88
+ "report writing": {"time": 0.0, "steps": 0.0,},
89
+ "report refinement": {"time": 0.0, "steps": 0.0,},
90
+ }
91
+
92
+ self.save = True
93
+ self.verbose = True
94
+ self.reviewers = ReviewersAgent(model=self.model_backbone, notes=self.notes, openai_api_key=self.openai_api_key)
95
+ self.phd = PhDStudentAgent(model=self.model_backbone, notes=self.notes, max_steps=self.max_steps, openai_api_key=self.openai_api_key)
96
+ self.postdoc = PostdocAgent(model=self.model_backbone, notes=self.notes, max_steps=self.max_steps, openai_api_key=self.openai_api_key)
97
+ self.professor = ProfessorAgent(model=self.model_backbone, notes=self.notes, max_steps=self.max_steps, openai_api_key=self.openai_api_key)
98
+ self.ml_engineer = MLEngineerAgent(model=self.model_backbone, notes=self.notes, max_steps=self.max_steps, openai_api_key=self.openai_api_key)
99
+ self.sw_engineer = SWEngineerAgent(model=self.model_backbone, notes=self.notes, max_steps=self.max_steps, openai_api_key=self.openai_api_key)
100
+
101
+
102
+ def set_model(self, model):
103
+ self.set_agent_attr("model", model)
104
+ self.reviewers.model = model
105
+
106
+ def save_state(self, phase):
107
+ """
108
+ Save state for phase
109
+ @param phase: (str) phase string
110
+ @return: None
111
+ """
112
+ with open(f"state_saves/Paper{self.paper_index}.pkl", "wb") as f:
113
+ pickle.dump(self, f)
114
+
115
+ def set_agent_attr(self, attr, obj):
116
+ """
117
+ Set attribute for all agents
118
+ @param attr: (str) agent attribute
119
+ @param obj: (object) object attribute
120
+ @return: None
121
+ """
122
+ setattr(self.phd, attr, obj)
123
+ setattr(self.postdoc, attr, obj)
124
+ setattr(self.professor, attr, obj)
125
+ setattr(self.ml_engineer, attr, obj)
126
+ setattr(self.sw_engineer, attr, obj)
127
+
128
+ def reset_agents(self):
129
+ """
130
+ Reset all agent states
131
+ @return: None
132
+ """
133
+ self.phd.reset()
134
+ self.postdoc.reset()
135
+ self.professor.reset()
136
+ self.ml_engineer.reset()
137
+ self.sw_engineer.reset()
138
+
139
+ def perform_research(self):
140
+ """
141
+ Loop through all research phases
142
+ @return: None
143
+ """
144
+ for phase, subtasks in self.phases:
145
+ phase_start_time = time.time() # Start timing the phase
146
+ if self.verbose: print(f"{'*'*50}\nBeginning phase: {phase}\n{'*'*50}")
147
+ for subtask in subtasks:
148
+ if self.agentRxiv:
149
+ if self.verbose: print(f"{'&' * 30}\n[Lab #{self.lab_index} Paper #{self.paper_index}] Beginning subtask: {subtask}\n{'&' * 30}")
150
+ else:
151
+ if self.verbose: print(f"{'&'*30}\nBeginning subtask: {subtask}\n{'&'*30}")
152
+ if type(self.phase_models) == dict:
153
+ if subtask in self.phase_models:
154
+ self.set_model(self.phase_models[subtask])
155
+ else: self.set_model(f"{DEFAULT_LLM_BACKBONE}")
156
+ if (subtask not in self.phase_status or not self.phase_status[subtask]) and subtask == "literature review":
157
+ repeat = True
158
+ while repeat: repeat = self.literature_review()
159
+ self.phase_status[subtask] = True
160
+ if (subtask not in self.phase_status or not self.phase_status[subtask]) and subtask == "plan formulation":
161
+ repeat = True
162
+ while repeat: repeat = self.plan_formulation()
163
+ self.phase_status[subtask] = True
164
+ if (subtask not in self.phase_status or not self.phase_status[subtask]) and subtask == "data preparation":
165
+ repeat = True
166
+ while repeat: repeat = self.data_preparation()
167
+ self.phase_status[subtask] = True
168
+ if (subtask not in self.phase_status or not self.phase_status[subtask]) and subtask == "running experiments":
169
+ repeat = True
170
+ while repeat: repeat = self.running_experiments()
171
+ self.phase_status[subtask] = True
172
+ if (subtask not in self.phase_status or not self.phase_status[subtask]) and subtask == "results interpretation":
173
+ repeat = True
174
+ while repeat: repeat = self.results_interpretation()
175
+ self.phase_status[subtask] = True
176
+ if (subtask not in self.phase_status or not self.phase_status[subtask]) and subtask == "report writing":
177
+ repeat = True
178
+ while repeat: repeat = self.report_writing()
179
+ self.phase_status[subtask] = True
180
+ if (subtask not in self.phase_status or not self.phase_status[subtask]) and subtask == "report refinement":
181
+ return_to_exp_phase = self.report_refinement()
182
+
183
+ if not return_to_exp_phase:
184
+ if self.save: self.save_state(subtask)
185
+ return
186
+
187
+ self.set_agent_attr("second_round", return_to_exp_phase)
188
+ self.set_agent_attr("prev_report", copy(self.phd.report))
189
+ self.set_agent_attr("prev_exp_results", copy(self.phd.exp_results))
190
+ self.set_agent_attr("prev_results_code", copy(self.phd.results_code))
191
+ self.set_agent_attr("prev_interpretation", copy(self.phd.interpretation))
192
+
193
+ self.phase_status["plan formulation"] = False
194
+ self.phase_status["data preparation"] = False
195
+ self.phase_status["running experiments"] = False
196
+ self.phase_status["results interpretation"] = False
197
+ self.phase_status["report writing"] = False
198
+ self.phase_status["report refinement"] = False
199
+ self.perform_research()
200
+ if self.save: self.save_state(subtask)
201
+ # Calculate and print the duration of the phase
202
+ phase_end_time = time.time()
203
+ phase_duration = phase_end_time - phase_start_time
204
+ print(f"Subtask '{subtask}' completed in {phase_duration:.2f} seconds.")
205
+ self.statistics_per_phase[subtask]["time"] = phase_duration
206
+
207
+ def report_refinement(self):
208
+ """
209
+ Perform report refinement phase
210
+ @return: (bool) whether to repeat the phase
211
+ """
212
+ reviews = self.reviewers.inference(self.phd.plan, self.phd.report)
213
+ print("Reviews:", reviews)
214
+ if self.human_in_loop_flag["report refinement"]:
215
+ print(f"Provided are reviews from a set of three reviewers: {reviews}")
216
+ input("Would you like to be completed with the project or should the agents go back and improve their experimental results?\n (y) for go back (n) for complete project: ")
217
+ else:
218
+ review_prompt = f"Provided are reviews from a set of three reviewers: {reviews}. Would you like to be completed with the project or do you want to go back to the planning phase and improve your experiments?\n Type y and nothing else to go back, type n and nothing else for complete project."
219
+ self.phd.phases.append("report refinement")
220
+ if self.review_override:
221
+ if self.review_total_steps == self.review_ovrd_steps:
222
+ response = "n"
223
+ else:
224
+ response = "y"
225
+ self.review_ovrd_steps += 1
226
+ else:
227
+ response = self.phd.inference(
228
+ research_topic=self.research_topic, phase="report refinement", feedback=review_prompt, step=0)
229
+ if len(response) == 0:
230
+ raise Exception("Model did not respond")
231
+ response = response.lower().strip()[0]
232
+ if response == "n":
233
+ if self.verbose: print("*"*40, "\n", "REVIEW COMPLETE", "\n", "*"*40)
234
+ return False
235
+ elif response == "y":
236
+ self.set_agent_attr("reviewer_response", f"Provided are reviews from a set of three reviewers: {reviews}.")
237
+ return True
238
+ else: raise Exception("Model did not respond")
239
+
240
+ def report_writing(self):
241
+ """
242
+ Perform report writing phase
243
+ @return: (bool) whether to repeat the phase
244
+ """
245
+ # experiment notes
246
+ report_notes = [_note["note"] for _note in self.ml_engineer.notes if "report writing" in _note["phases"]]
247
+ report_notes = f"Notes for the task objective: {report_notes}\n" if len(report_notes) > 0 else ""
248
+ # instantiate mle-solver
249
+ from papersolver import PaperSolver
250
+ self.reference_papers = []
251
+ solver = PaperSolver(notes=report_notes, max_steps=self.papersolver_max_steps, plan=self.phd.plan, exp_code=self.phd.results_code, exp_results=self.phd.exp_results, insights=self.phd.interpretation, lit_review=self.phd.lit_review, ref_papers=self.reference_papers, topic=research_topic, openai_api_key=self.openai_api_key, llm_str=self.model_backbone["report writing"], compile_pdf=compile_pdf, save_loc=self.lab_dir)
252
+ # run initialization for solver
253
+ solver.initial_solve()
254
+ # run solver for N mle optimization steps
255
+ for _ in range(self.papersolver_max_steps): solver.solve()
256
+ # get best report results
257
+ report = "\n".join(solver.best_report[0][0])
258
+ score = solver.best_report[0][1]
259
+ match = re.search(r'\\title\{([^}]*)\}', report)
260
+ if match: report_title = match.group(1).replace(" ", "_")
261
+ else: report_title = "\n".join([str(random.randint(0, 10)) for _ in range(10)])
262
+ if self.agentRxiv: shutil.copyfile(self.lab_dir + "/tex/temp.pdf", f"uploads/{report_title}.pdf")
263
+ if self.verbose: print(f"Report writing completed, reward function score: {score}")
264
+ if self.human_in_loop_flag["report writing"]:
265
+ retry = self.human_in_loop("report writing", report)
266
+ if retry: return retry
267
+ self.set_agent_attr("report", report)
268
+ readme = self.professor.generate_readme()
269
+ save_to_file(f"./{self.lab_dir}", "readme.md", readme)
270
+ save_to_file(f"./{self.lab_dir}", "report.txt", report)
271
+ self.reset_agents()
272
+ return False
273
+
274
+ def results_interpretation(self):
275
+ """
276
+ Perform results interpretation phase
277
+ @return: (bool) whether to repeat the phase
278
+ """
279
+ max_tries = self.max_steps
280
+ dialogue = str()
281
+ # iterate until max num tries to complete task is exhausted
282
+ for _i in range(max_tries):
283
+ print(f"@@ Lab #{self.lab_index} Paper #{self.paper_index} @@")
284
+ resp = self.postdoc.inference(self.research_topic, "results interpretation", feedback=dialogue, step=_i)
285
+ if self.verbose: print("Postdoc: ", resp, "\n~~~~~~~~~~~")
286
+ dialogue = str()
287
+ if "```DIALOGUE" in resp:
288
+ dialogue = extract_prompt(resp, "DIALOGUE")
289
+ dialogue = f"The following is dialogue produced by the postdoctoral researcher: {dialogue}"
290
+ if self.verbose: print("#"*40, "\n", "Postdoc Dialogue:", dialogue, "\n", "#"*40)
291
+ if "```INTERPRETATION" in resp:
292
+ interpretation = extract_prompt(resp, "INTERPRETATION")
293
+ if self.human_in_loop_flag["results interpretation"]:
294
+ retry = self.human_in_loop("results interpretation", interpretation)
295
+ if retry: return retry
296
+ self.set_agent_attr("interpretation", interpretation)
297
+ # reset agent state
298
+ self.reset_agents()
299
+ self.statistics_per_phase["results interpretation"]["steps"] = _i
300
+ return False
301
+ resp = self.phd.inference(self.research_topic, "results interpretation", feedback=dialogue, step=_i)
302
+ if self.verbose: print("PhD Student: ", resp, "\n~~~~~~~~~~~")
303
+ dialogue = str()
304
+ if "```DIALOGUE" in resp:
305
+ dialogue = extract_prompt(resp, "DIALOGUE")
306
+ dialogue = f"The following is dialogue produced by the PhD student: {dialogue}"
307
+ if self.verbose: print("#"*40, "\n", "PhD Dialogue:", dialogue, "#"*40, "\n")
308
+ raise Exception("Max tries during phase: Results Interpretation")
309
+
310
+ def running_experiments(self):
311
+ """
312
+ Perform running experiments phase
313
+ @return: (bool) whether to repeat the phase
314
+ """
315
+ # experiment notes
316
+ experiment_notes = [_note["note"] for _note in self.ml_engineer.notes if "running experiments" in _note["phases"]]
317
+ experiment_notes = f"Notes for the task objective: {experiment_notes}\n" if len(experiment_notes) > 0 else ""
318
+ # instantiate mle-solver
319
+ solver = MLESolver(dataset_code=self.ml_engineer.dataset_code, notes=experiment_notes, insights=self.ml_engineer.lit_review_sum, max_steps=self.mlesolver_max_steps, plan=self.ml_engineer.plan, openai_api_key=self.openai_api_key, llm_str=self.model_backbone["running experiments"])
320
+ # run initialization for solver
321
+ solver.initial_solve()
322
+ # run solver for N mle optimization steps
323
+ for _ in range(self.mlesolver_max_steps-1):
324
+ solver.solve()
325
+ # get best code results
326
+ code = "\n".join(solver.best_codes[0][0])
327
+ # regenerate figures from top code
328
+ #execute_code(code)
329
+ score = solver.best_codes[0][1]
330
+ exp_results = solver.best_codes[0][2]
331
+ if self.verbose: print(f"Running experiments completed, reward function score: {score}")
332
+ if self.human_in_loop_flag["running experiments"]:
333
+ retry = self.human_in_loop("data preparation", code)
334
+ if retry: return retry
335
+ save_to_file(f"./{self.lab_dir}/src", "run_experiments.py", code)
336
+ save_to_file(f"./{self.lab_dir}/src", "experiment_output.log", exp_results)
337
+ self.set_agent_attr("results_code", code)
338
+ self.set_agent_attr("exp_results", exp_results)
339
+ # reset agent state
340
+ self.reset_agents()
341
+ return False
342
+
343
+ def data_preparation(self):
344
+ """
345
+ Perform data preparation phase
346
+ @return: (bool) whether to repeat the phase
347
+ """
348
+ max_tries = self.max_steps
349
+ ml_feedback = str()
350
+ ml_dialogue = str()
351
+ swe_feedback = str()
352
+ ml_command = str()
353
+ hf_engine = HFDataSearch()
354
+ # iterate until max num tries to complete task is exhausted
355
+ for _i in range(max_tries):
356
+ print(f"@@ Lab #{self.lab_index} Paper #{self.paper_index} @@")
357
+ if ml_feedback != "":
358
+ ml_feedback_in = "Feedback provided to the ML agent: " + ml_feedback
359
+ else: ml_feedback_in = ""
360
+ resp = self.sw_engineer.inference(self.research_topic, "data preparation", feedback=f"{ml_dialogue}\nFeedback from previous command: {swe_feedback}\n{ml_command}{ml_feedback_in}", step=_i)
361
+ swe_feedback = str()
362
+ swe_dialogue = str()
363
+ if "```DIALOGUE" in resp:
364
+ dialogue = extract_prompt(resp, "DIALOGUE")
365
+ swe_dialogue = f"\nThe following is dialogue produced by the SW Engineer: {dialogue}\n"
366
+ if self.verbose: print("#"*40, f"\nThe following is dialogue produced by the SW Engineer: {dialogue}", "\n", "#"*40)
367
+ if "```SUBMIT_CODE" in resp:
368
+ final_code = extract_prompt(resp, "SUBMIT_CODE")
369
+ code_resp = execute_code(final_code, timeout=60)
370
+ if self.verbose: print("!"*100, "\n", f"CODE RESPONSE: {code_resp}")
371
+ swe_feedback += f"\nCode Response: {code_resp}\n"
372
+ if "[CODE EXECUTION ERROR]" in code_resp:
373
+ swe_feedback += "\nERROR: Final code had an error and could not be submitted! You must address and fix this error.\n"
374
+ else:
375
+ if self.human_in_loop_flag["data preparation"]:
376
+ retry = self.human_in_loop("data preparation", final_code)
377
+ if retry: return retry
378
+ save_to_file(f"./{self.lab_dir}/src", "load_data.py", final_code)
379
+ self.set_agent_attr("dataset_code", final_code)
380
+ # reset agent state
381
+ self.reset_agents()
382
+ self.statistics_per_phase["data preparation"]["steps"] = _i
383
+ return False
384
+
385
+ if ml_feedback != "":
386
+ ml_feedback_in = "Feedback from previous command: " + ml_feedback
387
+ else:
388
+ ml_feedback_in = ""
389
+ resp = self.ml_engineer.inference(
390
+ self.research_topic, "data preparation",
391
+ feedback=f"{swe_dialogue}\n{ml_feedback_in}", step=_i)
392
+ #if self.verbose: print("ML Engineer: ", resp, "\n~~~~~~~~~~~")
393
+ ml_feedback = str()
394
+ ml_dialogue = str()
395
+ ml_command = str()
396
+ if "```DIALOGUE" in resp:
397
+ dialogue = extract_prompt(resp, "DIALOGUE")
398
+ ml_dialogue = f"\nThe following is dialogue produced by the ML Engineer: {dialogue}\n"
399
+ if self.verbose: print("#" * 40, f"\nThe following is dialogue produced by the ML Engineer: {dialogue}", "#" * 40, "\n")
400
+ if "```python" in resp:
401
+ code = extract_prompt(resp, "python")
402
+ code = self.ml_engineer.dataset_code + "\n" + code
403
+ code_resp = execute_code(code, timeout=120)
404
+ ml_command = f"Code produced by the ML agent:\n{code}"
405
+ ml_feedback += f"\nCode Response: {code_resp}\n"
406
+ if self.verbose: print("!"*100, "\n", f"CODE RESPONSE: {code_resp}")
407
+ if "```SEARCH_HF" in resp:
408
+ hf_query = extract_prompt(resp, "SEARCH_HF")
409
+ hf_res = "\n".join(hf_engine.results_str(hf_engine.retrieve_ds(hf_query)))
410
+ ml_command = f"HF search command produced by the ML agent:\n{hf_query}"
411
+ ml_feedback += f"Huggingface results: {hf_res}\n"
412
+ raise Exception("Max tries during phase: Data Preparation")
413
+
414
+ def plan_formulation(self):
415
+ """
416
+ Perform plan formulation phase
417
+ @return: (bool) whether to repeat the phase
418
+ """
419
+ max_tries = self.max_steps
420
+ dialogue = str()
421
+ # iterate until max num tries to complete task is exhausted
422
+ for _i in range(max_tries):
423
+ print(f"@@ Lab #{self.lab_index} Paper #{self.paper_index} @@")
424
+ # inference postdoc to
425
+ resp = self.postdoc.inference(self.research_topic, "plan formulation", feedback=dialogue, step=_i)
426
+ if self.verbose: print("Postdoc: ", resp, "\n~~~~~~~~~~~")
427
+ dialogue = str()
428
+
429
+ if "```DIALOGUE" in resp:
430
+ dialogue = extract_prompt(resp, "DIALOGUE")
431
+ dialogue = f"The following is dialogue produced by the postdoctoral researcher: {dialogue}"
432
+ if self.verbose: print("#"*40, "\n", "Postdoc Dialogue:", dialogue, "\n", "#"*40)
433
+
434
+ if "```PLAN" in resp:
435
+ plan = extract_prompt(resp, "PLAN")
436
+ if self.human_in_loop_flag["plan formulation"]:
437
+ retry = self.human_in_loop("plan formulation", plan)
438
+ if retry: return retry
439
+ self.set_agent_attr("plan", plan)
440
+ # reset agent state
441
+ self.reset_agents()
442
+ self.statistics_per_phase["plan formulation"]["steps"] = _i
443
+ return False
444
+
445
+ resp = self.phd.inference(self.research_topic, "plan formulation", feedback=dialogue, step=_i)
446
+ if self.verbose: print("PhD Student: ", resp, "\n~~~~~~~~~~~")
447
+
448
+ dialogue = str()
449
+ if "```DIALOGUE" in resp:
450
+ dialogue = extract_prompt(resp, "DIALOGUE")
451
+ dialogue = f"The following is dialogue produced by the PhD student: {dialogue}"
452
+ if self.verbose: print("#"*40, "\n", "PhD Dialogue:", dialogue, "#"*40, "\n")
453
+ if self.except_if_fail:
454
+ raise Exception("Max tries during phase: Plan Formulation")
455
+ else:
456
+ plan = "No plan specified."
457
+ if self.human_in_loop_flag["plan formulation"]:
458
+ retry = self.human_in_loop("plan formulation", plan)
459
+ if retry: return retry
460
+ self.set_agent_attr("plan", plan)
461
+ # reset agent state
462
+ self.reset_agents()
463
+ return False
464
+
465
+ def literature_review(self):
466
+ """
467
+ Perform literature review phase
468
+ @return: (bool) whether to repeat the phase
469
+ """
470
+ arx_eng = ArxivSearch()
471
+ max_tries = self.max_steps # lit review often requires extra steps
472
+ # get initial response from PhD agent
473
+ resp = self.phd.inference(self.research_topic, "literature review", step=0, temp=0.4)
474
+ if self.verbose: print(resp, "\n~~~~~~~~~~~")
475
+ # iterate until max num tries to complete task is exhausted
476
+ for _i in range(max_tries):
477
+ print(f"@@ Lab #{self.lab_index} Paper #{self.paper_index} @@")
478
+ feedback = str()
479
+ # grab summary of papers from arxiv
480
+ if "```SUMMARY" in resp:
481
+ query = extract_prompt(resp, "SUMMARY")
482
+ papers = arx_eng.find_papers_by_str(query, N=self.arxiv_num_summaries)
483
+ if self.agentRxiv:
484
+ if GLOBAL_AGENTRXIV.num_papers() > 0:
485
+ papers += GLOBAL_AGENTRXIV.search_agentrxiv(query, self.num_agentrxiv_papers,)
486
+ feedback = f"You requested arXiv papers related to the query {query}, here was the response\n{papers}"
487
+
488
+ # grab full text from arxiv ID
489
+ elif "```FULL_TEXT" in resp:
490
+ query = extract_prompt(resp, "FULL_TEXT")
491
+ if self.agentRxiv and "AgentRxiv" in query: full_text = GLOBAL_AGENTRXIV.retrieve_full_text(query,)
492
+ else: full_text = arx_eng.retrieve_full_paper_text(query)
493
+ # expiration timer so that paper does not remain in context too long
494
+ arxiv_paper = f"```EXPIRATION {self.arxiv_paper_exp_time}\n" + full_text + "```"
495
+ feedback = arxiv_paper
496
+
497
+ # if add paper, extract and add to lit review, provide feedback
498
+ elif "```ADD_PAPER" in resp:
499
+ query = extract_prompt(resp, "ADD_PAPER")
500
+ if self.agentRxiv and "AgentRxiv" in query: feedback, text = self.phd.add_review(query, arx_eng, agentrxiv=True, GLOBAL_AGENTRXIV=GLOBAL_AGENTRXIV)
501
+ else: feedback, text = self.phd.add_review(query, arx_eng)
502
+ if len(self.reference_papers) < self.num_ref_papers:
503
+ self.reference_papers.append(text)
504
+
505
+ # completion condition
506
+ if len(self.phd.lit_review) >= self.num_papers_lit_review:
507
+ # generate formal review
508
+ lit_review_sum = self.phd.format_review()
509
+ # if human in loop -> check if human is happy with the produced review
510
+ if self.human_in_loop_flag["literature review"]:
511
+ retry = self.human_in_loop("literature review", lit_review_sum)
512
+ # if not happy, repeat the process with human feedback
513
+ if retry:
514
+ self.phd.lit_review = []
515
+ return retry
516
+ # otherwise, return lit review and move on to next stage
517
+ if self.verbose: print(self.phd.lit_review_sum)
518
+ # set agent
519
+ self.set_agent_attr("lit_review_sum", lit_review_sum)
520
+ # reset agent state
521
+ self.reset_agents()
522
+ self.statistics_per_phase["literature review"]["steps"] = _i
523
+ return False
524
+ resp = self.phd.inference(self.research_topic, "literature review", feedback=feedback, step=_i + 1, temp=0.4)
525
+ if self.verbose: print(resp, "\n~~~~~~~~~~~")
526
+ if self.except_if_fail: raise Exception("Max tries during phase: Literature Review")
527
+ else:
528
+ if len(self.phd.lit_review) >= self.num_papers_lit_review:
529
+ # generate formal review
530
+ lit_review_sum = self.phd.format_review()
531
+ # if human in loop -> check if human is happy with the produced review
532
+ if self.human_in_loop_flag["literature review"]:
533
+ retry = self.human_in_loop("literature review", lit_review_sum)
534
+ # if not happy, repeat the process with human feedback
535
+ if retry:
536
+ self.phd.lit_review = []
537
+ return retry
538
+ # otherwise, return lit review and move on to next stage
539
+ if self.verbose: print(self.phd.lit_review_sum)
540
+ # set agent
541
+ self.set_agent_attr("lit_review_sum", lit_review_sum)
542
+ # reset agent state
543
+ self.reset_agents()
544
+ self.statistics_per_phase["literature review"]["steps"] = _i
545
+ return False
546
+
547
+ def human_in_loop(self, phase, phase_prod):
548
+ """
549
+ Get human feedback for phase output
550
+ @param phase: (str) current phase
551
+ @param phase_prod: (str) current phase result
552
+ @return: (bool) whether to repeat the loop
553
+ """
554
+ print("\n\n\n\n\n")
555
+ print(f"Presented is the result of the phase [{phase}]: {phase_prod}")
556
+ y_or_no = None
557
+ # repeat until a valid answer is provided
558
+ while y_or_no not in ["y", "n"]:
559
+ y_or_no = input("\n\n\nAre you happy with the presented content? Respond Y or N: ").strip().lower()
560
+ # if person is happy with feedback, move on to next stage
561
+ if y_or_no == "y": pass
562
+ # if not ask for feedback and repeat
563
+ elif y_or_no == "n":
564
+ # ask the human for feedback
565
+ notes_for_agent = input("Please provide notes for the agent so that they can try again and improve performance: ")
566
+ # reset agent state
567
+ self.reset_agents()
568
+ # add suggestions to the notes
569
+ self.notes.append({
570
+ "phases": [phase],
571
+ "note": notes_for_agent})
572
+ return True
573
+ else: print("Invalid response, type Y or N")
574
+ return False
575
+
576
+ class AgentRxiv:
577
+ def __init__(self, lab_index=0):
578
+ self.lab_index = lab_index
579
+ self.server_thread = None
580
+ self.initialize_server()
581
+ self.pdf_text = dict()
582
+ self.summaries = dict()
583
+
584
+ def initialize_server(self):
585
+ # Calculate the port dynamically
586
+ port = 5000 + self.lab_index
587
+ # Start the server on the computed port using a lambda to pass the port value
588
+ self.server_thread = threading.Thread(target=lambda: self.run_server(port))
589
+ self.server_thread.daemon = True
590
+ self.server_thread.start()
591
+ time.sleep(5) # allow time for the server to start up
592
+
593
+ @staticmethod
594
+ def num_papers():
595
+ return len(os.listdir("uploads"))
596
+
597
+ def retrieve_full_text(self, arxiv_id):
598
+ try:
599
+ return self.pdf_text[arxiv_id]
600
+ except Exception:
601
+ return "Paper ID not found?"
602
+
603
+ @staticmethod
604
+ def read_pdf_pypdf2(pdf_path):
605
+ with open(pdf_path, 'rb') as pdf_file:
606
+ reader = PyPDF2.PdfReader(pdf_file)
607
+ text = ''
608
+ for page_num in range(len(reader.pages)):
609
+ page = reader.pages[page_num]
610
+ text += page.extract_text()
611
+ return text
612
+
613
+ def search_agentrxiv(self, search_query, num_papers):
614
+ # Use the dynamic port here as well
615
+ url = f'http://127.0.0.1:{5000 + self.lab_index}/api/search?q={search_query}'
616
+ return_str = str()
617
+ try:
618
+ with app.app_context():
619
+ update_papers_from_uploads()
620
+ response = requests.get(url)
621
+ response.raise_for_status()
622
+ data = response.json()
623
+ return_str += "Search Query:" + data['query']
624
+ return_str += "Results:"
625
+ for result in data['results'][:num_papers]:
626
+ arxiv_id = f"AgentRxiv:ID_{result['id']}"
627
+ if arxiv_id not in self.summaries:
628
+ filename = Path(f'_tmp_{self.lab_index}.pdf')
629
+ response = requests.get(result['pdf_url'])
630
+ filename.write_bytes(response.content)
631
+ self.pdf_text[arxiv_id] = self.read_pdf_pypdf2(f'_tmp_{self.lab_index}.pdf')
632
+ self.summaries[arxiv_id] = query_model(
633
+ prompt=self.pdf_text[arxiv_id],
634
+ system_prompt="Please provide a 5 sentence summary of this paper.",
635
+ openai_api_key=os.getenv('OPENAI_API_KEY'),
636
+ model_str="gpt-4o-mini"
637
+ )
638
+ return_str += f"Title: {result['filename']}"
639
+ return_str += f"Summary: {self.summaries[arxiv_id]}\n"
640
+ formatted_date = date.today().strftime("%d/%m/%Y")
641
+ return_str += f"Publication Date: {formatted_date}\n"
642
+ return_str += f"arXiv paper ID: AgentRxiv:ID_{result['id']}"
643
+ return_str += "-" * 40
644
+ except Exception as e:
645
+ print(f"AgentRxiv Error: {e}")
646
+ return_str += f"Error: {e}"
647
+ return return_str
648
+
649
+ def run_server(self, port):
650
+ run_app(port=port)
651
+
652
+
653
+ def parse_arguments():
654
+ parser = argparse.ArgumentParser(description="AgentLaboratory Research Workflow")
655
+
656
+ parser.add_argument(
657
+ '--yaml-location',
658
+ type=str,
659
+ default="experiment_configs/MATH_agentlab.yaml",
660
+ help='Location of YAML to load config data.'
661
+ )
662
+
663
+ return parser.parse_args()
664
+
665
+
666
+ def parse_yaml(yaml_file_loc):
667
+ with open(yaml_file_loc, 'r') as file: agentlab_data = yaml.safe_load(file)
668
+ class YamlDataHolder:
669
+ def __init__(self): pass
670
+ parser = YamlDataHolder()
671
+ if "copilot_mode" in agentlab_data: parser.copilot_mode = agentlab_data["copilot_mode"]
672
+ else: parser.copilot_mode = False
673
+ if 'load-previous' in agentlab_data: parser.load_previous = agentlab_data["load-previous"]
674
+ else: parser.load_previous = False
675
+ if 'research-topic' in agentlab_data: parser.research_topic = agentlab_data["research-topic"]
676
+ if 'api-key' in agentlab_data: parser.api_key = agentlab_data["api-key"]
677
+ if 'deepseek-api-key' in agentlab_data: parser.deepseek_api_key = agentlab_data["deepseek-api-key"]
678
+ if 'compile-latex' in agentlab_data: parser.compile_latex = agentlab_data["compile-latex"]
679
+ else: parser.compile_latex = True
680
+ if 'llm-backend' in agentlab_data: parser.llm_backend = agentlab_data["llm-backend"]
681
+ else: parser.llm_backend = "o3-mini"
682
+ if 'lit-review-backend' in agentlab_data: parser.lit_review_backend = agentlab_data["lit-review-backend"]
683
+ else: parser.lit_review_backend = "gpt-4o-mini"
684
+ if 'language' in agentlab_data: parser.language = agentlab_data["language"]
685
+ else: parser.language = "English"
686
+ if 'num-papers-lit-review' in agentlab_data: parser.num_papers_lit_review = agentlab_data["num-papers-lit-review"]
687
+ else: parser.num_papers_lit_review = 5
688
+ if 'mlesolver-max-steps' in agentlab_data: parser.mlesolver_max_steps = agentlab_data["mlesolver-max-steps"]
689
+ else: parser.mlesolver_max_steps = 3
690
+ if 'papersolver-max-steps' in agentlab_data: parser.papersolver_max_steps = agentlab_data["papersolver-max-steps"]
691
+ else: parser.papersolver_max_steps = 5
692
+ if 'task-notes' in agentlab_data: parser.task_notes = agentlab_data["task-notes"]
693
+ else: parser.task_notes = []
694
+ if 'num-papers-to-write' in agentlab_data: parser.num_papers_to_write = agentlab_data["num-papers-to-write"]
695
+ else: parser.num_papers_to_write = 100
696
+ if 'parallel-labs' in agentlab_data: parser.parallel_labs = agentlab_data["parallel-labs"]
697
+ else: parser.parallel_labs = False
698
+ if 'num-parallel-labs' in agentlab_data: parser.num_parallel_labs = agentlab_data["num-parallel-labs"]
699
+ else: parser.num_parallel_labs = 8
700
+ if 'except-if-fail' in agentlab_data: parser.except_if_fail = agentlab_data["except-if-fail"]
701
+ else: parser.except_if_fail = False
702
+ if 'agentRxiv' in agentlab_data: parser.agentRxiv = agentlab_data["agentRxiv"]
703
+ else: parser.agentRxiv = False
704
+ if 'construct-agentRxiv' in agentlab_data: parser.construct_agentRxiv = agentlab_data["construct-agentRxiv"]
705
+ else: parser.construct_agentRxiv = False
706
+ if 'agentrxiv-papers' in agentlab_data: parser.agentrxiv_papers = agentlab_data["agentrxiv-papers"]
707
+ else: parser.agentrxiv_papers = 5
708
+
709
+ if 'lab-index' in agentlab_data: parser.lab_index = agentlab_data["lab-index"]
710
+ else: parser.lab_index = 0
711
+ return parser
712
+
713
+
714
+ if __name__ == "__main__":
715
+ user_args = parse_arguments()
716
+ yaml_to_use = user_args.yaml_location
717
+ args = parse_yaml(yaml_to_use)
718
+
719
+ llm_backend = args.llm_backend
720
+ human_mode = args.copilot_mode.lower() == "true" if type(args.copilot_mode) == str else args.copilot_mode
721
+ compile_pdf = args.compile_latex.lower() == "true" if type(args.compile_latex) == str else args.compile_latex
722
+ load_previous = args.load_previous.lower() == "true" if type(args.load_previous) == str else args.load_previous
723
+ parallel_labs = args.parallel_labs.lower() == "true" if type(args.parallel_labs) == str else args.parallel_labs
724
+ except_if_fail = args.except_if_fail.lower() == "true" if type(args.except_if_fail) == str else args.except_if_fail
725
+ agentRxiv = args.agentRxiv.lower() == "true" if type(args.agentRxiv) == str else args.agentRxiv
726
+ construct_agentRxiv = args.construct_agentRxiv.lower() == "true" if type(args.construct_agentRxiv) == str else args.construct_agentRxiv
727
+ lab_index = int(args.lab_index) if type(args.construct_agentRxiv) == str else args.lab_index
728
+
729
+ try: num_papers_to_write = int(args.num_papers_to_write.lower()) if type(args.num_papers_to_write) == str else args.num_papers_to_write
730
+ except Exception: raise Exception("args.num_papers_lit_review must be a valid integer!")
731
+ try: num_papers_lit_review = int(args.num_papers_lit_review.lower()) if type(args.num_papers_lit_review) == str else args.num_papers_lit_review
732
+ except Exception: raise Exception("args.num_papers_lit_review must be a valid integer!")
733
+ try: papersolver_max_steps = int(args.papersolver_max_steps.lower()) if type(args.papersolver_max_steps) == str else args.papersolver_max_steps
734
+ except Exception: raise Exception("args.papersolver_max_steps must be a valid integer!")
735
+ try: mlesolver_max_steps = int(args.mlesolver_max_steps.lower()) if type(args.mlesolver_max_steps) == str else args.mlesolver_max_steps
736
+ except Exception: raise Exception("args.mlesolver_max_steps must be a valid integer!")
737
+ if parallel_labs:
738
+ num_parallel_labs = int(args.num_parallel_labs)
739
+ print("="*20 , f"RUNNING {num_parallel_labs} LABS IN PARALLEL", "="*20)
740
+ else: num_parallel_labs = 0
741
+
742
+ api_key = (os.getenv('OPENAI_API_KEY') or args.api_key) if (hasattr(args, 'api_key') or os.getenv('OPENAI_API_KEY')) else None
743
+ deepseek_api_key = (os.getenv('DEEPSEEK_API_KEY') or args.deepseek_api_key) if (hasattr(args, 'deepseek_api_key') or os.getenv('DEEPSEEK_API_KEY')) else None
744
+ if api_key is not None and os.getenv('OPENAI_API_KEY') is None: os.environ["OPENAI_API_KEY"] = args.api_key
745
+ if deepseek_api_key is not None and os.getenv('DEEPSEEK_API_KEY') is None: os.environ["DEEPSEEK_API_KEY"] = args.deepseek_api_key
746
+
747
+ if not api_key and not deepseek_api_key: raise ValueError("API key must be provided via --api-key / -deepseek-api-key or the OPENAI_API_KEY / DEEPSEEK_API_KEY environment variable.")
748
+
749
+ if human_mode or args.research_topic is None: research_topic = input("Please name an experiment idea for AgentLaboratory to perform: ")
750
+ else: research_topic = args.research_topic
751
+
752
+ task_notes_LLM = list()
753
+ task_notes = args.task_notes
754
+ for _task in task_notes:
755
+ for _note in task_notes[_task]:
756
+ task_notes_LLM.append({"phases": [_task.replace("-", " ")], "note": _note})
757
+
758
+ if args.language != "English":
759
+ task_notes_LLM.append(
760
+ {"phases": ["literature review", "plan formulation", "data preparation", "running experiments", "results interpretation", "report writing", "report refinement"],
761
+ "note": f"You should always write in the following language to converse and to write the report {args.language}"},
762
+ )
763
+
764
+ human_in_loop = {
765
+ "literature review": human_mode,
766
+ "plan formulation": human_mode,
767
+ "data preparation": human_mode,
768
+ "running experiments": human_mode,
769
+ "results interpretation": human_mode,
770
+ "report writing": human_mode,
771
+ "report refinement": human_mode,
772
+ }
773
+
774
+ agent_models = {
775
+ "literature review": llm_backend,
776
+ "plan formulation": llm_backend,
777
+ "data preparation": llm_backend,
778
+ "running experiments": llm_backend,
779
+ "report writing": llm_backend,
780
+ "results interpretation": llm_backend,
781
+ "paper refinement": llm_backend,
782
+ }
783
+ if parallel_labs:
784
+ remove_figures()
785
+ GLOBAL_AGENTRXIV = AgentRxiv()
786
+ remove_directory(f"{RESEARCH_DIR_PATH}")
787
+ os.mkdir(os.path.join(".", f"{RESEARCH_DIR_PATH}"))
788
+ from concurrent.futures import ThreadPoolExecutor, as_completed
789
+ if not compile_pdf: raise Exception("PDF compilation must be used with agentRxiv!")
790
+ def run_lab(parallel_lab_index):
791
+ time_str = str()
792
+ time_now = time.time()
793
+ for _paper_index in range(num_papers_to_write):
794
+ lab_dir = os.path.join(RESEARCH_DIR_PATH, f"research_dir_lab{parallel_lab_index}_paper{_paper_index}")
795
+ os.mkdir(lab_dir)
796
+ os.mkdir(os.path.join(lab_dir, "src"))
797
+ os.mkdir(os.path.join(lab_dir, "tex"))
798
+ lab_instance = LaboratoryWorkflow(
799
+ parallelized=True,
800
+ research_topic=research_topic,
801
+ notes=task_notes_LLM,
802
+ agent_model_backbone=agent_models,
803
+ human_in_loop_flag=human_in_loop,
804
+ openai_api_key=api_key,
805
+ compile_pdf=compile_pdf,
806
+ num_papers_lit_review=num_papers_lit_review,
807
+ papersolver_max_steps=papersolver_max_steps,
808
+ mlesolver_max_steps=mlesolver_max_steps,
809
+ paper_index=_paper_index,
810
+ lab_index=parallel_lab_index,
811
+ except_if_fail=except_if_fail,
812
+ lab_dir=lab_dir,
813
+ agentRxiv=True,
814
+ agentrxiv_papers=args.agentrxiv_papers
815
+ )
816
+ lab_instance.perform_research()
817
+ time_str += str(time.time() - time_now) + " | "
818
+ with open(f"agent_times_{parallel_lab_index}.txt", "w") as f:
819
+ f.write(time_str)
820
+ time_now = time.time()
821
+
822
+ with ThreadPoolExecutor(max_workers=num_parallel_labs) as executor:
823
+ futures = [executor.submit(run_lab, lab_idx) for lab_idx in range(num_parallel_labs)]
824
+ for future in as_completed(futures):
825
+ try: future.result()
826
+ except Exception as e: print(f"Error in lab: {e}")
827
+
828
+ raise NotImplementedError("Todo: implement parallel labs")
829
+ else:
830
+ # remove previous files
831
+ remove_figures()
832
+ if agentRxiv: GLOBAL_AGENTRXIV = AgentRxiv(lab_index)
833
+ if not agentRxiv:
834
+ remove_directory(f"{RESEARCH_DIR_PATH}")
835
+ os.mkdir(os.path.join(".", f"{RESEARCH_DIR_PATH}"))
836
+ # make src and research directory
837
+ if not os.path.exists("state_saves"): os.mkdir(os.path.join(".", "state_saves"))
838
+ time_str = str()
839
+ time_now = time.time()
840
+ for _paper_index in range(num_papers_to_write):
841
+ lab_direct = f"{RESEARCH_DIR_PATH}/research_dir_{_paper_index}_lab_{lab_index}"
842
+ os.mkdir(os.path.join(".", lab_direct))
843
+ os.mkdir(os.path.join(f"./{lab_direct}", "src"))
844
+ os.mkdir(os.path.join(f"./{lab_direct}", "tex"))
845
+ lab = LaboratoryWorkflow(
846
+ research_topic=research_topic,
847
+ notes=task_notes_LLM,
848
+ agent_model_backbone=agent_models,
849
+ human_in_loop_flag=human_in_loop,
850
+ openai_api_key=api_key,
851
+ compile_pdf=compile_pdf,
852
+ num_papers_lit_review=num_papers_lit_review,
853
+ papersolver_max_steps=papersolver_max_steps,
854
+ mlesolver_max_steps=mlesolver_max_steps,
855
+ paper_index=_paper_index,
856
+ except_if_fail=except_if_fail,
857
+ agentRxiv=False,
858
+ lab_index=lab_index,
859
+ lab_dir=f"./{lab_direct}"
860
+ )
861
+ lab.perform_research()
862
+ time_str += str(time.time() - time_now) + " | "
863
+ with open(f"agent_times_{lab_index}.txt", "w") as f:
864
+ f.write(time_str)
865
+ time_now = time.time()
866
+
867
+
868
+
869
+
870
+
871
+
872
+
873
+ """
874
+ @@@@@@@@@@@@@@@ CHECKLIST @@@@@@@@@@@@@@@
875
+ Practical:
876
+ ----------
877
+ - Make a better config system (YAML?)
878
+
879
+ Advancements:
880
+ -------------
881
+ - Make the ability to have agents build on top of their own research
882
+ - Run agent labs in parallel (asynch)
883
+
884
+ """
885
+
886
+
887
+
888
+
889
+
890
+
891
+
agentlab-pipeline/decision_point_attention.yaml ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Decision Point Attention — Linear Attention + Agent 研究
2
+ # 目标: NeurIPS 2026 (DDL: May 6)
3
+
4
+ copilot-mode: False
5
+
6
+ research-topic: >
7
+ Design and evaluate 'Decision Point Attention' (DPA), a novel hybrid attention mechanism
8
+ for LLM-based agents that uses linear attention (GLA/DeltaNet) as the default backbone
9
+ but dynamically routes critical 'decision point' tokens through full softmax attention layers.
10
+
11
+ MOTIVATION: MiniMax abandoned linear attention in their M2 model because it failed on
12
+ multi-turn agent reasoning tasks. Current hybrid models (Jamba 1:7, Kimi Linear 1:3) add
13
+ full attention uniformly, wasting compute. Agent trajectories have clear structure —
14
+ not all steps need full attention. Tool selection, plan revision, and error recovery are
15
+ critical decision points; observation parsing and routine text are not.
16
+
17
+ APPROACH:
18
+ 1. Build a GLA/Mamba-2 backbone with a learned binary router that classifies each token
19
+ as 'decision point' (route to softmax) or 'routine' (stay in linear attention)
20
+ 2. Train the router using agent trajectory data with supervision: tokens at tool-call
21
+ boundaries, plan changes, and error messages are labeled as decision points
22
+ 3. Evaluate on multi-step agent benchmarks
23
+
24
+ EXPERIMENTS TO RUN:
25
+ 1. Implement a simplified DPA simulation: compare performance of linear attention models
26
+ vs Transformers on multi-step reasoning tasks (HotpotQA, GSM8K chain-of-thought)
27
+ 2. Analyze existing agent trajectories to quantify what fraction of tokens are at
28
+ decision points (hypothesis: <15%)
29
+ 3. Simulate DPA by masking attention: use a Transformer but restrict most tokens to
30
+ local/linear attention window, only allowing full attention at decision points
31
+ 4. Measure accuracy vs compute tradeoff across different decision-point ratios (5%, 10%, 15%, 25%, 50%)
32
+ 5. Compare with uniform hybrid baselines (every Nth layer is full attention)
33
+
34
+ KEY RELATED WORK:
35
+ - MiniMax M2: linear attention failed on multi-hop reasoning in production
36
+ - NAtS-L (2026): token-level routing between linear/softmax attention
37
+ - Kimi Linear KDA: 3:1 linear-to-full ratio for agentic workloads
38
+ - Based (ICML 2024): recall-throughput tradeoff in linear attention
39
+ - Gated DeltaNet (ICLR 2025): delta rule improves recall
40
+ - Illusion of State (ICML 2024): SSM state-tracking limitations
41
+
42
+ api-key: "at-8365f13dc30f20e20f83abb3cc2fde83295ca871"
43
+ llm-backend: "llmbox/gpt-5.2"
44
+ lit-review-backend: "llmbox/gpt-5.2"
45
+
46
+ language: "English"
47
+
48
+ num-papers-lit-review: 8
49
+ num-papers-to-write: 1
50
+ parallel-labs: False
51
+
52
+ mlesolver-max-steps: 5
53
+ papersolver-max-steps: 3
54
+ lab-index: 1
55
+ load-existing: False
56
+ except-if-fail: False
57
+ compile-latex: False
58
+
59
+ task-notes:
60
+ plan-formulation:
61
+ - 'Focus on designing a simulation experiment that demonstrates the value of decision-point-aware attention routing for agent tasks'
62
+ - 'Use existing pretrained models (e.g., Mamba-2, RWKV, GPT-2/Qwen-small) rather than training from scratch'
63
+ - 'The key insight to validate: not all tokens in an agent trajectory need full attention — only ~10-15% at decision points'
64
+ - 'Design clear baselines: (1) full Transformer, (2) pure linear attention, (3) uniform hybrid, (4) DPA (ours)'
65
+ - 'DO NOT PLAN FOR TOO LONG. Submit your plan soon.'
66
+ data-preparation:
67
+ - 'Use HotpotQA, GSM8K, or ToolBench datasets for multi-step reasoning evaluation'
68
+ - 'Create synthetic agent trajectories with labeled decision points (tool calls, plan changes, errors)'
69
+ - 'from datasets import load_dataset'
70
+ running-experiments:
71
+ - "For all strings you instantiate you must use triple quotes (''')"
72
+ - 'Use PyTorch for all model experiments'
73
+ - 'You can use HuggingFace transformers to load pretrained models'
74
+ - 'Focus on measuring: accuracy at different attention routing ratios (5%, 10%, 15%, 25%, 50%, 100%)'
75
+ - 'Also measure FLOPs/latency to show efficiency gains'
76
+ - 'Create publication-quality figures showing accuracy vs compute tradeoff curves'
77
+ - 'Parallelize experiments where possible'
78
+ - 'Generate figures with clean, professional design suitable for NeurIPS'
79
+ results-interpretation:
80
+ - 'Compare DPA against uniform hybrid and full Transformer baselines'
81
+ - 'Show that ~10-15% decision-point ratio achieves >95% of full Transformer accuracy with significant compute savings'
82
+ - 'Discuss implications for deploying efficient agent systems'
83
+ report-writing:
84
+ - 'Write in NeurIPS 2026 paper format'
85
+ - 'Title suggestion: Decision Point Attention: Strategic Full Attention Routing for Efficient Agent Reasoning with Linear Attention'
86
+ - 'Emphasize the practical motivation from MiniMax M2 failure case'
agentlab-pipeline/inference_patched.py ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import openai
2
+ import time, tiktoken
3
+ from openai import OpenAI
4
+ import os, anthropic, json
5
+ import google.generativeai as genai
6
+
7
+ TOKENS_IN = dict()
8
+ TOKENS_OUT = dict()
9
+
10
+ encoding = tiktoken.get_encoding("cl100k_base")
11
+
12
+ def curr_cost_est():
13
+ costmap_in = {
14
+ "gpt-4o": 2.50 / 1000000,
15
+ "gpt-4o-mini": 0.150 / 1000000,
16
+ "o1-preview": 15.00 / 1000000,
17
+ "o1-mini": 3.00 / 1000000,
18
+ "claude-3-5-sonnet": 3.00 / 1000000,
19
+ "deepseek-chat": 1.00 / 1000000,
20
+ "o1": 15.00 / 1000000,
21
+ "o3-mini": 1.10 / 1000000,
22
+ }
23
+ costmap_out = {
24
+ "gpt-4o": 10.00/ 1000000,
25
+ "gpt-4o-mini": 0.6 / 1000000,
26
+ "o1-preview": 60.00 / 1000000,
27
+ "o1-mini": 12.00 / 1000000,
28
+ "claude-3-5-sonnet": 12.00 / 1000000,
29
+ "deepseek-chat": 5.00 / 1000000,
30
+ "o1": 60.00 / 1000000,
31
+ "o3-mini": 4.40 / 1000000,
32
+ }
33
+ return sum([costmap_in[_]*TOKENS_IN[_] for _ in TOKENS_IN]) + sum([costmap_out[_]*TOKENS_OUT[_] for _ in TOKENS_OUT])
34
+
35
+ def query_model(model_str, prompt, system_prompt, openai_api_key=None, gemini_api_key=None, anthropic_api_key=None, tries=5, timeout=5.0, temp=None, print_cost=True, version="1.5"):
36
+ preloaded_api = os.getenv('OPENAI_API_KEY')
37
+ if openai_api_key is None and preloaded_api is not None:
38
+ openai_api_key = preloaded_api
39
+ if openai_api_key is None and anthropic_api_key is None:
40
+ raise Exception("No API key provided in query_model function")
41
+ if openai_api_key is not None:
42
+ openai.api_key = openai_api_key
43
+ os.environ["OPENAI_API_KEY"] = openai_api_key
44
+ if anthropic_api_key is not None:
45
+ os.environ["ANTHROPIC_API_KEY"] = anthropic_api_key
46
+ if gemini_api_key is not None:
47
+ os.environ["GEMINI_API_KEY"] = gemini_api_key
48
+ for _ in range(tries):
49
+ try:
50
+ if model_str == "gpt-4o-mini" or model_str == "gpt4omini" or model_str == "gpt-4omini" or model_str == "gpt4o-mini":
51
+ model_str = "gpt-4o-mini"
52
+ messages = [
53
+ {"role": "system", "content": system_prompt},
54
+ {"role": "user", "content": prompt}]
55
+ if version == "0.28":
56
+ if temp is None:
57
+ completion = openai.ChatCompletion.create(
58
+ model=f"{model_str}", # engine = "deployment_name".
59
+ messages=messages
60
+ )
61
+ else:
62
+ completion = openai.ChatCompletion.create(
63
+ model=f"{model_str}", # engine = "deployment_name".
64
+ messages=messages, temperature=temp
65
+ )
66
+ else:
67
+ client = OpenAI()
68
+ if temp is None:
69
+ completion = client.chat.completions.create(
70
+ model="gpt-4o-mini-2024-07-18", messages=messages, )
71
+ else:
72
+ completion = client.chat.completions.create(
73
+ model="gpt-4o-mini-2024-07-18", messages=messages, temperature=temp)
74
+ answer = completion.choices[0].message.content
75
+
76
+ elif model_str == "gemini-2.0-pro":
77
+ genai.configure(api_key=gemini_api_key)
78
+ model = genai.GenerativeModel(model_name="gemini-2.0-pro-exp-02-05", system_instruction=system_prompt)
79
+ answer = model.generate_content(prompt).text
80
+ elif model_str == "gemini-1.5-pro":
81
+ genai.configure(api_key=gemini_api_key)
82
+ model = genai.GenerativeModel(model_name="gemini-1.5-pro", system_instruction=system_prompt)
83
+ answer = model.generate_content(prompt).text
84
+ elif model_str == "o3-mini":
85
+ model_str = "o3-mini"
86
+ messages = [
87
+ {"role": "user", "content": system_prompt + prompt}]
88
+ if version == "0.28":
89
+ completion = openai.ChatCompletion.create(
90
+ model=f"{model_str}", messages=messages)
91
+ else:
92
+ client = OpenAI()
93
+ completion = client.chat.completions.create(
94
+ model="o3-mini-2025-01-31", messages=messages)
95
+ answer = completion.choices[0].message.content
96
+
97
+ elif model_str == "claude-3.5-sonnet":
98
+ client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
99
+ message = client.messages.create(
100
+ model="claude-3-5-sonnet-latest",
101
+ system=system_prompt,
102
+ messages=[{"role": "user", "content": prompt}])
103
+ answer = json.loads(message.to_json())["content"][0]["text"]
104
+ elif model_str == "gpt4o" or model_str == "gpt-4o":
105
+ model_str = "gpt-4o"
106
+ messages = [
107
+ {"role": "system", "content": system_prompt},
108
+ {"role": "user", "content": prompt}]
109
+ if version == "0.28":
110
+ if temp is None:
111
+ completion = openai.ChatCompletion.create(
112
+ model=f"{model_str}", # engine = "deployment_name".
113
+ messages=messages
114
+ )
115
+ else:
116
+ completion = openai.ChatCompletion.create(
117
+ model=f"{model_str}", # engine = "deployment_name".
118
+ messages=messages, temperature=temp)
119
+ else:
120
+ client = OpenAI()
121
+ if temp is None:
122
+ completion = client.chat.completions.create(
123
+ model="gpt-4o-2024-08-06", messages=messages, )
124
+ else:
125
+ completion = client.chat.completions.create(
126
+ model="gpt-4o-2024-08-06", messages=messages, temperature=temp)
127
+ answer = completion.choices[0].message.content
128
+ elif model_str == "deepseek-chat":
129
+ model_str = "deepseek-chat"
130
+ messages = [
131
+ {"role": "system", "content": system_prompt},
132
+ {"role": "user", "content": prompt}]
133
+ if version == "0.28":
134
+ raise Exception("Please upgrade your OpenAI version to use DeepSeek client")
135
+ else:
136
+ deepseek_client = OpenAI(
137
+ api_key=os.getenv('DEEPSEEK_API_KEY'),
138
+ base_url="https://api.deepseek.com/v1"
139
+ )
140
+ if temp is None:
141
+ completion = deepseek_client.chat.completions.create(
142
+ model="deepseek-chat",
143
+ messages=messages)
144
+ else:
145
+ completion = deepseek_client.chat.completions.create(
146
+ model="deepseek-chat",
147
+ messages=messages,
148
+ temperature=temp)
149
+ answer = completion.choices[0].message.content
150
+ elif model_str == "o1-mini":
151
+ model_str = "o1-mini"
152
+ messages = [
153
+ {"role": "user", "content": system_prompt + prompt}]
154
+ if version == "0.28":
155
+ completion = openai.ChatCompletion.create(
156
+ model=f"{model_str}", # engine = "deployment_name".
157
+ messages=messages)
158
+ else:
159
+ client = OpenAI()
160
+ completion = client.chat.completions.create(
161
+ model="o1-mini-2024-09-12", messages=messages)
162
+ answer = completion.choices[0].message.content
163
+ elif model_str == "o1":
164
+ model_str = "o1"
165
+ messages = [
166
+ {"role": "user", "content": system_prompt + prompt}]
167
+ if version == "0.28":
168
+ completion = openai.ChatCompletion.create(
169
+ model="o1-2024-12-17", # engine = "deployment_name".
170
+ messages=messages)
171
+ else:
172
+ client = OpenAI()
173
+ completion = client.chat.completions.create(
174
+ model="o1-2024-12-17", messages=messages)
175
+ answer = completion.choices[0].message.content
176
+ elif model_str == "o1-preview":
177
+ model_str = "o1-preview"
178
+ messages = [
179
+ {"role": "user", "content": system_prompt + prompt}]
180
+ if version == "0.28":
181
+ completion = openai.ChatCompletion.create(
182
+ model=f"{model_str}", # engine = "deployment_name".
183
+ messages=messages)
184
+ else:
185
+ client = OpenAI()
186
+ completion = client.chat.completions.create(
187
+ model="o1-preview", messages=messages)
188
+ answer = completion.choices[0].message.content
189
+
190
+ elif model_str.startswith("llmbox/"):
191
+ # ByteDance LLMBox Gateway (OpenAI-compatible)
192
+ actual_model = model_str.split("/", 1)[1]
193
+ messages = [
194
+ {"role": "system", "content": system_prompt},
195
+ {"role": "user", "content": prompt}]
196
+ llmbox_client = OpenAI(
197
+ api_key=os.getenv('LLMBOX_API_KEY', 'at-8365f13dc30f20e20f83abb3cc2fde83295ca871'),
198
+ base_url=os.getenv('LLMBOX_BASE_URL', 'https://llmbox.bytedance.net/v1')
199
+ )
200
+ kwargs = {"model": actual_model, "messages": messages}
201
+ if temp is not None:
202
+ kwargs["temperature"] = temp
203
+ completion = llmbox_client.chat.completions.create(**kwargs)
204
+ raw_content = completion.choices[0].message.content
205
+ # LLMBox may return list or string
206
+ if isinstance(raw_content, list):
207
+ answer = "".join([c.get("text", str(c)) if isinstance(c, dict) else str(c) for c in raw_content])
208
+ else:
209
+ answer = str(raw_content)
210
+ model_str = "gpt-4o" # for cost tracking fallback
211
+
212
+ try:
213
+ if model_str in ["o1-preview", "o1-mini", "claude-3.5-sonnet", "o1", "o3-mini"]:
214
+ encoding = tiktoken.encoding_for_model("gpt-4o")
215
+ elif model_str in ["deepseek-chat"]:
216
+ encoding = tiktoken.encoding_for_model("cl100k_base")
217
+ else:
218
+ encoding = tiktoken.encoding_for_model(model_str)
219
+ if model_str not in TOKENS_IN:
220
+ TOKENS_IN[model_str] = 0
221
+ TOKENS_OUT[model_str] = 0
222
+ TOKENS_IN[model_str] += len(encoding.encode(system_prompt + prompt))
223
+ TOKENS_OUT[model_str] += len(encoding.encode(answer))
224
+ if print_cost:
225
+ print(f"Current experiment cost = ${curr_cost_est()}, ** Approximate values, may not reflect true cost")
226
+ except Exception as e:
227
+ if print_cost: print(f"Cost approximation has an error? {e}")
228
+ return answer
229
+ except Exception as e:
230
+ print("Inference Exception:", e)
231
+ time.sleep(timeout)
232
+ continue
233
+ raise Exception("Max retries: timeout")
234
+
235
+
236
+ #print(query_model(model_str="o1-mini", prompt="hi", system_prompt="hey"))
agentlab-pipeline/tools_patched.py ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from utils import *
2
+
3
+ import os
4
+ import time
5
+ import arxiv
6
+ import io, sys
7
+ import traceback
8
+ import matplotlib
9
+ import numpy as np
10
+ import multiprocessing
11
+ from pypdf import PdfReader
12
+ from datasets import load_dataset
13
+ from psutil._common import bytes2human
14
+ from datasets import load_dataset_builder
15
+ from semanticscholar import SemanticScholar
16
+ from sklearn.metrics.pairwise import linear_kernel
17
+ from sklearn.feature_extraction.text import TfidfVectorizer
18
+
19
+
20
+
21
+ class HFDataSearch:
22
+ def __init__(self, like_thr=3, dwn_thr=50) -> None:
23
+ """
24
+ Class for finding relevant huggingface datasets
25
+ :param like_thr:
26
+ :param dwn_thr:
27
+ """
28
+ self.dwn_thr = dwn_thr
29
+ self.like_thr = like_thr
30
+ self.ds = load_dataset("nkasmanoff/huggingface-datasets")["train"]
31
+
32
+ # Initialize lists to collect filtered data
33
+ filtered_indices = []
34
+ filtered_descriptions = []
35
+ filtered_likes = []
36
+ filtered_downloads = []
37
+
38
+ # Iterate over the dataset and filter based on criteria
39
+ for idx, item in enumerate(self.ds):
40
+ # Get likes and downloads, handling None values
41
+ likes = int(item['likes']) if item['likes'] is not None else 0
42
+ downloads = int(item['downloads']) if item['downloads'] is not None else 0
43
+
44
+ # Check if likes and downloads meet the thresholds
45
+ if likes >= self.like_thr and downloads >= self.dwn_thr:
46
+ # Check if the description is a non-empty string
47
+ description = item['description']
48
+ if isinstance(description, str) and description.strip():
49
+ # Collect the data
50
+ filtered_indices.append(idx)
51
+ filtered_descriptions.append(description)
52
+ filtered_likes.append(likes)
53
+ filtered_downloads.append(downloads)
54
+
55
+ # Check if any datasets meet all criteria
56
+ if not filtered_indices:
57
+ print("No datasets meet the specified criteria.")
58
+ self.ds = []
59
+ self.descriptions = []
60
+ self.likes_norm = []
61
+ self.downloads_norm = []
62
+ self.description_vectors = None
63
+ return # Exit the constructor
64
+
65
+ # Filter the datasets using the collected indices
66
+ self.ds = self.ds.select(filtered_indices)
67
+
68
+ # Update descriptions, likes, and downloads
69
+ self.descriptions = filtered_descriptions
70
+ self.likes = np.array(filtered_likes)
71
+ self.downloads = np.array(filtered_downloads)
72
+
73
+ # Normalize likes and downloads
74
+ self.likes_norm = self._normalize(self.likes)
75
+ self.downloads_norm = self._normalize(self.downloads)
76
+
77
+ # Vectorize the descriptions
78
+ self.vectorizer = TfidfVectorizer()
79
+ self.description_vectors = self.vectorizer.fit_transform(self.descriptions)
80
+
81
+ def _normalize(self, arr):
82
+ min_val = arr.min()
83
+ max_val = arr.max()
84
+ if max_val - min_val == 0:
85
+ return np.zeros_like(arr, dtype=float)
86
+ return (arr - min_val) / (max_val - min_val)
87
+
88
+ def retrieve_ds(self, query, N=10, sim_w=1.0, like_w=0.0, dwn_w=0.0):
89
+ """
90
+ Retrieves the top N datasets matching the query, weighted by likes and downloads.
91
+ :param query: The search query string.
92
+ :param N: The number of results to return.
93
+ :param sim_w: Weight for cosine similarity.
94
+ :param like_w: Weight for likes.
95
+ :param dwn_w: Weight for downloads.
96
+ :return: List of top N dataset items.
97
+ """
98
+ if not self.ds or self.description_vectors is None:
99
+ print("No datasets available to search.")
100
+ return []
101
+
102
+ query_vector = self.vectorizer.transform([query])
103
+ cosine_similarities = linear_kernel(query_vector, self.description_vectors).flatten()
104
+ # Normalize cosine similarities
105
+ cosine_similarities_norm = self._normalize(cosine_similarities)
106
+ # Compute final scores
107
+ final_scores = (
108
+ sim_w * cosine_similarities_norm +
109
+ like_w * self.likes_norm +
110
+ dwn_w * self.downloads_norm
111
+ )
112
+ # Get top N indices
113
+ top_indices = final_scores.argsort()[-N:][::-1]
114
+ # Convert indices to Python ints
115
+ top_indices = [int(i) for i in top_indices]
116
+ top_datasets = [self.ds[i] for i in top_indices]
117
+ # check if dataset has a test & train set
118
+ has_test_set = list()
119
+ has_train_set = list()
120
+ ds_size_info = list()
121
+ for i in top_indices:
122
+ try:
123
+ dbuilder = load_dataset_builder(self.ds[i]["id"], trust_remote_code=True).info
124
+ except Exception as e:
125
+ has_test_set.append(False)
126
+ has_train_set.append(False)
127
+ ds_size_info.append((None, None, None, None))
128
+ continue
129
+
130
+ if dbuilder.splits is None:
131
+ has_test_set.append(False)
132
+ has_train_set.append(False)
133
+ ds_size_info.append((None, None, None, None))
134
+ continue
135
+ # Print number of examples for
136
+ has_test, has_train = "test" in dbuilder.splits, "train" in dbuilder.splits
137
+ has_test_set.append(has_test)
138
+ has_train_set.append(has_train)
139
+ test_dwn_size, test_elem_size = None, None
140
+ train_dwn_size, train_elem_size = None, None
141
+ if has_test:
142
+ test_dwn_size = bytes2human(dbuilder.splits["test"].num_bytes)
143
+ test_elem_size = dbuilder.splits["test"].num_examples
144
+ if has_train:
145
+ train_dwn_size = bytes2human(dbuilder.splits["train"].num_bytes)
146
+ train_elem_size = dbuilder.splits["train"].num_examples
147
+ ds_size_info.append((test_dwn_size, test_elem_size, train_dwn_size, train_elem_size))
148
+ for _i in range(len(top_datasets)):
149
+ top_datasets[_i]["has_test_set"] = has_test_set[_i]
150
+ top_datasets[_i]["has_train_set"] = has_train_set[_i]
151
+ top_datasets[_i]["test_download_size"] = ds_size_info[_i][0]
152
+ top_datasets[_i]["test_element_size"] = ds_size_info[_i][1]
153
+ top_datasets[_i]["train_download_size"] = ds_size_info[_i][2]
154
+ top_datasets[_i]["train_element_size"] = ds_size_info[_i][3]
155
+ return top_datasets
156
+
157
+ def results_str(self, results):
158
+ """
159
+ Provide results as list of results in human-readable format.
160
+ :param results: (list(dict)) list of results from search
161
+ :return: (list(str)) list of results in human-readable format
162
+ """
163
+ result_strs = list()
164
+ for result in results:
165
+ res_str = f"Dataset ID: {result['id']}\n"
166
+ res_str += f"Description: {result['description']}\n"
167
+ res_str += f"Likes: {result['likes']}\n"
168
+ res_str += f"Downloads: {result['downloads']}\n"
169
+ res_str += f"Has Testing Set: {result['has_test_set']}\n"
170
+ res_str += f"Has Training Set: {result['has_train_set']}\n"
171
+ res_str += f"Test Download Size: {result['test_download_size']}\n"
172
+ res_str += f"Test Dataset Size: {result['test_element_size']}\n"
173
+ res_str += f"Train Download Size: {result['train_download_size']}\n"
174
+ res_str += f"Train Dataset Size: {result['train_element_size']}\n"
175
+ result_strs.append(res_str)
176
+ return result_strs
177
+
178
+
179
+ class SemanticScholarSearch:
180
+ def __init__(self):
181
+ self.sch_engine = SemanticScholar(retry=False)
182
+
183
+ def find_papers_by_str(self, query, N=10):
184
+ paper_sums = list()
185
+ results = self.sch_engine.search_paper(query, limit=N, min_citation_count=3, open_access_pdf=True)
186
+ for _i in range(len(results)):
187
+ paper_sum = f'Title: {results[_i].title}\n'
188
+ paper_sum += f'Abstract: {results[_i].abstract}\n'
189
+ paper_sum += f'Citations: {results[_i].citationCount}\n'
190
+ paper_sum += f'Release Date: year {results[_i].publicationDate.year}, month {results[_i].publicationDate.month}, day {results[_i].publicationDate.day}\n'
191
+ paper_sum += f'Venue: {results[_i].venue}\n'
192
+ paper_sum += f'Paper ID: {results[_i].externalIds["DOI"]}\n'
193
+ paper_sums.append(paper_sum)
194
+ return paper_sums
195
+
196
+ def retrieve_full_paper_text(self, query):
197
+ pass
198
+
199
+
200
+ class ArxivSearch:
201
+ def __init__(self):
202
+ # Construct the default API client.
203
+ self.sch_engine = arxiv.Client()
204
+
205
+ def _process_query(self, query: str) -> str:
206
+ """Process query string to fit within MAX_QUERY_LENGTH while preserving as much information as possible"""
207
+ MAX_QUERY_LENGTH = 300
208
+
209
+ if len(query) <= MAX_QUERY_LENGTH:
210
+ return query
211
+
212
+ # Split into words
213
+ words = query.split()
214
+ processed_query = []
215
+ current_length = 0
216
+
217
+ # Add words while staying under the limit
218
+ # Account for spaces between words
219
+ for word in words:
220
+ # +1 for the space that will be added between words
221
+ if current_length + len(word) + 1 <= MAX_QUERY_LENGTH:
222
+ processed_query.append(word)
223
+ current_length += len(word) + 1
224
+ else:
225
+ break
226
+
227
+ return ' '.join(processed_query)
228
+
229
+ def find_papers_by_str(self, query, N=20):
230
+ processed_query = self._process_query(query)
231
+ max_retries = 3
232
+ retry_count = 0
233
+
234
+ while retry_count < max_retries:
235
+ try:
236
+ search = arxiv.Search(
237
+ query="abs:" + processed_query,
238
+ max_results=N,
239
+ sort_by=arxiv.SortCriterion.Relevance)
240
+
241
+ paper_sums = list()
242
+ # `results` is a generator; you can iterate over its elements one by one...
243
+ for r in self.sch_engine.results(search):
244
+ paperid = r.pdf_url.split("/")[-1]
245
+ pubdate = str(r.published).split(" ")[0]
246
+ paper_sum = f"Title: {r.title}\n"
247
+ paper_sum += f"Summary: {r.summary}\n"
248
+ paper_sum += f"Publication Date: {pubdate}\n"
249
+ #paper_sum += f"Categories: {' '.join(r.categories)}\n"
250
+ paper_sum += f"arXiv paper ID: {paperid}\n"
251
+ paper_sums.append(paper_sum)
252
+ time.sleep(2.0)
253
+ return "\n".join(paper_sums)
254
+
255
+ except Exception as e:
256
+ retry_count += 1
257
+ if retry_count < max_retries:
258
+ time.sleep(2 * retry_count)
259
+ continue
260
+ return None
261
+
262
+ def retrieve_full_paper_text(self, query, MAX_LEN=50000):
263
+ pdf_text = str()
264
+ paper = next(arxiv.Client().results(arxiv.Search(id_list=[query])))
265
+ # Download the PDF with retry logic
266
+ for _dl_try in range(3):
267
+ try:
268
+ paper.download_pdf(filename="downloaded-paper.pdf")
269
+ break
270
+ except Exception as e:
271
+ print(f"PDF download retry {_dl_try+1}/3: {e}")
272
+ time.sleep(5 * (_dl_try + 1))
273
+ if _dl_try == 2:
274
+ return "DOWNLOAD FAILED"
275
+ # creating a pdf reader object
276
+ reader = PdfReader('downloaded-paper.pdf')
277
+ # Iterate over all the pages
278
+ for page_number, page in enumerate(reader.pages, start=1):
279
+ # Extract text from the page
280
+ try:
281
+ text = page.extract_text()
282
+ except Exception as e:
283
+ os.remove("downloaded-paper.pdf")
284
+ time.sleep(2.0)
285
+ return "EXTRACTION FAILED"
286
+
287
+ # Do something with the text (e.g., print it)
288
+ pdf_text += f"--- Page {page_number} ---"
289
+ pdf_text += text
290
+ pdf_text += "\n"
291
+ os.remove("downloaded-paper.pdf")
292
+ time.sleep(2.0)
293
+ return pdf_text[:MAX_LEN]
294
+
295
+
296
+ # Set the non-interactive backend early in the module
297
+ matplotlib.use('Agg')
298
+ import matplotlib.pyplot as plt
299
+
300
+ def worker_run_code(code_str, output_queue):
301
+ output_capture = io.StringIO()
302
+ sys.stdout = output_capture
303
+ try:
304
+ # Create a globals dictionary with __name__ set to "__main__"
305
+ globals_dict = {"__name__": "__main__"}
306
+ exec(code_str, globals_dict)
307
+ except Exception as e:
308
+ output_capture.write(f"[CODE EXECUTION ERROR]: {str(e)}\n")
309
+ traceback.print_exc(file=output_capture)
310
+ finally:
311
+ sys.stdout = sys.__stdout__
312
+ output_queue.put(output_capture.getvalue())
313
+
314
+ def execute_code(code_str, timeout=600, MAX_LEN=1000):
315
+ #code_str = code_str.replace("\\n", "\n")
316
+ code_str = "from utils import *\n" + code_str
317
+ if "load_dataset('pubmed" in code_str:
318
+ return "[CODE EXECUTION ERROR] pubmed Download took way too long. Program terminated"
319
+ if "exit(" in code_str:
320
+ return "[CODE EXECUTION ERROR] The exit() command is not allowed you must remove this."
321
+ output_queue = multiprocessing.Queue()
322
+ proc = multiprocessing.Process(target=worker_run_code, args=(code_str, output_queue))
323
+ proc.start()
324
+ proc.join(timeout)
325
+ if proc.is_alive():
326
+ proc.terminate() # Forcefully kill the process
327
+ proc.join()
328
+ return (f"[CODE EXECUTION ERROR]: Code execution exceeded the timeout limit of {timeout} seconds. "
329
+ "You must reduce the time complexity of your code.")
330
+ else:
331
+ if not output_queue.empty(): output = output_queue.get()
332
+ else: output = ""
333
+ return output
conversation/session-2026-03-20-to-24.md ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Claude 对话记录 — 2026-03-20 ~ 2026-03-24
2
+
3
+ ## 会话概览
4
+ 这是一个跨 4 天的超长会话,涵盖:股票模拟、租房搜索、AI 研究规划、论文 idea 生成、项目搭建。
5
+
6
+ ---
7
+
8
+ ## Part 1: 美股模拟投资系统 (3/20)
9
+
10
+ ### 搭建内容
11
+ - `~/stock-sim/portfolio.py` — 基础买卖/持仓操作
12
+ - `~/stock-sim/monitor.py` — 实时行情监控(终端刷新)
13
+ - `~/stock-sim/stock_screener.py` — 技术面选股器(69只美股,100分制评分)
14
+ - `~/stock-sim/auto_trader.py` — 自动交易Bot(每日定时分析+虚拟买卖)
15
+
16
+ ### 初始持仓
17
+ 各 $20K:AAPL(80股), GOOGL(66股), MSFT(52股), NVDA(115股), TSLA(54股)
18
+
19
+ ### 接入 daily_stock_analysis
20
+ - Clone https://github.com/ZhuLinsen/daily_stock_analysis (24k stars)
21
+ - 配置 LLMBox 网关 (llmbox.bytedance.net) 作为 AI backend
22
+ - 模型: gpt-5.2 via OpenAI-compatible API
23
+ - 成功运行 AI 分析: PLTR(63分Buy), NET(62分Buy), PFE(58分Buy)
24
+
25
+ ### 自动交易Bot操作
26
+ - 减仓: AAPL/NVDA/MSFT/TSLA 各卖一半(空头排列)
27
+ - 买入: MS $12K(评分71,MACD金叉)
28
+ - 保留 $28K 现金
29
+
30
+ ### 上传
31
+ - HuggingFace: https://huggingface.co/jasonfan/stock-sim
32
+
33
+ ---
34
+
35
+ ## Part 2: 湾区租房搜索 (3/20-21)
36
+
37
+ ### 搜索平台
38
+ 1. **Bay123** (bay123.com) — 华人租房论坛,9万+帖子
39
+ 2. **Zillow** — 主流租房平台
40
+ 3. **小红书** — App内搜索 "湾区租房 1b1b"(1290万浏览量)
41
+ 4. **一亩三分地** — 华人社区
42
+ 5. **Bay Panda** — 南湾聚合
43
+
44
+ ### 核心需求
45
+ - 离 TikTok San Jose (250 E Caribbean Dr) 半小时车程
46
+ - 1B1B 或好的 2B2B
47
+ - 亚洲人出租优先
48
+
49
+ ### 精选房源
50
+ | 房源 | 价格 | 离TT |
51
+ |------|------|------|
52
+ | The Arches (94089) | $2,207+ | 5分钟 |
53
+ | Orchard Gardens (94089) | $1,927+ | 4分钟 |
54
+ | Encasa (94089) | $3,582+ | 3分钟 |
55
+ | North SJ 95131 1B1B (Bay123) | $2,800全包 | 15分钟 |
56
+ | Sunnyvale 94085 (Bay123) | $2,000 | 10分钟 |
57
+
58
+ ### 交互地图
59
+ - 本地: `~/stock-sim/rental_data/rental_map.html`
60
+ - 公开: https://huggingface.co/spaces/jasonfan/rental-map
61
+
62
+ ### Vineyard Apartments
63
+ - 2851 Homestead Rd, Santa Clara 95051
64
+ - 离TT ~5 miles, 10-17分钟
65
+ - 官方电话: (408) 203-1488
66
+
67
+ ### GitHub 爬虫工具
68
+ - 小红书: NanmiCoder/MediaCrawler (43.6k stars)
69
+ - Zillow: scrapehero/zillow_real_estate (144 stars)
70
+
71
+ ---
72
+
73
+ ## Part 3: OpenClaw 配置修复 (3/21)
74
+
75
+ ### 问题
76
+ `openclaw` 报 FailoverError — `claude-w` 连接 LLMBox 网关时,`claude-opus-4-6` 模型对 jiashuo.fan 不可用。
77
+
78
+ ### 修复
79
+ - 将 `~/.openclaw/openclaw.json` 中 `cliBackends.claude-cli.command` 从 `/Users/bytedance/.local/bin/claude-w` 改为 `/Users/bytedance/.local/bin/claude`
80
+ - `openclaw gateway restart`
81
+
82
+ ---
83
+
84
+ ## Part 4: Fine-tuning 估算 (3/23)
85
+
86
+ ### 20K ICLR+NeurIPS 论文 Fine-tune
87
+ - 数据量: ~160M tokens, PDF ~60-80GB, 文本 ~1-2GB
88
+ - 7B LoRA (8xH100): ~1小时/3epochs
89
+ - 72B QLoRA (8xH100): ~12-18小时/3epochs
90
+
91
+ ---
92
+
93
+ ## Part 5: AI Research Agent 全景调研 (3/23)
94
+
95
+ ### 调研项目
96
+ | 项目 | Stars | 说明 |
97
+ |------|-------|------|
98
+ | karpathy/autoresearch | 52.2K | 自动实验迭代 |
99
+ | SakanaAI/AI-Scientist | 12.5K | idea→实验→论文 |
100
+ | AutoResearchClaw | 8K | 23阶段全流水线 |
101
+ | AgentLaboratory | 5.4K | co-pilot人机协作 |
102
+ | HKUDS/AI-Researcher | 4.9K | NeurIPS 2025 Spotlight |
103
+ | AI-Scientist-v2 | 2.3K | Tree search, ICLR accepted paper |
104
+
105
+ ---
106
+
107
+ ## Part 6: 研究方向探索 — ICL + Long Document (3/23)
108
+
109
+ ### 4个方向
110
+ 1. Many-Shot ICL 极限与优化
111
+ 2. Lost in the Middle 位置偏差
112
+ 3. RAG vs Long Context for ICL
113
+ 4. 高效长上下文架构
114
+
115
+ ### 会议 DDL
116
+ - COLM 2026: 3/31 (太赶)
117
+ - NeurIPS 2026: 5/6 (44天)
118
+ - EMNLP 2026: 5/25 (63天,最推荐)
119
+
120
+ ---
121
+
122
+ ## Part 7: Linear Attention + RAG — 10个 Idea (3/23)
123
+
124
+ ### Top Ideas
125
+ 1. RAG 替代 Hybrid 中的 Softmax 层
126
+ 2. State-Aware Adaptive Retrieval
127
+ 3. RETRO-Linear
128
+ 4. RAG 解决 Linear Attention 多跳推理
129
+ 5. Retrieval-Guided State Compression
130
+ 6. State Size vs Retrieval Budget 最优分配
131
+ 7. Linear Attention 天然去噪 RAG
132
+ 8. RAG-Aware Distillation
133
+ 9. Streaming RAG with Linear Attention
134
+ 10. LIRA-Bench (Benchmark)
135
+
136
+ ---
137
+
138
+ ## Part 8: Agent + ICL + Linear Attention — 10个 Idea (3/23)
139
+
140
+ ### Top Ideas
141
+ 1. AgentBench on Linear Attention 系统评估
142
+ 2. **Decision Point Attention** ← 最终选定
143
+ 3. Recurrent State as Agent Working Memory
144
+ 4. Many-Shot ICL Tool Learning
145
+ 5. Infinite-Horizon Streaming Agent
146
+ 6. Agent Trajectory Compression
147
+ 7. State-Tracking-Aware Hybrid
148
+ 8. Multi-Agent Debate with Linear Attention
149
+ 9. ICL-Tuned Linear Attention for Agent Adaptation
150
+ 10. Titans Memory + Agent Episodic Memory
151
+
152
+ ---
153
+
154
+ ## Part 8: Decision Point Attention 项目搭建 (3/23-24)
155
+
156
+ ### 核心 Idea
157
+ Linear attention 在多轮 agent 推理中失败(MiniMax M2 案例)。DPA 只在关键决策点(~10-15%)用 full attention,其余用 linear attention。
158
+
159
+ ### 已完成
160
+ - `~/decision-point-attention/` — 完整项目框架
161
+ - Router: Learned (STE) + Fixed ratio + 监督 loss
162
+ - Models: DPA + Full Transformer + Pure Linear + Uniform Hybrid
163
+ - Data: 2000条标注 agent trajectory (avg 15% decision ratio)
164
+ - Eval: benchmark.py (simulate/train/finetune 三模式)
165
+ - Figures: 3个出版级 PDF (accuracy_vs_flops, ratio_ablation, trajectory_analysis)
166
+ - Simulation: 已跑完 13 个模型变体
167
+
168
+ ### AgentLaboratory 自动研究
169
+ - Clone + 配置 LLMBox 网关
170
+ - 修复: PDF 下载重试 + LLMBox list 返回值兼容
171
+ - Literature Review 已完成: Routing Transformer, HAG 等论文分析
172
+ - 到实验阶段时代码执行出错(已手动搭建替代)
173
+
174
+ ### 上传
175
+ - HuggingFace: https://huggingface.co/jasonfan/decision-point-attention
176
+
177
+ ### 待完成(需要 Merlin GPU)
178
+ - `src/train.py` — 训练主循环
179
+ - Qwen2.5-7B + DPA Router 训练
180
+ - HotpotQA/GSM8K 真实评估
181
+ - NeurIPS LaTeX 论文
182
+
183
+ ---
184
+
185
+ ## 关键配置信息
186
+
187
+ ### LLMBox (字节内部免费)
188
+ - URL: https://llmbox.bytedance.net/v1
189
+ - Token: at-8365f13dc30f20e20f83abb3cc2fde83295ca871
190
+ - 可用模型: gpt-5.x, glm-5, kimi-k2.5, minimax-2.x
191
+
192
+ ### HuggingFace
193
+ - 用户: jasonfan
194
+ - Repos: stock-sim, rental-map (Space), decision-point-attention
195
+
196
+ ### OpenClaw
197
+ - 版本: 2026.3.12
198
+ - Backend: claude (改自 claude-w)
199
+ - Gateway: ws://127.0.0.1:18789
dpa-project/CONVERSATION_LOG.md ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Decision Point Attention — 研究对话记录
2
+
3
+ ## 项目起源
4
+ - 日期: 2026-03-23
5
+ - 目标会议: NeurIPS 2026 (DDL: May 6, 2026)
6
+
7
+ ## 核心 Idea
8
+ **Decision Point Attention (DPA)**: 在 agent trajectory 中,只有 ~10-15% 的 token 是关键决策点(tool call, plan revision, error recovery)。DPA 让这些 token 走 full softmax attention,其余走 linear attention,实现 Transformer 级推理能力 + 接近线性注意力的效率。
9
+
10
+ ## 动机
11
+ - MiniMax M2 放弃 linear attention 因为多轮推理失败
12
+ - Jamba (1:7), Kimi Linear (1:3) 均匀混合,浪费算力
13
+ - Agent trajectory 有明确结构,不是每步都同等重要
14
+
15
+ ## 相关工作
16
+ - Routing Transformer (ICLR 2020): content-based sparse routing
17
+ - NAtS-L (2026): token-level hybrid routing
18
+ - Kimi Linear KDA: 3:1 ratio for agentic workloads
19
+ - Based (ICML 2024): recall-throughput tradeoff
20
+ - Gated DeltaNet (ICLR 2025): delta rule improves recall
21
+ - Illusion of State (ICML 2024): SSM state-tracking limitations
22
+ - RNNs are not Transformers Yet (ICLR 2025): RAG/attention closes gap
23
+
24
+ ## 实验计划
25
+ 1. Simulation: 模拟不同 decision ratio 下的 quality vs compute tradeoff (DONE)
26
+ 2. Trajectory Analysis: 分析真实 agent trajectories 中 decision point 比例 (DONE)
27
+ 3. Training: Fine-tune Qwen2.5-7B with DPA router on Merlin 8xH100 (TODO)
28
+ 4. Evaluation: HotpotQA, GSM8K, ToolBench benchmarks (TODO)
29
+ 5. Ablation: Router architecture, ratio sweeps, layer placement (TODO)
30
+
31
+ ## 文件结构
32
+ - `src/models/router.py` — Decision Point Router (learned + fixed)
33
+ - `src/models/dpa_model.py` — DPA architecture (LinearAttn + FullAttn + Router)
34
+ - `src/models/baselines.py` — Full Transformer, Pure Linear, Uniform Hybrid
35
+ - `src/data/agent_trajectory.py` — Trajectory generator & labeling
36
+ - `src/data/datasets.py` — HotpotQA, GSM8K, ToolBench loaders
37
+ - `src/eval/benchmark.py` — Unified evaluation pipeline
38
+ - `src/eval/metrics.py` — FLOPs, latency, KV cache metrics
39
+ - `src/eval/visualize.py` — Publication figures
40
+ - `configs/` — Simulation & training configs
41
+ - `scripts/` — Run scripts for local & Merlin
42
+
43
+ ## Simulation 结果 (random init, 未训练)
44
+ | Model | FLOPs Ratio | PPL |
45
+ |-------|-------------|-----|
46
+ | Full Transformer | 100% | 38905 |
47
+ | Pure Linear | 12.5% | 38134 |
48
+ | Uniform Hybrid | 27.1% | 38856 |
49
+ | DPA (10%) | 22.5% | 36799 |
50
+ | DPA (15%) | 27.5% | 37978 |
51
+ | DPA (25%) | 37.5% | 38174 |
52
+
53
+ (PPL 差异来自随机初始化,训练后会有显著差别)
54
+
55
+ ## 下一步
56
+ 1. 在 Merlin 上跑 `scripts/run_dpa.sh` 训练 7B 模型
57
+ 2. 评估在 HotpotQA/GSM8K 上的多步推理准确率
58
+ 3. 写 NeurIPS 论文(LaTeX 模板已准备)
dpa-project/README.md ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Decision Point Attention (DPA)
2
+
3
+ **Strategic Full Attention Routing for Efficient Agent Reasoning with Linear Attention**
4
+
5
+ Target: NeurIPS 2026 (DDL: May 6, 2026)
6
+
7
+ ## Core Idea
8
+
9
+ Linear attention models (Mamba, GLA, DeltaNet) fail on multi-turn agent reasoning (MiniMax M2 case).
10
+ Current hybrids (Jamba 1:7, Kimi Linear 1:3) add full attention uniformly.
11
+ **DPA routes only "decision point" tokens (~10-15%) through full softmax attention**, keeping the rest on linear attention.
12
+
13
+ ## Project Structure
14
+
15
+ ```
16
+ src/
17
+ models/
18
+ dpa_model.py # DPA architecture (GLA + softmax router)
19
+ baselines.py # Transformer, pure linear, uniform hybrid baselines
20
+ router.py # Decision point router (learned binary classifier)
21
+ data/
22
+ agent_trajectory.py # Synthetic agent trajectory generator
23
+ decision_points.py # Decision point labeling & analysis
24
+ datasets.py # HotpotQA, GSM8K, ToolBench loaders
25
+ eval/
26
+ benchmark.py # Unified evaluation pipeline
27
+ metrics.py # Accuracy, FLOPs, latency, KV cache metrics
28
+ visualize.py # Publication figures
29
+ configs/
30
+ dpa_7b.yaml # 7B model config
31
+ dpa_72b.yaml # 72B model config (Merlin 8xH100)
32
+ scripts/
33
+ run_baseline.sh # Run all baselines
34
+ run_dpa.sh # Run DPA experiments
35
+ run_ablation.sh # Ablation studies
36
+ paper/
37
+ main.tex # NeurIPS 2026 LaTeX
38
+ results/ # Experiment outputs
39
+ figures/ # Generated plots
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ```bash
45
+ # 1. Install deps
46
+ pip install -r requirements.txt
47
+
48
+ # 2. Run trajectory analysis (no GPU needed)
49
+ python src/data/decision_points.py
50
+
51
+ # 3. Run attention simulation (CPU/MPS OK)
52
+ python src/eval/benchmark.py --mode simulate
53
+
54
+ # 4. Run full training (8xH100 on Merlin)
55
+ bash scripts/run_dpa.sh
56
+ ```
dpa-project/configs/dpa_sim.yaml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Simulation config (CPU/MPS, no training)
2
+ model:
3
+ vocab_size: 32000
4
+ hidden_size: 512
5
+ num_layers: 6
6
+ num_heads: 8
7
+ max_seq_len: 1024
8
+
9
+ experiment:
10
+ mode: simulate
11
+ batch_size: 4
12
+ ratios: [0.05, 0.10, 0.15, 0.25, 0.50]
13
+ baselines: [full_transformer, pure_linear, uniform_hybrid]
14
+
15
+ output_dir: results/simulation
dpa-project/configs/dpa_train_7b.yaml ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Training config for 7B model on 8xH100 (Merlin)
2
+ model:
3
+ base_model: "Qwen/Qwen2.5-7B"
4
+ router_type: learned
5
+ target_ratio: 0.15
6
+ lora:
7
+ r: 16
8
+ alpha: 32
9
+ target_modules: ["q_proj", "k_proj", "v_proj", "o_proj"]
10
+
11
+ training:
12
+ num_epochs: 3
13
+ batch_size: 4
14
+ gradient_accumulation: 4
15
+ learning_rate: 2.0e-5
16
+ router_lr: 1.0e-4
17
+ warmup_ratio: 0.05
18
+ weight_decay: 0.01
19
+ max_seq_len: 4096
20
+ bf16: true
21
+ deepspeed: configs/ds_zero2.json
22
+
23
+ data:
24
+ train: data/agent_trajectories.json
25
+ eval_datasets: [hotpotqa, gsm8k]
26
+
27
+ eval:
28
+ eval_steps: 200
29
+ save_steps: 500
30
+ ratios_to_test: [0.05, 0.10, 0.15, 0.20, 0.25]
31
+
32
+ output_dir: results/dpa_7b
33
+ wandb_project: decision-point-attention
dpa-project/data/agent_trajectories.json ADDED
The diff for this file is too large to render. See raw diff
 
dpa-project/requirements.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch>=2.1.0
2
+ transformers>=4.40.0
3
+ datasets>=2.19.0
4
+ accelerate>=0.30.0
5
+ peft>=0.11.0
6
+ mamba-ssm>=2.0.0
7
+ causal-conv1d>=1.2.0
8
+ triton>=2.3.0
9
+ flash-attn>=2.5.0
10
+ einops>=0.7.0
11
+ wandb>=0.17.0
12
+ matplotlib>=3.8.0
13
+ seaborn>=0.13.0
14
+ pandas>=2.0.0
15
+ numpy>=1.24.0
16
+ scipy>=1.11.0
17
+ tqdm>=4.65.0
18
+ pyyaml>=6.0
19
+ jsonlines>=4.0.0
20
+ tiktoken>=0.7.0
21
+ fvcore>=0.1.5
dpa-project/results/simulation_results.json ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "model_type": "full_transformer",
4
+ "accuracy": 0.0,
5
+ "perplexity": 38904.5234375,
6
+ "decision_ratio": 1.0,
7
+ "flops_ratio": 1.0,
8
+ "latency_ms": 1096.70090675354,
9
+ "kv_cache_mb": 0,
10
+ "num_params": 39328768
11
+ },
12
+ {
13
+ "model_type": "pure_linear",
14
+ "accuracy": 0.0,
15
+ "perplexity": 38133.7421875,
16
+ "decision_ratio": 0.0,
17
+ "flops_ratio": 0.125,
18
+ "latency_ms": 451.3728618621826,
19
+ "kv_cache_mb": 0,
20
+ "num_params": 39328768
21
+ },
22
+ {
23
+ "model_type": "uniform_hybrid",
24
+ "accuracy": 0.0,
25
+ "perplexity": 38856.0234375,
26
+ "decision_ratio": 0.25,
27
+ "flops_ratio": 0.2708333333333333,
28
+ "latency_ms": 3.0851364135742188,
29
+ "kv_cache_mb": 0,
30
+ "num_params": 39328768
31
+ },
32
+ {
33
+ "model_type": "dpa_r5%",
34
+ "accuracy": 0.0,
35
+ "perplexity": 37879.0078125,
36
+ "decision_ratio": 0.5183919270833334,
37
+ "flops_ratio": 0.17499999999999996,
38
+ "latency_ms": 856.7311763763428,
39
+ "kv_cache_mb": 0,
40
+ "num_params": 47590924
41
+ },
42
+ {
43
+ "model_type": "dpa_r10%",
44
+ "accuracy": 0.0,
45
+ "perplexity": 36798.87890625,
46
+ "decision_ratio": 0.5647786458333334,
47
+ "flops_ratio": 0.225,
48
+ "latency_ms": 38.178205490112305,
49
+ "kv_cache_mb": 0,
50
+ "num_params": 47590924
51
+ },
52
+ {
53
+ "model_type": "dpa_r15%",
54
+ "accuracy": 0.0,
55
+ "perplexity": 37978.19140625,
56
+ "decision_ratio": 0.5673828125,
57
+ "flops_ratio": 0.275,
58
+ "latency_ms": 36.331892013549805,
59
+ "kv_cache_mb": 0,
60
+ "num_params": 47590924
61
+ },
62
+ {
63
+ "model_type": "dpa_r25%",
64
+ "accuracy": 0.0,
65
+ "perplexity": 38174.203125,
66
+ "decision_ratio": 0.5888671875,
67
+ "flops_ratio": 0.375,
68
+ "latency_ms": 35.56203842163086,
69
+ "kv_cache_mb": 0,
70
+ "num_params": 47590924
71
+ },
72
+ {
73
+ "model_type": "dpa_r50%",
74
+ "accuracy": 0.0,
75
+ "perplexity": 37756.52734375,
76
+ "decision_ratio": 0.4591471354166667,
77
+ "flops_ratio": 0.625,
78
+ "latency_ms": 36.00716590881348,
79
+ "kv_cache_mb": 0,
80
+ "num_params": 47590924
81
+ },
82
+ {
83
+ "model_type": "dpa_fixed_r5%",
84
+ "accuracy": 0.0,
85
+ "perplexity": 37851.92578125,
86
+ "decision_ratio": 0.048828125,
87
+ "flops_ratio": 0.17499999999999996,
88
+ "latency_ms": 2232.9022884368896,
89
+ "kv_cache_mb": 0,
90
+ "num_params": 47196160
91
+ },
92
+ {
93
+ "model_type": "dpa_fixed_r10%",
94
+ "accuracy": 0.0,
95
+ "perplexity": 37950.85546875,
96
+ "decision_ratio": 0.099609375,
97
+ "flops_ratio": 0.225,
98
+ "latency_ms": 67.90804862976074,
99
+ "kv_cache_mb": 0,
100
+ "num_params": 47196160
101
+ },
102
+ {
103
+ "model_type": "dpa_fixed_r15%",
104
+ "accuracy": 0.0,
105
+ "perplexity": 38790.15625,
106
+ "decision_ratio": 0.1484375,
107
+ "flops_ratio": 0.275,
108
+ "latency_ms": 57.621002197265625,
109
+ "kv_cache_mb": 0,
110
+ "num_params": 47196160
111
+ },
112
+ {
113
+ "model_type": "dpa_fixed_r25%",
114
+ "accuracy": 0.0,
115
+ "perplexity": 39025.77734375,
116
+ "decision_ratio": 0.25,
117
+ "flops_ratio": 0.375,
118
+ "latency_ms": 61.93709373474121,
119
+ "kv_cache_mb": 0,
120
+ "num_params": 47196160
121
+ },
122
+ {
123
+ "model_type": "dpa_fixed_r50%",
124
+ "accuracy": 0.0,
125
+ "perplexity": 36291.3359375,
126
+ "decision_ratio": 0.5,
127
+ "flops_ratio": 0.625,
128
+ "latency_ms": 60.697078704833984,
129
+ "kv_cache_mb": 0,
130
+ "num_params": 47196160
131
+ }
132
+ ]
dpa-project/scripts/run_dpa.sh ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Run full DPA training on Merlin (8xH100)
3
+ # Usage: ssh merlin "cd ~/decision-point-attention && bash scripts/run_dpa.sh"
4
+ set -e
5
+
6
+ cd "$(dirname "$0")/.."
7
+
8
+ export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
9
+ export WANDB_PROJECT=decision-point-attention
10
+
11
+ echo "=== DPA Training on 8xH100 ==="
12
+ echo "Model: Qwen2.5-7B + LoRA + DPA Router"
13
+ echo "Target NeurIPS 2026 (DDL: May 6)"
14
+
15
+ # Step 1: Generate data
16
+ python3 src/data/agent_trajectory.py
17
+
18
+ # Step 2: Train with DeepSpeed ZeRO-2
19
+ torchrun --nproc_per_node=8 \
20
+ src/train.py \
21
+ --config configs/dpa_train_7b.yaml
22
+
23
+ # Step 3: Evaluate on benchmarks
24
+ python3 src/eval/benchmark.py \
25
+ --mode finetune \
26
+ --output-dir results/dpa_7b
27
+
28
+ # Step 4: Generate figures
29
+ python3 src/eval/visualize.py results/dpa_7b/simulation_results.json
30
+
31
+ echo "=== Training complete! ==="
dpa-project/scripts/run_simulation.sh ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Run DPA simulation on local machine (CPU/MPS, no GPU needed)
3
+ set -e
4
+
5
+ cd "$(dirname "$0")/.."
6
+
7
+ echo "=== Generating agent trajectory dataset ==="
8
+ python3 src/data/agent_trajectory.py
9
+
10
+ echo ""
11
+ echo "=== Running DPA simulation benchmark ==="
12
+ python3 src/eval/benchmark.py \
13
+ --mode simulate \
14
+ --hidden-size 512 \
15
+ --num-layers 6 \
16
+ --num-heads 8 \
17
+ --seq-len 1024 \
18
+ --batch-size 4 \
19
+ --output-dir results/simulation
20
+
21
+ echo ""
22
+ echo "=== Generating figures ==="
23
+ python3 src/eval/visualize.py results/simulation/simulation_results.json
24
+ python3 src/eval/visualize.py data/agent_trajectories.json # trajectory analysis
25
+
26
+ echo ""
27
+ echo "=== Done! Check results/ and figures/ ==="
dpa-project/src/__init__.py ADDED
File without changes
dpa-project/src/data/__init__.py ADDED
File without changes
dpa-project/src/data/agent_trajectory.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Agent Trajectory Generator & Decision Point Labeling
3
+
4
+ Generates synthetic agent trajectories with labeled decision points.
5
+ Also loads real trajectories from AgentBench / ToolBench.
6
+ """
7
+
8
+ import json
9
+ import random
10
+ from pathlib import Path
11
+ from dataclasses import dataclass, field
12
+ from typing import List, Optional
13
+
14
+
15
+ @dataclass
16
+ class TrajectoryStep:
17
+ role: str # "thought", "action", "observation", "error", "plan"
18
+ content: str
19
+ is_decision_point: bool = False
20
+ tokens: List[str] = field(default_factory=list)
21
+
22
+
23
+ @dataclass
24
+ class AgentTrajectory:
25
+ task: str
26
+ steps: List[TrajectoryStep]
27
+ total_tokens: int = 0
28
+ decision_point_tokens: int = 0
29
+
30
+ @property
31
+ def decision_ratio(self):
32
+ if self.total_tokens == 0:
33
+ return 0
34
+ return self.decision_point_tokens / self.total_tokens
35
+
36
+
37
+ # Decision point patterns
38
+ DECISION_PATTERNS = {
39
+ "tool_call": [
40
+ "I need to call", "Let me use", "Action:", "Tool:", "API call:",
41
+ "function_call", "execute(", "search(", "query(",
42
+ ],
43
+ "plan_revision": [
44
+ "Let me reconsider", "Actually,", "Wait,", "On second thought",
45
+ "I should change", "New plan:", "Revised approach:", "Instead,",
46
+ ],
47
+ "error_recovery": [
48
+ "Error:", "Failed:", "Exception:", "Traceback", "retry",
49
+ "That didn't work", "Let me try another", "fallback",
50
+ ],
51
+ "state_update": [
52
+ "Result:", "Output:", "The answer is", "Found:",
53
+ "Updated:", "Status:", "Observation:",
54
+ ],
55
+ }
56
+
57
+ # Routine patterns (NOT decision points)
58
+ ROUTINE_PATTERNS = [
59
+ "Let me think about this...",
60
+ "Looking at the data...",
61
+ "Based on the context...",
62
+ "The document mentions...",
63
+ "According to the passage...",
64
+ "Step {i}: ",
65
+ "Processing...",
66
+ "Reading the input...",
67
+ ]
68
+
69
+
70
+ def generate_synthetic_trajectory(
71
+ num_steps=20, decision_ratio=0.15, task="multi-hop QA"
72
+ ) -> AgentTrajectory:
73
+ """Generate a synthetic agent trajectory with labeled decision points."""
74
+ steps = []
75
+ total_toks = 0
76
+ dp_toks = 0
77
+
78
+ for i in range(num_steps):
79
+ is_dp = random.random() < decision_ratio
80
+
81
+ if is_dp:
82
+ # Pick a decision point type
83
+ dp_type = random.choice(list(DECISION_PATTERNS.keys()))
84
+ pattern = random.choice(DECISION_PATTERNS[dp_type])
85
+
86
+ if dp_type == "tool_call":
87
+ content = f"{pattern} search_api('query about {task} step {i}')"
88
+ role = "action"
89
+ elif dp_type == "plan_revision":
90
+ content = f"{pattern} the approach for step {i} needs adjustment."
91
+ role = "thought"
92
+ elif dp_type == "error_recovery":
93
+ content = f"{pattern} step {i} encountered an issue. Trying alternative."
94
+ role = "error"
95
+ else:
96
+ content = f"{pattern} step {i} yielded new information for {task}."
97
+ role = "observation"
98
+ else:
99
+ pattern = random.choice(ROUTINE_PATTERNS).format(i=i)
100
+ content = f"{pattern} analyzing information related to {task}."
101
+ role = "thought"
102
+
103
+ tokens = content.split()
104
+ total_toks += len(tokens)
105
+ if is_dp:
106
+ dp_toks += len(tokens)
107
+
108
+ steps.append(TrajectoryStep(
109
+ role=role, content=content,
110
+ is_decision_point=is_dp, tokens=tokens,
111
+ ))
112
+
113
+ return AgentTrajectory(
114
+ task=task, steps=steps,
115
+ total_tokens=total_toks, decision_point_tokens=dp_toks,
116
+ )
117
+
118
+
119
+ def generate_dataset(num_trajectories=1000, save_path=None):
120
+ """Generate a dataset of labeled agent trajectories."""
121
+ tasks = [
122
+ "multi-hop question answering",
123
+ "code debugging with tool use",
124
+ "web navigation and form filling",
125
+ "API orchestration pipeline",
126
+ "database query planning",
127
+ "research paper analysis",
128
+ ]
129
+
130
+ trajectories = []
131
+ for i in range(num_trajectories):
132
+ task = random.choice(tasks)
133
+ num_steps = random.randint(10, 40)
134
+ ratio = random.uniform(0.08, 0.25)
135
+ traj = generate_synthetic_trajectory(num_steps, ratio, task)
136
+ trajectories.append(traj)
137
+
138
+ # Statistics
139
+ ratios = [t.decision_ratio for t in trajectories]
140
+ avg_ratio = sum(ratios) / len(ratios)
141
+ print(f"Generated {len(trajectories)} trajectories")
142
+ print(f"Avg decision ratio: {avg_ratio:.2%}")
143
+ print(f"Min/Max ratio: {min(ratios):.2%} / {max(ratios):.2%}")
144
+ print(f"Avg steps: {sum(len(t.steps) for t in trajectories) / len(trajectories):.1f}")
145
+
146
+ if save_path:
147
+ save_path = Path(save_path)
148
+ save_path.parent.mkdir(parents=True, exist_ok=True)
149
+ data = []
150
+ for t in trajectories:
151
+ data.append({
152
+ "task": t.task,
153
+ "total_tokens": t.total_tokens,
154
+ "decision_point_tokens": t.decision_point_tokens,
155
+ "decision_ratio": t.decision_ratio,
156
+ "steps": [
157
+ {"role": s.role, "content": s.content,
158
+ "is_decision_point": s.is_decision_point}
159
+ for s in t.steps
160
+ ],
161
+ })
162
+ with open(save_path, "w") as f:
163
+ json.dump(data, f, indent=2)
164
+ print(f"Saved to {save_path}")
165
+
166
+ return trajectories
167
+
168
+
169
+ def label_decision_points(text: str) -> List[bool]:
170
+ """Label each token in text as decision point or not."""
171
+ tokens = text.split()
172
+ labels = []
173
+ for i, tok in enumerate(tokens):
174
+ is_dp = False
175
+ context = " ".join(tokens[max(0, i - 3):i + 3])
176
+ for patterns in DECISION_PATTERNS.values():
177
+ for p in patterns:
178
+ if p.lower() in context.lower():
179
+ is_dp = True
180
+ break
181
+ if is_dp:
182
+ break
183
+ labels.append(is_dp)
184
+ return labels
185
+
186
+
187
+ if __name__ == "__main__":
188
+ # Generate and save dataset
189
+ trajectories = generate_dataset(
190
+ num_trajectories=2000,
191
+ save_path="data/agent_trajectories.json",
192
+ )
dpa-project/src/data/datasets.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Dataset loaders for DPA experiments.
3
+ Loads HotpotQA, GSM8K, ToolBench for multi-step reasoning evaluation.
4
+ """
5
+
6
+ from datasets import load_dataset
7
+ from torch.utils.data import Dataset
8
+ import torch
9
+ import json
10
+
11
+
12
+ class MultiStepReasoningDataset(Dataset):
13
+ """Unified dataset for multi-step reasoning tasks."""
14
+
15
+ def __init__(self, dataset_name="hotpotqa", split="validation",
16
+ tokenizer=None, max_length=2048, max_samples=None):
17
+ self.tokenizer = tokenizer
18
+ self.max_length = max_length
19
+ self.dataset_name = dataset_name
20
+
21
+ if dataset_name == "hotpotqa":
22
+ ds = load_dataset("hotpot_qa", "distractor", split=split)
23
+ self.data = [self._process_hotpotqa(item) for item in ds]
24
+ elif dataset_name == "gsm8k":
25
+ ds = load_dataset("openai/gsm8k", "main", split=split)
26
+ self.data = [self._process_gsm8k(item) for item in ds]
27
+ elif dataset_name == "toolbench":
28
+ # ToolBench needs manual download
29
+ self.data = self._load_toolbench(split)
30
+ else:
31
+ raise ValueError(f"Unknown dataset: {dataset_name}")
32
+
33
+ if max_samples:
34
+ self.data = self.data[:max_samples]
35
+
36
+ print(f"Loaded {len(self.data)} samples from {dataset_name}/{split}")
37
+
38
+ def _process_hotpotqa(self, item):
39
+ context = " ".join([
40
+ " ".join(sents) for sents in item["context"]["sentences"]
41
+ ])
42
+ return {
43
+ "input": f"Answer the question based on the context.\n\nContext: {context}\n\nQuestion: {item['question']}\n\nAnswer:",
44
+ "target": item["answer"],
45
+ "type": item["type"], # "bridge" or "comparison"
46
+ "num_hops": 2 if item["type"] == "bridge" else 1,
47
+ }
48
+
49
+ def _process_gsm8k(self, item):
50
+ return {
51
+ "input": f"Solve step by step:\n\n{item['question']}\n\nSolution:",
52
+ "target": item["answer"],
53
+ "type": "math_reasoning",
54
+ "num_hops": item["answer"].count("\n") + 1,
55
+ }
56
+
57
+ def _load_toolbench(self, split):
58
+ # Placeholder — ToolBench needs separate download
59
+ return []
60
+
61
+ def __len__(self):
62
+ return len(self.data)
63
+
64
+ def __getitem__(self, idx):
65
+ item = self.data[idx]
66
+ if self.tokenizer:
67
+ encoding = self.tokenizer(
68
+ item["input"], max_length=self.max_length,
69
+ truncation=True, padding="max_length", return_tensors="pt",
70
+ )
71
+ return {
72
+ "input_ids": encoding["input_ids"].squeeze(0),
73
+ "attention_mask": encoding["attention_mask"].squeeze(0),
74
+ "target": item["target"],
75
+ "num_hops": item["num_hops"],
76
+ }
77
+ return item
78
+
79
+
80
+ def get_dataset(name, split="validation", tokenizer=None, max_samples=500):
81
+ """Convenience function to load a dataset."""
82
+ return MultiStepReasoningDataset(
83
+ dataset_name=name, split=split,
84
+ tokenizer=tokenizer, max_samples=max_samples,
85
+ )
dpa-project/src/eval/__init__.py ADDED
File without changes
dpa-project/src/eval/benchmark.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Unified benchmark for DPA experiments.
3
+
4
+ Modes:
5
+ 1. simulate — simulate DPA with attention masking on pretrained models (CPU/MPS OK)
6
+ 2. train — train DPA from scratch (needs GPU)
7
+ 3. finetune — finetune pretrained model with DPA wrapper (8xH100)
8
+ """
9
+
10
+ import argparse
11
+ import json
12
+ import time
13
+ import torch
14
+ import torch.nn as nn
15
+ from pathlib import Path
16
+ from dataclasses import dataclass
17
+
18
+ import sys
19
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
20
+
21
+ from src.models.baselines import build_model
22
+ from src.models.dpa_model import DPATransformer
23
+ from src.eval.metrics import compute_flops, compute_metrics
24
+
25
+
26
+ @dataclass
27
+ class BenchmarkResult:
28
+ model_type: str
29
+ accuracy: float
30
+ perplexity: float
31
+ decision_ratio: float
32
+ flops_ratio: float # relative to full transformer
33
+ latency_ms: float
34
+ kv_cache_mb: float
35
+ num_params: int
36
+
37
+
38
+ def count_params(model):
39
+ return sum(p.numel() for p in model.parameters())
40
+
41
+
42
+ def estimate_flops(model_type, seq_len, hidden_size, num_layers, num_heads, decision_ratio=1.0):
43
+ """Estimate FLOPs for different model types."""
44
+ # Full attention FLOPs per layer: 4 * seq_len^2 * hidden_size (QKV + output)
45
+ full_attn_flops = 4 * seq_len * seq_len * hidden_size
46
+ # Linear attention FLOPs per layer: 4 * seq_len * hidden_size * (hidden_size / num_heads)
47
+ linear_attn_flops = 4 * seq_len * hidden_size * (hidden_size // num_heads)
48
+
49
+ if model_type == "full_transformer":
50
+ return full_attn_flops * num_layers
51
+ elif model_type == "pure_linear":
52
+ return linear_attn_flops * num_layers
53
+ elif model_type == "uniform_hybrid":
54
+ full_layers = num_layers // 4
55
+ linear_layers = num_layers - full_layers
56
+ return full_attn_flops * full_layers + linear_attn_flops * linear_layers
57
+ elif model_type.startswith("dpa"):
58
+ # DPA: decision_ratio of tokens use full attention
59
+ dpa_flops_per_layer = (
60
+ linear_attn_flops + # linear for all tokens
61
+ decision_ratio * full_attn_flops # full only for decision points
62
+ )
63
+ return dpa_flops_per_layer * num_layers
64
+ return 0
65
+
66
+
67
+ def run_simulation(args):
68
+ """Run DPA simulation experiment (no training, attention masking only)."""
69
+ print("=" * 60)
70
+ print("DPA Simulation Experiment")
71
+ print("=" * 60)
72
+
73
+ device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
74
+ print(f"Device: {device}")
75
+
76
+ model_types = ["full_transformer", "pure_linear", "uniform_hybrid", "dpa", "dpa_fixed"]
77
+ ratios = [0.05, 0.10, 0.15, 0.25, 0.50]
78
+
79
+ results = []
80
+
81
+ # Common config
82
+ cfg = dict(
83
+ vocab_size=32000, hidden_size=args.hidden_size,
84
+ num_layers=args.num_layers, num_heads=args.num_heads,
85
+ max_seq_len=args.seq_len,
86
+ )
87
+
88
+ # Generate random data for simulation
89
+ batch = torch.randint(0, cfg["vocab_size"], (args.batch_size, args.seq_len), device=device)
90
+ labels = torch.randint(0, cfg["vocab_size"], (args.batch_size, args.seq_len), device=device)
91
+
92
+ for model_type in model_types:
93
+ if model_type in ("dpa", "dpa_fixed"):
94
+ for ratio in ratios:
95
+ model = build_model(model_type, target_ratio=ratio, **cfg).to(device)
96
+ model.eval()
97
+
98
+ with torch.no_grad():
99
+ t0 = time.time()
100
+ outputs = model(batch, labels=labels)
101
+ latency = (time.time() - t0) * 1000
102
+
103
+ flops = estimate_flops(
104
+ model_type, args.seq_len, cfg["hidden_size"],
105
+ cfg["num_layers"], cfg["num_heads"], ratio,
106
+ )
107
+ full_flops = estimate_flops(
108
+ "full_transformer", args.seq_len, cfg["hidden_size"],
109
+ cfg["num_layers"], cfg["num_heads"],
110
+ )
111
+
112
+ result = BenchmarkResult(
113
+ model_type=f"{model_type}_r{ratio:.0%}",
114
+ accuracy=0.0, # filled in real eval
115
+ perplexity=torch.exp(outputs["loss"]).item() if outputs["loss"] else 0,
116
+ decision_ratio=outputs.get("avg_decision_ratio", ratio),
117
+ flops_ratio=flops / full_flops,
118
+ latency_ms=latency,
119
+ kv_cache_mb=0,
120
+ num_params=count_params(model),
121
+ )
122
+ results.append(result)
123
+ print(f" {result.model_type}: ppl={result.perplexity:.2f}, "
124
+ f"flops_ratio={result.flops_ratio:.2%}, "
125
+ f"latency={result.latency_ms:.1f}ms, "
126
+ f"params={result.num_params:,}")
127
+ del model
128
+ else:
129
+ model = build_model(model_type, **cfg).to(device)
130
+ model.eval()
131
+
132
+ with torch.no_grad():
133
+ t0 = time.time()
134
+ outputs = model(batch, labels=labels)
135
+ latency = (time.time() - t0) * 1000
136
+
137
+ dr = outputs.get("avg_decision_ratio", 1.0 if model_type == "full_transformer" else 0.0)
138
+ flops = estimate_flops(
139
+ model_type, args.seq_len, cfg["hidden_size"],
140
+ cfg["num_layers"], cfg["num_heads"], dr,
141
+ )
142
+ full_flops = estimate_flops(
143
+ "full_transformer", args.seq_len, cfg["hidden_size"],
144
+ cfg["num_layers"], cfg["num_heads"],
145
+ )
146
+
147
+ result = BenchmarkResult(
148
+ model_type=model_type,
149
+ accuracy=0.0,
150
+ perplexity=torch.exp(outputs["loss"]).item() if outputs["loss"] else 0,
151
+ decision_ratio=dr,
152
+ flops_ratio=flops / full_flops,
153
+ latency_ms=latency,
154
+ kv_cache_mb=0,
155
+ num_params=count_params(model),
156
+ )
157
+ results.append(result)
158
+ print(f" {result.model_type}: ppl={result.perplexity:.2f}, "
159
+ f"flops_ratio={result.flops_ratio:.2%}, "
160
+ f"latency={result.latency_ms:.1f}ms, "
161
+ f"params={result.num_params:,}")
162
+ del model
163
+
164
+ # Save results
165
+ output_path = Path(args.output_dir) / "simulation_results.json"
166
+ output_path.parent.mkdir(parents=True, exist_ok=True)
167
+ with open(output_path, "w") as f:
168
+ json.dump([vars(r) for r in results], f, indent=2)
169
+ print(f"\nResults saved to {output_path}")
170
+
171
+ return results
172
+
173
+
174
+ def main():
175
+ parser = argparse.ArgumentParser(description="DPA Benchmark")
176
+ parser.add_argument("--mode", choices=["simulate", "train", "finetune"], default="simulate")
177
+ parser.add_argument("--hidden-size", type=int, default=512)
178
+ parser.add_argument("--num-layers", type=int, default=6)
179
+ parser.add_argument("--num-heads", type=int, default=8)
180
+ parser.add_argument("--seq-len", type=int, default=1024)
181
+ parser.add_argument("--batch-size", type=int, default=4)
182
+ parser.add_argument("--output-dir", type=str, default="results")
183
+ args = parser.parse_args()
184
+
185
+ if args.mode == "simulate":
186
+ run_simulation(args)
187
+ elif args.mode == "train":
188
+ print("Training mode — requires GPU. Use scripts/run_dpa.sh on Merlin.")
189
+ elif args.mode == "finetune":
190
+ print("Finetune mode — requires 8xH100. Use scripts/run_dpa.sh on Merlin.")
191
+
192
+
193
+ if __name__ == "__main__":
194
+ main()
dpa-project/src/eval/metrics.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Metrics for DPA evaluation."""
2
+
3
+ import torch
4
+ import time
5
+
6
+
7
+ def compute_flops(model, input_ids, method="estimate"):
8
+ """Compute FLOPs for a forward pass."""
9
+ try:
10
+ from fvcore.nn import FlopCountAnalysis
11
+ flops = FlopCountAnalysis(model, input_ids)
12
+ return flops.total()
13
+ except ImportError:
14
+ # Rough estimate
15
+ num_params = sum(p.numel() for p in model.parameters())
16
+ seq_len = input_ids.shape[1]
17
+ return 2 * num_params * seq_len # ~2N per token
18
+
19
+
20
+ def compute_latency(model, input_ids, num_runs=10, warmup=3):
21
+ """Measure inference latency."""
22
+ device = next(model.parameters()).device
23
+
24
+ # Warmup
25
+ with torch.no_grad():
26
+ for _ in range(warmup):
27
+ model(input_ids)
28
+ if device.type == "cuda":
29
+ torch.cuda.synchronize()
30
+
31
+ # Measure
32
+ times = []
33
+ with torch.no_grad():
34
+ for _ in range(num_runs):
35
+ if device.type == "cuda":
36
+ torch.cuda.synchronize()
37
+ t0 = time.perf_counter()
38
+ model(input_ids)
39
+ if device.type == "cuda":
40
+ torch.cuda.synchronize()
41
+ times.append((time.perf_counter() - t0) * 1000)
42
+
43
+ return {
44
+ "mean_ms": sum(times) / len(times),
45
+ "std_ms": (sum((t - sum(times)/len(times))**2 for t in times) / len(times)) ** 0.5,
46
+ "min_ms": min(times),
47
+ "max_ms": max(times),
48
+ }
49
+
50
+
51
+ def compute_kv_cache_size(model, seq_len, batch_size=1, dtype_bytes=2):
52
+ """Estimate KV cache memory in MB."""
53
+ config = getattr(model, "config", None)
54
+ if config:
55
+ num_layers = getattr(config, "num_hidden_layers", 6)
56
+ num_heads = getattr(config, "num_attention_heads", 8)
57
+ head_dim = getattr(config, "hidden_size", 512) // num_heads
58
+ else:
59
+ num_layers = 6
60
+ num_heads = 8
61
+ head_dim = 64
62
+
63
+ # KV cache: 2 (K+V) * layers * batch * heads * seq_len * head_dim * dtype
64
+ total_bytes = 2 * num_layers * batch_size * num_heads * seq_len * head_dim * dtype_bytes
65
+ return total_bytes / (1024 * 1024) # MB
66
+
67
+
68
+ def compute_metrics(model, dataloader, tokenizer=None, max_samples=500):
69
+ """Compute accuracy and perplexity on a dataset."""
70
+ model.eval()
71
+ device = next(model.parameters()).device
72
+
73
+ total_loss = 0
74
+ total_tokens = 0
75
+ total_correct = 0
76
+ total_samples = 0
77
+ decision_ratios = []
78
+
79
+ with torch.no_grad():
80
+ for i, batch in enumerate(dataloader):
81
+ if i >= max_samples:
82
+ break
83
+
84
+ input_ids = batch["input_ids"].to(device)
85
+ labels = input_ids.clone()
86
+ labels[:, :-1] = input_ids[:, 1:]
87
+ labels[:, -1] = -100
88
+
89
+ outputs = model(input_ids, labels=labels)
90
+
91
+ if outputs.get("loss") is not None:
92
+ total_loss += outputs["loss"].item() * input_ids.shape[1]
93
+ total_tokens += input_ids.shape[1]
94
+
95
+ if "avg_decision_ratio" in outputs:
96
+ decision_ratios.append(outputs["avg_decision_ratio"])
97
+
98
+ total_samples += 1
99
+
100
+ avg_loss = total_loss / max(total_tokens, 1)
101
+ perplexity = torch.exp(torch.tensor(avg_loss)).item()
102
+
103
+ return {
104
+ "perplexity": perplexity,
105
+ "avg_loss": avg_loss,
106
+ "num_samples": total_samples,
107
+ "avg_decision_ratio": sum(decision_ratios) / len(decision_ratios) if decision_ratios else 0,
108
+ }
dpa-project/src/eval/visualize.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Publication-quality figures for DPA paper."""
2
+
3
+ import json
4
+ import matplotlib.pyplot as plt
5
+ import matplotlib
6
+ import numpy as np
7
+ from pathlib import Path
8
+
9
+ matplotlib.rcParams.update({
10
+ "font.size": 12, "font.family": "serif",
11
+ "axes.labelsize": 14, "axes.titlesize": 15,
12
+ "xtick.labelsize": 11, "ytick.labelsize": 11,
13
+ "legend.fontsize": 10, "figure.dpi": 150,
14
+ })
15
+
16
+ COLORS = {
17
+ "full_transformer": "#2196F3",
18
+ "pure_linear": "#FF9800",
19
+ "uniform_hybrid": "#4CAF50",
20
+ "dpa": "#E91E63",
21
+ "dpa_fixed": "#9C27B0",
22
+ }
23
+
24
+
25
+ def plot_accuracy_vs_flops(results_path, save_path="figures/accuracy_vs_flops.pdf"):
26
+ """Main figure: accuracy vs compute tradeoff."""
27
+ with open(results_path) as f:
28
+ results = json.load(f)
29
+
30
+ fig, ax = plt.subplots(1, 1, figsize=(8, 5))
31
+
32
+ for r in results:
33
+ name = r["model_type"]
34
+ base = name.split("_r")[0] if "_r" in name else name
35
+ color = COLORS.get(base, "#666")
36
+ marker = "★" if base == "dpa" else "o"
37
+ size = 120 if base == "dpa" else 60
38
+
39
+ ax.scatter(r["flops_ratio"], r["perplexity"],
40
+ c=color, s=size, zorder=5,
41
+ label=name if base not in [n.split("_r")[0] for n in [rr["model_type"] for rr in results[:results.index(r)]]] else "")
42
+
43
+ # Connect DPA points
44
+ dpa_results = [r for r in results if r["model_type"].startswith("dpa_r")]
45
+ if dpa_results:
46
+ xs = [r["flops_ratio"] for r in sorted(dpa_results, key=lambda x: x["flops_ratio"])]
47
+ ys = [r["perplexity"] for r in sorted(dpa_results, key=lambda x: x["flops_ratio"])]
48
+ ax.plot(xs, ys, c=COLORS["dpa"], linewidth=2, alpha=0.5, linestyle="--")
49
+
50
+ ax.set_xlabel("FLOPs (relative to Full Transformer)")
51
+ ax.set_ylabel("Perplexity ↓")
52
+ ax.set_title("Decision Point Attention: Accuracy vs Compute")
53
+ ax.legend(loc="upper right")
54
+ ax.grid(True, alpha=0.3)
55
+
56
+ Path(save_path).parent.mkdir(parents=True, exist_ok=True)
57
+ fig.tight_layout()
58
+ fig.savefig(save_path, bbox_inches="tight")
59
+ print(f"Saved {save_path}")
60
+ plt.close()
61
+
62
+
63
+ def plot_decision_ratio_ablation(results_path, save_path="figures/ratio_ablation.pdf"):
64
+ """Ablation: effect of decision point ratio."""
65
+ with open(results_path) as f:
66
+ results = json.load(f)
67
+
68
+ dpa_results = [r for r in results if "dpa" in r["model_type"] and "_r" in r["model_type"]]
69
+ if not dpa_results:
70
+ print("No DPA ratio results found")
71
+ return
72
+
73
+ ratios = [r["decision_ratio"] for r in dpa_results]
74
+ ppls = [r["perplexity"] for r in dpa_results]
75
+ flops = [r["flops_ratio"] for r in dpa_results]
76
+
77
+ # Get baselines
78
+ full_ppl = next((r["perplexity"] for r in results if r["model_type"] == "full_transformer"), None)
79
+ linear_ppl = next((r["perplexity"] for r in results if r["model_type"] == "pure_linear"), None)
80
+
81
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
82
+
83
+ # Left: perplexity vs ratio
84
+ ax1.plot(ratios, ppls, "o-", color=COLORS["dpa"], linewidth=2, markersize=8, label="DPA")
85
+ if full_ppl:
86
+ ax1.axhline(full_ppl, color=COLORS["full_transformer"], linestyle="--", label=f"Full Transformer ({full_ppl:.1f})")
87
+ if linear_ppl:
88
+ ax1.axhline(linear_ppl, color=COLORS["pure_linear"], linestyle="--", label=f"Pure Linear ({linear_ppl:.1f})")
89
+ ax1.set_xlabel("Decision Point Ratio")
90
+ ax1.set_ylabel("Perplexity ↓")
91
+ ax1.set_title("(a) Quality vs Decision Point Ratio")
92
+ ax1.legend()
93
+ ax1.grid(True, alpha=0.3)
94
+
95
+ # Right: FLOPs vs ratio
96
+ ax2.plot(ratios, flops, "s-", color=COLORS["dpa"], linewidth=2, markersize=8)
97
+ ax2.axhline(1.0, color=COLORS["full_transformer"], linestyle="--", label="Full Transformer (1.0x)")
98
+ ax2.set_xlabel("Decision Point Ratio")
99
+ ax2.set_ylabel("FLOPs (relative)")
100
+ ax2.set_title("(b) Compute Cost vs Decision Point Ratio")
101
+ ax2.legend()
102
+ ax2.grid(True, alpha=0.3)
103
+
104
+ fig.tight_layout()
105
+ fig.savefig(save_path, bbox_inches="tight")
106
+ print(f"Saved {save_path}")
107
+ plt.close()
108
+
109
+
110
+ def plot_trajectory_analysis(traj_path, save_path="figures/trajectory_analysis.pdf"):
111
+ """Visualize decision points in agent trajectories."""
112
+ with open(traj_path) as f:
113
+ trajectories = json.load(f)
114
+
115
+ ratios = [t["decision_ratio"] for t in trajectories]
116
+ step_types = {}
117
+ for t in trajectories:
118
+ for step in t["steps"]:
119
+ role = step["role"]
120
+ step_types.setdefault(role, {"dp": 0, "routine": 0})
121
+ if step["is_decision_point"]:
122
+ step_types[role]["dp"] += 1
123
+ else:
124
+ step_types[role]["routine"] += 1
125
+
126
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
127
+
128
+ # Left: distribution of decision ratios
129
+ ax1.hist(ratios, bins=30, color=COLORS["dpa"], alpha=0.7, edgecolor="white")
130
+ ax1.axvline(np.mean(ratios), color="red", linestyle="--", label=f"Mean: {np.mean(ratios):.1%}")
131
+ ax1.set_xlabel("Decision Point Ratio")
132
+ ax1.set_ylabel("Count")
133
+ ax1.set_title("(a) Distribution of Decision Ratios")
134
+ ax1.legend()
135
+
136
+ # Right: decision points by step type
137
+ roles = list(step_types.keys())
138
+ dp_counts = [step_types[r]["dp"] for r in roles]
139
+ routine_counts = [step_types[r]["routine"] for r in roles]
140
+
141
+ x = np.arange(len(roles))
142
+ ax2.bar(x - 0.2, dp_counts, 0.4, label="Decision Point", color=COLORS["dpa"])
143
+ ax2.bar(x + 0.2, routine_counts, 0.4, label="Routine", color="#ccc")
144
+ ax2.set_xticks(x)
145
+ ax2.set_xticklabels(roles, rotation=30)
146
+ ax2.set_ylabel("Count")
147
+ ax2.set_title("(b) Decision Points by Step Type")
148
+ ax2.legend()
149
+
150
+ fig.tight_layout()
151
+ fig.savefig(save_path, bbox_inches="tight")
152
+ print(f"Saved {save_path}")
153
+ plt.close()
154
+
155
+
156
+ if __name__ == "__main__":
157
+ import sys
158
+ if len(sys.argv) > 1:
159
+ plot_accuracy_vs_flops(sys.argv[1])
160
+ plot_decision_ratio_ablation(sys.argv[1])
dpa-project/src/models/__init__.py ADDED
File without changes
dpa-project/src/models/baselines.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Baseline models for comparison:
3
+ 1. FullTransformer — standard softmax attention (upper bound)
4
+ 2. PureLinearAttention — all linear attention (lower bound)
5
+ 3. UniformHybrid — every Nth layer is full attention (Jamba-style)
6
+ 4. DPA — our method (decision point routing)
7
+ """
8
+
9
+ from .dpa_model import DPATransformer, LinearAttention, FullAttention
10
+ import torch
11
+ import torch.nn as nn
12
+
13
+
14
+ def build_model(model_type, **kwargs):
15
+ """Factory function to build different model variants."""
16
+ defaults = dict(
17
+ vocab_size=32000, hidden_size=512, num_layers=6,
18
+ num_heads=8, max_seq_len=2048,
19
+ )
20
+ defaults.update(kwargs)
21
+
22
+ if model_type == "full_transformer":
23
+ return FullTransformerModel(**defaults)
24
+ elif model_type == "pure_linear":
25
+ return PureLinearModel(**defaults)
26
+ elif model_type == "uniform_hybrid":
27
+ return UniformHybridModel(**defaults)
28
+ elif model_type == "dpa":
29
+ return DPATransformer(router_type="learned", **defaults)
30
+ elif model_type == "dpa_fixed":
31
+ return DPATransformer(router_type="fixed", **defaults)
32
+ else:
33
+ raise ValueError(f"Unknown model type: {model_type}")
34
+
35
+
36
+ class FullTransformerModel(nn.Module):
37
+ """All layers use full softmax attention."""
38
+
39
+ def __init__(self, vocab_size, hidden_size, num_layers, num_heads, max_seq_len, **kw):
40
+ super().__init__()
41
+ self.embedding = nn.Embedding(vocab_size, hidden_size)
42
+ self.pos_embedding = nn.Embedding(max_seq_len, hidden_size)
43
+ self.layers = nn.ModuleList([
44
+ nn.ModuleDict({
45
+ "norm": nn.LayerNorm(hidden_size),
46
+ "attn": FullAttention(hidden_size, num_heads),
47
+ }) for _ in range(num_layers)
48
+ ])
49
+ self.norm = nn.LayerNorm(hidden_size)
50
+ self.output = nn.Linear(hidden_size, vocab_size, bias=False)
51
+
52
+ def forward(self, input_ids, attention_mask=None, labels=None):
53
+ B, L = input_ids.shape
54
+ pos = torch.arange(L, device=input_ids.device).unsqueeze(0)
55
+ x = self.embedding(input_ids) + self.pos_embedding(pos)
56
+
57
+ for layer in self.layers:
58
+ residual = x
59
+ x = layer["norm"](x)
60
+ x = residual + layer["attn"](x, attention_mask)
61
+
62
+ x = self.norm(x)
63
+ logits = self.output(x)
64
+ loss = None
65
+ if labels is not None:
66
+ loss = nn.functional.cross_entropy(
67
+ logits.view(-1, logits.size(-1)), labels.view(-1), ignore_index=-100)
68
+ return {"loss": loss, "logits": logits, "avg_decision_ratio": 1.0}
69
+
70
+
71
+ class PureLinearModel(nn.Module):
72
+ """All layers use linear attention only."""
73
+
74
+ def __init__(self, vocab_size, hidden_size, num_layers, num_heads, max_seq_len, **kw):
75
+ super().__init__()
76
+ self.embedding = nn.Embedding(vocab_size, hidden_size)
77
+ self.pos_embedding = nn.Embedding(max_seq_len, hidden_size)
78
+ self.layers = nn.ModuleList([
79
+ nn.ModuleDict({
80
+ "norm": nn.LayerNorm(hidden_size),
81
+ "attn": LinearAttention(hidden_size, num_heads),
82
+ }) for _ in range(num_layers)
83
+ ])
84
+ self.norm = nn.LayerNorm(hidden_size)
85
+ self.output = nn.Linear(hidden_size, vocab_size, bias=False)
86
+
87
+ def forward(self, input_ids, attention_mask=None, labels=None):
88
+ B, L = input_ids.shape
89
+ pos = torch.arange(L, device=input_ids.device).unsqueeze(0)
90
+ x = self.embedding(input_ids) + self.pos_embedding(pos)
91
+
92
+ for layer in self.layers:
93
+ residual = x
94
+ x = layer["norm"](x)
95
+ x = residual + layer["attn"](x, attention_mask)
96
+
97
+ x = self.norm(x)
98
+ logits = self.output(x)
99
+ loss = None
100
+ if labels is not None:
101
+ loss = nn.functional.cross_entropy(
102
+ logits.view(-1, logits.size(-1)), labels.view(-1), ignore_index=-100)
103
+ return {"loss": loss, "logits": logits, "avg_decision_ratio": 0.0}
104
+
105
+
106
+ class UniformHybridModel(nn.Module):
107
+ """Every Nth layer uses full attention, rest use linear (Jamba-style)."""
108
+
109
+ def __init__(self, vocab_size, hidden_size, num_layers, num_heads, max_seq_len,
110
+ full_attn_every=4, **kw):
111
+ super().__init__()
112
+ self.embedding = nn.Embedding(vocab_size, hidden_size)
113
+ self.pos_embedding = nn.Embedding(max_seq_len, hidden_size)
114
+ self.full_attn_every = full_attn_every
115
+
116
+ self.layers = nn.ModuleList()
117
+ for i in range(num_layers):
118
+ use_full = (i % full_attn_every == 0)
119
+ attn = FullAttention(hidden_size, num_heads) if use_full else LinearAttention(hidden_size, num_heads)
120
+ self.layers.append(nn.ModuleDict({
121
+ "norm": nn.LayerNorm(hidden_size),
122
+ "attn": attn,
123
+ "is_full": nn.Identity(), # marker
124
+ }))
125
+
126
+ self.norm = nn.LayerNorm(hidden_size)
127
+ self.output = nn.Linear(hidden_size, vocab_size, bias=False)
128
+ self._ratio = 1.0 / full_attn_every
129
+
130
+ def forward(self, input_ids, attention_mask=None, labels=None):
131
+ B, L = input_ids.shape
132
+ pos = torch.arange(L, device=input_ids.device).unsqueeze(0)
133
+ x = self.embedding(input_ids) + self.pos_embedding(pos)
134
+
135
+ for layer in self.layers:
136
+ residual = x
137
+ x = layer["norm"](x)
138
+ x = residual + layer["attn"](x, attention_mask)
139
+
140
+ x = self.norm(x)
141
+ logits = self.output(x)
142
+ loss = None
143
+ if labels is not None:
144
+ loss = nn.functional.cross_entropy(
145
+ logits.view(-1, logits.size(-1)), labels.view(-1), ignore_index=-100)
146
+ return {"loss": loss, "logits": logits, "avg_decision_ratio": self._ratio}
dpa-project/src/models/dpa_model.py ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Decision Point Attention (DPA) Model
3
+
4
+ Architecture:
5
+ - Default: Linear Attention (GLA / DeltaNet / Mamba-2) for all tokens
6
+ - Router: classifies each token as decision-point or routine
7
+ - Decision points: routed through full softmax attention
8
+ - Output: merged via gating
9
+
10
+ This module provides:
11
+ 1. DPALayer — single layer with conditional routing
12
+ 2. DPAModel — full model wrapping a pretrained backbone
13
+ 3. DPAWrapper — wraps any HuggingFace model for simulation experiments
14
+ """
15
+
16
+ import torch
17
+ import torch.nn as nn
18
+ import torch.nn.functional as F
19
+ import math
20
+ from .router import DecisionPointRouter, FixedRatioRouter
21
+
22
+
23
+ class LinearAttention(nn.Module):
24
+ """Simplified linear attention (kernel approximation) for simulation."""
25
+
26
+ def __init__(self, hidden_size, num_heads, head_dim=None):
27
+ super().__init__()
28
+ self.num_heads = num_heads
29
+ self.head_dim = head_dim or hidden_size // num_heads
30
+ self.hidden_size = hidden_size
31
+
32
+ self.q_proj = nn.Linear(hidden_size, num_heads * self.head_dim, bias=False)
33
+ self.k_proj = nn.Linear(hidden_size, num_heads * self.head_dim, bias=False)
34
+ self.v_proj = nn.Linear(hidden_size, num_heads * self.head_dim, bias=False)
35
+ self.o_proj = nn.Linear(num_heads * self.head_dim, hidden_size, bias=False)
36
+
37
+ def forward(self, x, attention_mask=None):
38
+ B, L, _ = x.shape
39
+ q = self.q_proj(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
40
+ k = self.k_proj(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
41
+ v = self.v_proj(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
42
+
43
+ # ELU+1 kernel for linear attention
44
+ q = F.elu(q) + 1
45
+ k = F.elu(k) + 1
46
+
47
+ # Linear attention: O(n) via associative scan
48
+ kv = torch.einsum("bhld,bhlv->bhdv", k, v) # (B, H, D, V)
49
+ z = k.sum(dim=2) # (B, H, D)
50
+ out = torch.einsum("bhld,bhdv->bhlv", q, kv) / (
51
+ torch.einsum("bhld,bhd->bhl", q, z).unsqueeze(-1) + 1e-6
52
+ )
53
+
54
+ out = out.transpose(1, 2).contiguous().view(B, L, -1)
55
+ return self.o_proj(out)
56
+
57
+
58
+ class FullAttention(nn.Module):
59
+ """Standard softmax attention."""
60
+
61
+ def __init__(self, hidden_size, num_heads, head_dim=None):
62
+ super().__init__()
63
+ self.num_heads = num_heads
64
+ self.head_dim = head_dim or hidden_size // num_heads
65
+ self.scale = self.head_dim ** -0.5
66
+
67
+ self.q_proj = nn.Linear(hidden_size, num_heads * self.head_dim, bias=False)
68
+ self.k_proj = nn.Linear(hidden_size, num_heads * self.head_dim, bias=False)
69
+ self.v_proj = nn.Linear(hidden_size, num_heads * self.head_dim, bias=False)
70
+ self.o_proj = nn.Linear(num_heads * self.head_dim, hidden_size, bias=False)
71
+
72
+ def forward(self, x, attention_mask=None):
73
+ B, L, _ = x.shape
74
+ q = self.q_proj(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
75
+ k = self.k_proj(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
76
+ v = self.v_proj(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
77
+
78
+ attn = torch.matmul(q, k.transpose(-2, -1)) * self.scale
79
+ if attention_mask is not None:
80
+ attn = attn + attention_mask
81
+ attn = F.softmax(attn, dim=-1)
82
+
83
+ out = torch.matmul(attn, v)
84
+ out = out.transpose(1, 2).contiguous().view(B, L, -1)
85
+ return self.o_proj(out)
86
+
87
+
88
+ class DPALayer(nn.Module):
89
+ """
90
+ Decision Point Attention Layer.
91
+ Routes tokens to linear or full attention based on router decision.
92
+ """
93
+
94
+ def __init__(self, hidden_size, num_heads, router_type="learned", target_ratio=0.15):
95
+ super().__init__()
96
+ self.linear_attn = LinearAttention(hidden_size, num_heads)
97
+ self.full_attn = FullAttention(hidden_size, num_heads)
98
+
99
+ if router_type == "learned":
100
+ self.router = DecisionPointRouter(hidden_size)
101
+ else:
102
+ self.router = FixedRatioRouter(ratio=target_ratio)
103
+
104
+ # Gating to merge linear and full attention outputs
105
+ self.gate = nn.Linear(hidden_size, hidden_size)
106
+ self.norm = nn.LayerNorm(hidden_size)
107
+
108
+ def forward(self, x, attention_mask=None):
109
+ """
110
+ Args:
111
+ x: (B, L, D) input hidden states
112
+ Returns:
113
+ out: (B, L, D) output
114
+ routing_info: dict with mask, ratio, probs
115
+ """
116
+ residual = x
117
+ x = self.norm(x)
118
+
119
+ # Get routing decision
120
+ routing_mask, routing_probs = self.router(x)
121
+
122
+ # Linear attention for ALL tokens (cheap baseline)
123
+ linear_out = self.linear_attn(x, attention_mask)
124
+
125
+ # Full attention ONLY for decision-point tokens
126
+ dp_mask = routing_mask.bool() # (B, L)
127
+
128
+ if dp_mask.any():
129
+ # Gather decision-point tokens
130
+ # For efficiency simulation, we compute full attention on the subset
131
+ full_out = self.full_attn(x, attention_mask)
132
+
133
+ # Merge: decision points use full attention, rest use linear
134
+ gate_values = torch.sigmoid(self.gate(x))
135
+ merged = torch.where(
136
+ dp_mask.unsqueeze(-1),
137
+ gate_values * full_out + (1 - gate_values) * linear_out,
138
+ linear_out,
139
+ )
140
+ else:
141
+ merged = linear_out
142
+
143
+ out = residual + merged
144
+
145
+ routing_info = {
146
+ "mask": routing_mask,
147
+ "probs": routing_probs,
148
+ "ratio": self.router.get_decision_ratio(routing_mask)
149
+ if hasattr(self.router, "get_decision_ratio")
150
+ else routing_mask.float().mean().item(),
151
+ }
152
+
153
+ return out, routing_info
154
+
155
+
156
+ class DPATransformer(nn.Module):
157
+ """
158
+ Full DPA Transformer for simulation experiments.
159
+ Stack of DPA layers with embedding and output head.
160
+ """
161
+
162
+ def __init__(
163
+ self,
164
+ vocab_size=32000,
165
+ hidden_size=512,
166
+ num_layers=6,
167
+ num_heads=8,
168
+ max_seq_len=2048,
169
+ router_type="learned",
170
+ target_ratio=0.15,
171
+ ):
172
+ super().__init__()
173
+ self.embedding = nn.Embedding(vocab_size, hidden_size)
174
+ self.pos_embedding = nn.Embedding(max_seq_len, hidden_size)
175
+
176
+ self.layers = nn.ModuleList(
177
+ [
178
+ DPALayer(hidden_size, num_heads, router_type, target_ratio)
179
+ for _ in range(num_layers)
180
+ ]
181
+ )
182
+
183
+ self.norm = nn.LayerNorm(hidden_size)
184
+ self.output = nn.Linear(hidden_size, vocab_size, bias=False)
185
+
186
+ self.hidden_size = hidden_size
187
+ self.num_layers = num_layers
188
+
189
+ def forward(self, input_ids, attention_mask=None, labels=None):
190
+ B, L = input_ids.shape
191
+ positions = torch.arange(L, device=input_ids.device).unsqueeze(0)
192
+
193
+ x = self.embedding(input_ids) + self.pos_embedding(positions)
194
+
195
+ all_routing_info = []
196
+ for layer in self.layers:
197
+ x, routing_info = layer(x, attention_mask)
198
+ all_routing_info.append(routing_info)
199
+
200
+ x = self.norm(x)
201
+ logits = self.output(x)
202
+
203
+ loss = None
204
+ if labels is not None:
205
+ loss = F.cross_entropy(
206
+ logits.view(-1, logits.size(-1)), labels.view(-1), ignore_index=-100
207
+ )
208
+
209
+ return {
210
+ "loss": loss,
211
+ "logits": logits,
212
+ "routing_info": all_routing_info,
213
+ "avg_decision_ratio": sum(r["ratio"] for r in all_routing_info)
214
+ / len(all_routing_info),
215
+ }
216
+
217
+
218
+ class DPAWrapper(nn.Module):
219
+ """
220
+ Wrap a pretrained HuggingFace model to simulate DPA.
221
+ Adds routing masks to existing attention layers.
222
+
223
+ Usage:
224
+ model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-7B")
225
+ dpa = DPAWrapper(model, target_ratio=0.15)
226
+ outputs = dpa(input_ids)
227
+ """
228
+
229
+ def __init__(self, base_model, target_ratio=0.15, router_type="fixed"):
230
+ super().__init__()
231
+ self.base_model = base_model
232
+ self.target_ratio = target_ratio
233
+
234
+ # Get hidden size from model config
235
+ config = base_model.config
236
+ hidden_size = getattr(config, "hidden_size", 4096)
237
+
238
+ if router_type == "learned":
239
+ self.router = DecisionPointRouter(hidden_size)
240
+ else:
241
+ self.router = FixedRatioRouter(ratio=target_ratio)
242
+
243
+ def forward(self, input_ids, attention_mask=None, labels=None):
244
+ # Get embeddings to compute routing
245
+ if hasattr(self.base_model, "model"):
246
+ embeds = self.base_model.model.embed_tokens(input_ids)
247
+ elif hasattr(self.base_model, "transformer"):
248
+ embeds = self.base_model.transformer.wte(input_ids)
249
+ else:
250
+ embeds = self.base_model.get_input_embeddings()(input_ids)
251
+
252
+ routing_mask, routing_probs = self.router(embeds)
253
+
254
+ # Forward through base model normally
255
+ outputs = self.base_model(
256
+ input_ids=input_ids,
257
+ attention_mask=attention_mask,
258
+ labels=labels,
259
+ )
260
+
261
+ outputs["routing_mask"] = routing_mask
262
+ outputs["routing_probs"] = routing_probs
263
+ outputs["decision_ratio"] = routing_mask.float().mean().item()
264
+
265
+ return outputs
dpa-project/src/models/router.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Decision Point Router — 学习哪些 token 是 agent 决策点,需要 full attention
3
+
4
+ Router 分类每个 token 为:
5
+ - 0: routine(线性注意力)
6
+ - 1: decision point(full softmax 注意力)
7
+
8
+ Decision points 包括:
9
+ - Tool call boundaries(函数调用、API 请求)
10
+ - Plan revision tokens("let me reconsider", "alternatively")
11
+ - Error recovery("error", "failed", "retry")
12
+ - State tracking(变量赋值、环境状态更新)
13
+ """
14
+
15
+ import torch
16
+ import torch.nn as nn
17
+ import torch.nn.functional as F
18
+
19
+
20
+ class DecisionPointRouter(nn.Module):
21
+ """Token-level binary router: decide linear vs full attention per token."""
22
+
23
+ def __init__(self, hidden_size, num_heads=1, temperature=1.0, hard=True):
24
+ super().__init__()
25
+ self.hidden_size = hidden_size
26
+ self.temperature = temperature
27
+ self.hard = hard
28
+
29
+ # Lightweight MLP router
30
+ self.router = nn.Sequential(
31
+ nn.Linear(hidden_size, hidden_size // 4),
32
+ nn.GELU(),
33
+ nn.Linear(hidden_size // 4, 1),
34
+ )
35
+
36
+ # Learnable threshold
37
+ self.threshold = nn.Parameter(torch.tensor(0.0))
38
+
39
+ def forward(self, hidden_states, return_mask=True):
40
+ """
41
+ Args:
42
+ hidden_states: (batch, seq_len, hidden_size)
43
+ Returns:
44
+ routing_mask: (batch, seq_len) — 1 for decision points, 0 for routine
45
+ routing_probs: (batch, seq_len) — soft probabilities
46
+ """
47
+ logits = self.router(hidden_states).squeeze(-1) # (B, L)
48
+ probs = torch.sigmoid(logits / self.temperature)
49
+
50
+ if self.hard and self.training:
51
+ # Straight-through Gumbel estimator
52
+ hard_mask = (probs > 0.5).float()
53
+ routing_mask = hard_mask - probs.detach() + probs # STE
54
+ elif self.hard:
55
+ routing_mask = (probs > 0.5).float()
56
+ else:
57
+ routing_mask = probs
58
+
59
+ return routing_mask, probs
60
+
61
+ def get_decision_ratio(self, routing_mask):
62
+ """Return fraction of tokens routed to full attention."""
63
+ return routing_mask.float().mean().item()
64
+
65
+
66
+ class SupervisedRouterLoss(nn.Module):
67
+ """Supervised loss for training router with labeled decision points."""
68
+
69
+ def __init__(self, target_ratio=0.15, ratio_weight=0.1):
70
+ super().__init__()
71
+ self.target_ratio = target_ratio
72
+ self.ratio_weight = ratio_weight
73
+ self.bce = nn.BCEWithLogitsLoss()
74
+
75
+ def forward(self, routing_probs, labels, routing_mask=None):
76
+ """
77
+ Args:
78
+ routing_probs: (B, L) soft probabilities
79
+ labels: (B, L) binary labels (1=decision point)
80
+ routing_mask: (B, L) hard mask for ratio penalty
81
+ """
82
+ # Classification loss
83
+ cls_loss = self.bce(routing_probs, labels.float())
84
+
85
+ # Budget/ratio regularization
86
+ if routing_mask is not None:
87
+ actual_ratio = routing_mask.float().mean()
88
+ ratio_loss = (actual_ratio - self.target_ratio) ** 2
89
+ else:
90
+ ratio_loss = torch.tensor(0.0)
91
+
92
+ return cls_loss + self.ratio_weight * ratio_loss
93
+
94
+
95
+ class FixedRatioRouter(nn.Module):
96
+ """Baseline: route top-k% tokens by hidden state norm (no learning)."""
97
+
98
+ def __init__(self, ratio=0.15):
99
+ super().__init__()
100
+ self.ratio = ratio
101
+
102
+ def forward(self, hidden_states, return_mask=True):
103
+ B, L, D = hidden_states.shape
104
+ k = max(1, int(L * self.ratio))
105
+
106
+ # Score by L2 norm of hidden state (high-info tokens have larger norms)
107
+ scores = hidden_states.norm(dim=-1) # (B, L)
108
+
109
+ # Top-k selection
110
+ topk_indices = scores.topk(k, dim=-1).indices
111
+ mask = torch.zeros(B, L, device=hidden_states.device)
112
+ mask.scatter_(1, topk_indices, 1.0)
113
+
114
+ return mask, scores / scores.max(dim=-1, keepdim=True).values
figures/accuracy_vs_flops.pdf ADDED
Binary file (21.8 kB). View file
 
figures/ratio_ablation.pdf ADDED
Binary file (19.4 kB). View file
 
figures/trajectory_analysis.pdf ADDED
Binary file (17.4 kB). View file