Upload folder using huggingface_hub
Browse files- README.md +79 -0
- agentlab-pipeline/agentlab_run.log +791 -0
- agentlab-pipeline/ai_lab_repo_patched.py +891 -0
- agentlab-pipeline/decision_point_attention.yaml +86 -0
- agentlab-pipeline/inference_patched.py +236 -0
- agentlab-pipeline/tools_patched.py +333 -0
- conversation/session-2026-03-20-to-24.md +199 -0
- dpa-project/CONVERSATION_LOG.md +58 -0
- dpa-project/README.md +56 -0
- dpa-project/configs/dpa_sim.yaml +15 -0
- dpa-project/configs/dpa_train_7b.yaml +33 -0
- dpa-project/data/agent_trajectories.json +0 -0
- dpa-project/requirements.txt +21 -0
- dpa-project/results/simulation_results.json +132 -0
- dpa-project/scripts/run_dpa.sh +31 -0
- dpa-project/scripts/run_simulation.sh +27 -0
- dpa-project/src/__init__.py +0 -0
- dpa-project/src/data/__init__.py +0 -0
- dpa-project/src/data/agent_trajectory.py +192 -0
- dpa-project/src/data/datasets.py +85 -0
- dpa-project/src/eval/__init__.py +0 -0
- dpa-project/src/eval/benchmark.py +194 -0
- dpa-project/src/eval/metrics.py +108 -0
- dpa-project/src/eval/visualize.py +160 -0
- dpa-project/src/models/__init__.py +0 -0
- dpa-project/src/models/baselines.py +146 -0
- dpa-project/src/models/dpa_model.py +265 -0
- dpa-project/src/models/router.py +114 -0
- figures/accuracy_vs_flops.pdf +0 -0
- figures/ratio_ablation.pdf +0 -0
- figures/trajectory_analysis.pdf +0 -0
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 |
+
[1mBertModel LOAD REPORT[0m from: sentence-transformers/all-MiniLM-L6-v2
|
| 3 |
+
Key | Status | |
|
| 4 |
+
------------------------+------------+--+-
|
| 5 |
+
embeddings.position_ids | UNEXPECTED | |
|
| 6 |
+
|
| 7 |
+
[3mNotes:
|
| 8 |
+
- UNEXPECTED[3m :can be ignored when loading from different task/architecture; not ok if you expect identical arch.[0m
|
| 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 |
+
[1mBertModel LOAD REPORT[0m from: sentence-transformers/all-MiniLM-L6-v2
|
| 613 |
+
Key | Status | |
|
| 614 |
+
------------------------+------------+--+-
|
| 615 |
+
embeddings.position_ids | UNEXPECTED | |
|
| 616 |
+
|
| 617 |
+
[3mNotes:
|
| 618 |
+
- UNEXPECTED[3m :can be ignored when loading from different task/architecture; not ok if you expect identical arch.[0m
|
| 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 |
+
[1mBertModel LOAD REPORT[0m from: sentence-transformers/all-MiniLM-L6-v2
|
| 637 |
+
Key | Status | |
|
| 638 |
+
------------------------+------------+--+-
|
| 639 |
+
embeddings.position_ids | UNEXPECTED | |
|
| 640 |
+
|
| 641 |
+
[3mNotes:
|
| 642 |
+
- UNEXPECTED[3m :can be ignored when loading from different task/architecture; not ok if you expect identical arch.[0m
|
| 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 |
+
[1mBertModel LOAD REPORT[0m from: sentence-transformers/all-MiniLM-L6-v2
|
| 658 |
+
Key | Status | |
|
| 659 |
+
------------------------+------------+--+-
|
| 660 |
+
embeddings.position_ids | UNEXPECTED | |
|
| 661 |
+
|
| 662 |
+
[3mNotes:
|
| 663 |
+
- UNEXPECTED[3m :can be ignored when loading from different task/architecture; not ok if you expect identical arch.[0m
|
| 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 |
+
[1mBertModel LOAD REPORT[0m from: sentence-transformers/all-MiniLM-L6-v2
|
| 680 |
+
Key | Status | |
|
| 681 |
+
------------------------+------------+--+-
|
| 682 |
+
embeddings.position_ids | UNEXPECTED | |
|
| 683 |
+
|
| 684 |
+
[3mNotes:
|
| 685 |
+
- UNEXPECTED[3m :can be ignored when loading from different task/architecture; not ok if you expect identical arch.[0m
|
| 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 |
+
[1mBertModel LOAD REPORT[0m from: sentence-transformers/all-MiniLM-L6-v2
|
| 705 |
+
Key | Status | |
|
| 706 |
+
------------------------+------------+--+-
|
| 707 |
+
embeddings.position_ids | UNEXPECTED | |
|
| 708 |
+
|
| 709 |
+
[3mNotes:
|
| 710 |
+
- UNEXPECTED[3m :can be ignored when loading from different task/architecture; not ok if you expect identical arch.[0m
|
| 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 |
+
[1mBertModel LOAD REPORT[0m from: sentence-transformers/all-MiniLM-L6-v2
|
| 725 |
+
Key | Status | |
|
| 726 |
+
------------------------+------------+--+-
|
| 727 |
+
embeddings.position_ids | UNEXPECTED | |
|
| 728 |
+
|
| 729 |
+
[3mNotes:
|
| 730 |
+
- UNEXPECTED[3m :can be ignored when loading from different task/architecture; not ok if you expect identical arch.[0m
|
| 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 |
+
[1mBertModel LOAD REPORT[0m from: sentence-transformers/all-MiniLM-L6-v2
|
| 749 |
+
Key | Status | |
|
| 750 |
+
------------------------+------------+--+-
|
| 751 |
+
embeddings.position_ids | UNEXPECTED | |
|
| 752 |
+
|
| 753 |
+
[3mNotes:
|
| 754 |
+
- UNEXPECTED[3m :can be ignored when loading from different task/architecture; not ok if you expect identical arch.[0m
|
| 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 |
+
[1mBertModel LOAD REPORT[0m from: sentence-transformers/all-MiniLM-L6-v2
|
| 769 |
+
Key | Status | |
|
| 770 |
+
------------------------+------------+--+-
|
| 771 |
+
embeddings.position_ids | UNEXPECTED | |
|
| 772 |
+
|
| 773 |
+
[3mNotes:
|
| 774 |
+
- UNEXPECTED[3m :can be ignored when loading from different task/architecture; not ok if you expect identical arch.[0m
|
| 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
|
|
|