Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- .gitattributes +3 -0
- README.md +184 -133
- assets/Logs.png +3 -0
- assets/molforge_architecture.png +3 -0
- assets/reward_curve.png +3 -0
- index.html +457 -0
- molforge_grpo_official_submission.ipynb +132 -79
.gitattributes
CHANGED
|
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
assets/Logs.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
assets/molforge_architecture.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
assets/reward_curve.png filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -1,163 +1,214 @@
|
|
| 1 |
---
|
| 2 |
-
title: MolForge
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk:
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
# MolForge
|
| 11 |
|
| 12 |
-
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
- hidden target mutation traps (e.g. KRAS resistance panel shifts)
|
| 17 |
-
- visible task metadata, team communication, assay results, and remaining budget
|
| 18 |
-
- simulated `RDKit` descriptors and `TDC` (Therapeutics Data Commons) predictions (QED, SA_Score, LogP, TPSA)
|
| 19 |
-
- dense step-wise reward (in curriculum mode) plus terminal reward for submission quality
|
| 20 |
|
| 21 |
-
|
| 22 |
-
1. `reset()` picks a biological scenario (e.g. `level_1_medium`) and seeds the simulator.
|
| 23 |
-
2. The agent receives a `MolForgeObservation` describing the task, the starting molecule scaffold, and the current visible state.
|
| 24 |
-
3. The agent (acting as different roles) submits a `MolForgeAction` such as `edit`, `run_assay`, `propose_nomination`, or `submit`.
|
| 25 |
-
4. The **Governance rule engine** checks whether the action is valid, requiring multi-agent consensus for final decisions.
|
| 26 |
-
5. The transition engine updates the molecule, spends the assay budget, and returns oracle readings.
|
| 27 |
-
6. The reward computer scores the step based on whether the action was invalid, vetoed, or successful.
|
| 28 |
-
7. The environment returns a new observation with updated history, assay readings, and reward.
|
| 29 |
-
8. The episode ends when the agent successfully submits the molecule, exhausts its budget, or reaches the maximum step horizon.
|
| 30 |
|
| 31 |
-
--
|
| 32 |
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
-
|
| 36 |
-
The simulator keeps ground-truth properties that the agent never directly sees. It contains:
|
| 37 |
-
- The true underlying scoring functions for `potency`, `safety`, and `synthesizability`.
|
| 38 |
-
- Sunk-cost traps and late-stage target mutations (e.g., in `level_2_hard`).
|
| 39 |
-
- The strict constraints required for a valid submission.
|
| 40 |
-
- The remaining hidden milestones for the scenario.
|
| 41 |
|
| 42 |
-
|
| 43 |
-
The agent only sees `MolForgeObservation`, which includes:
|
| 44 |
-
- The current `TaskSpec` and `scenario_id`.
|
| 45 |
-
- Pipeline history and previous actions.
|
| 46 |
-
- The current molecular scaffold (in SMILES format).
|
| 47 |
-
- The `budget_used` and `remaining_budget`.
|
| 48 |
-
- Responses from the `run_assay` oracle (TDC predictors and RDKit descriptors).
|
| 49 |
-
- The `GovernanceStatus` showing which specialist agents have approved or objected.
|
| 50 |
-
- The `step_reward_breakdown`.
|
| 51 |
|
| 52 |
-
This separation is what makes the environment a POMDP rather than a fully observed simulator.
|
| 53 |
|
| 54 |
-
|
| 55 |
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
#
|
| 59 |
-
|
| 60 |
-
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
#
|
| 65 |
-
This is where episodes come from. It defines a curated library of three biological scenarios, each bundling a starting scaffold, a budget, and a specific molecular target:
|
| 66 |
-
- `level_0_easy`: Potency-first optimization with a generous budget and a starting scaffold that is one or two edits from success.
|
| 67 |
-
- `level_1_medium`: Multi-objective optimization with safety as a hard constraint and moderate budget pressure.
|
| 68 |
-
- `level_2_hard`: A sunk-cost trap plus late target mutation. The initial scaffold family has a hidden liability, and the best policy is often to restart early.
|
| 69 |
-
|
| 70 |
-
### `server/actions.py` & `server/governance.py`
|
| 71 |
-
The rule engines enforcing scientific and procedural constraints before each action is applied:
|
| 72 |
-
- `run_assay`: Costs budget. Assembles the fragments into a valid `SMILES` string and evaluates the current molecule using `TDC` Oracles and `RDKit` logic (e.g. `MolLogP`, `TPSA`, `NumRotatableBonds`, `QED`).
|
| 73 |
-
- `edit`: Replaces a specific R-group slot (`warhead`, `hinge`, `solvent_tail`, `back_pocket`) with a new chemical fragment (e.g. `acrylamide`, `fluorophenyl`, `morpholine`). Clears previously gathered evidence.
|
| 74 |
-
- `submit`: Ends the episode. Triggers the final evaluation grader against the scenario's strict hard constraints (`potency_min`, `toxicity_max`, `synth_min`).
|
| 75 |
-
- **Governance**: Certain actions require multi-agent consensus. If the `Lead Chemist` tries to submit without the `Safety Specialist`'s approval, the action is vetoed.
|
| 76 |
-
|
| 77 |
-
### `server/molforge_environment.py`
|
| 78 |
-
This is the orchestration layer that ties everything together.
|
| 79 |
-
On `reset()` it:
|
| 80 |
-
- Generates a task scenario.
|
| 81 |
-
- Clears the message log, history, and resets the molecule to the default scaffold.
|
| 82 |
-
|
| 83 |
-
On `step()` it:
|
| 84 |
-
- Checks governance rules and validates the action.
|
| 85 |
-
- Executes the action (e.g. replacing an R-group fragment or running an assay).
|
| 86 |
-
- Computes reward (via Curriculum or Assay-Gated mode).
|
| 87 |
-
- Builds the next `MolForgeObservation`.
|
| 88 |
|
| 89 |
---
|
| 90 |
|
| 91 |
-
## What actually happens on one step
|
| 92 |
-
Here is the concrete order of operations for `env.step(action)`:
|
| 93 |
-
1. Increment the step counter.
|
| 94 |
-
2. Run validation checks. If the action format is invalid, return a failure report and a `-1.0` reward.
|
| 95 |
-
3. Assess **Governance**. If a required specialist agent vetoes the action, the action is blocked and penalized.
|
| 96 |
-
4. Execute the action (`edit`, `run_assay`, `submit`).
|
| 97 |
-
5. Deduct oracle budget if `run_assay` was called.
|
| 98 |
-
6. Compute decomposed reward from the state transition (e.g., getting penalized for redundant assays).
|
| 99 |
-
7. If the episode is ending (via `submit`, max steps, or zero budget), compute the terminal `submission_score`.
|
| 100 |
-
8. Return an observation that exposes the visible summary but not the hidden truth.
|
| 101 |
|
| 102 |
-
|
| 103 |
|
| 104 |
-
|
| 105 |
-
Most scenarios reward a sensible experiment order similar to:
|
| 106 |
-
1. `run_assay` (Assay potency and safety of the baseline molecule).
|
| 107 |
-
2. `edit` (Swap an R-group fragment to improve a weak property).
|
| 108 |
-
3. `run_assay` (Gather new evidence for the modified molecule).
|
| 109 |
-
4. `propose_nomination` (Discuss the findings with the multi-agent review board).
|
| 110 |
-
5. `submit` (Finalize the candidate).
|
| 111 |
|
| 112 |
-
|
| 113 |
|
| 114 |
-
|
|
|
|
| 115 |
|
| 116 |
-
|
|
|
|
| 117 |
|
| 118 |
-
|
| 119 |
|
| 120 |
-
|
| 121 |
-
- Gives partial credit at the end of an episode even if the model didn't submit, provided it gathered useful evidence.
|
| 122 |
-
- It actively prevents "reward hacking" by penalizing assay-spamming, and giving massive multipliers to successful submissions.
|
| 123 |
|
| 124 |
-
|
| 125 |
-
- Strict OpenEnv hackathon rules.
|
| 126 |
-
- If the agent does not formally `submit` the candidate, the final score is `0.0`.
|
| 127 |
-
- No partial credit is given for just gathering evidence.
|
| 128 |
|
| 129 |
-
|
| 130 |
-
- The agent explicitly chooses `submit`.
|
| 131 |
-
- Resources (oracle budget) are exhausted.
|
| 132 |
-
- The environment reaches `MAX_STEPS`.
|
| 133 |
|
| 134 |
-
--
|
| 135 |
|
| 136 |
-
##
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
```python
|
| 145 |
-
from models import MolForgeAction
|
| 146 |
-
from server.molforge_environment import MolForgeEnvironment
|
| 147 |
-
|
| 148 |
-
env = MolForgeEnvironment()
|
| 149 |
-
obs = env.reset()
|
| 150 |
-
|
| 151 |
-
action = MolForgeAction(
|
| 152 |
-
action_type="run_assay",
|
| 153 |
-
acting_role="Lead Chemist",
|
| 154 |
-
tool_name="potency_oracle",
|
| 155 |
-
rationale="Need to gather baseline potency evidence."
|
| 156 |
-
)
|
| 157 |
-
obs = env.step(action)
|
| 158 |
-
print(obs.reward)
|
| 159 |
-
print(obs.last_transition_summary)
|
| 160 |
```
|
| 161 |
|
| 162 |
-
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: "MolForge: Verifier-Driven RL for Drug Discovery"
|
| 3 |
+
emoji: 🧬
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: blue
|
| 6 |
+
sdk: static
|
| 7 |
+
pinned: false
|
| 8 |
+
license: mit
|
| 9 |
+
tags:
|
| 10 |
+
- reinforcement-learning
|
| 11 |
+
- drug-discovery
|
| 12 |
+
- chemistry
|
| 13 |
+
- multi-agent
|
| 14 |
+
- oncology
|
| 15 |
+
- molecular-simulation
|
| 16 |
+
- openenv
|
| 17 |
---
|
| 18 |
|
| 19 |
+
# MolForge: Verifier-Driven RL for Drug Discovery
|
| 20 |
|
| 21 |
+
MolForge is a reinforcement learning environment that simulates a **medical oncology discovery lab**. Unlike traditional LLM tasks where the model generates a final answer in one shot, MolForge forces the model to execute the **scientific method** under real-world constraints: budget, toxicity, and synthesis complexity.
|
| 22 |
|
| 23 |
+
**[View the MolForge Space Deployment on Hugging Face](https://huggingface.co/spaces/Adhitya122/molforge)**
|
| 24 |
+
**[Try the RL Training Notebook on Google Colab](https://colab.research.google.com/drive/1c6npGkGNbbbd8XFNeS6zInBpopLnJ4W4?usp=sharing)**
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
### The Scientific Method as a Workflow
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
+
Imagine a biotech team tasked with optimizing a lead candidate for **KRAS G12C** (including a high-difficulty resistance panel). The model doesn't just "write" a molecule; it controls a specialist team that must navigate a resource-constrained laboratory:
|
| 29 |
|
| 30 |
+
- **Lead Chemist**: Proposes molecular edits and decides when to submit.
|
| 31 |
+
- **Assay Planner**: Allocates limited budget to run empirical tests.
|
| 32 |
+
- **Toxicologist**: Reviews safety risks and can object to unsafe designs.
|
| 33 |
+
- **Process Chemist**: Evaluates whether the molecule is practical to synthesize.
|
| 34 |
|
| 35 |
+
Every action—editing a fragment, running a docking simulation, or ordering a toxicity assay—is a decision that impacts the final outcome. The model must learn to gather enough evidence to justify a submission while keeping the project within budget.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
+
> **Core Philosophy:** The LLM is not the judge. The LLM is the scientist being judged by external, verifiable reality.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
|
|
|
| 39 |
|
| 40 |
+
## What Makes MolForge Special?
|
| 41 |
|
| 42 |
+
MolForge is built to move beyond simple "molecule generation" into "scientific workflow optimization." Here are the seven core pillars that make it unique:
|
| 43 |
+
|
| 44 |
+
1. [**Verifier-Based Evaluation**](#1-verifier-based-evaluation): The LLM is the scientist, not the judge. It is held accountable by real-world verifiers like **RDKit** and **TDC**.
|
| 45 |
+
2. [**Chemical & Molecular Simulations**](#2-chemical--molecular-simulations): Realistic simulation of potency and existence using heuristic docking, **RDKit**, and **TDC**.
|
| 46 |
+
3. [**Self-Correction & Improvement Loop**](#3-self-correction--improvement-loop): After each edit, agents receive structured feedback from verifiers, allowing them to self-correct.
|
| 47 |
+
4. [**Decomposed Reward Architecture**](#4-decomposed-reward-architecture): Multi-step rewards for every action (research, edits, coordination) provide high observability.
|
| 48 |
+
5. [**Scientific Model Improvement**](#5-scientific-model-improvement): Real verifier feedback (Reviews) guides the model toward scientifically sound designs.
|
| 49 |
+
6. [**Strategic Training Modes**](#6-strategic-training-modes): A dual-mode system using **Curriculum mode** (partial credit) and **Assay-Gated mode** (strict).
|
| 50 |
+
7. [**Multi-Agent Governance**](#7-multi-agent-governance): A specialized team that plans, executes, and shares information to coordinate the next plan of action.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
---
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
+
## Architecture
|
| 56 |
|
| 57 |
+
The architecture is a closed scientific feedback loop:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
+

|
| 60 |
|
| 61 |
+
### The POMDP Framework: Hidden vs. Visible State
|
| 62 |
+
MolForge is designed as a **partially observable Markov decision process (POMDP)**. This separation is what makes the environment a scientific challenge rather than a simple optimization task.
|
| 63 |
|
| 64 |
+
- **Hidden State**: The simulator tracks the ground-truth scoring for `potency`, `safety`, and `synthesizability`. It also hides "sunk-cost traps" and late-stage target mutation shifts (e.g., in `level_2_hard`) that the agent must discover through evidence.
|
| 65 |
+
- **Visible State**: The agent only sees noisy `MolForgeObservation` reports: pipeline history, SMILES scaffolds, remaining budget, and the structured feedback from the verifier assays (RDKit and TDC).
|
| 66 |
|
| 67 |
+
## Scientific Verifier Layers
|
| 68 |
|
| 69 |
+
### RDKit: chemical plausibility
|
|
|
|
|
|
|
| 70 |
|
| 71 |
+
RDKit checks molecule-like behavior and chemistry descriptors. In MolForge, this layer is used to keep the molecule edits grounded in chemical reality. It supports descriptor-style reasoning such as lipophilicity, polarity, tractability, and drug-likeness.
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
+
### TDC: biomedical outcome signals
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
+
Therapeutics Data Commons represents the medical outcome side of the environment. It provides the project with a path toward realistic prediction tasks such as toxicity, synthesis difficulty, and drug-likeness. In the default Docker deployment, RDKit remains active and TDC is optional because it can pull a heavier platform-sensitive ML stack.
|
| 76 |
|
| 77 |
+
### Heuristic docking: receptor fit
|
| 78 |
+
|
| 79 |
+
MolForge includes a docking-style surrogate that answers three fast questions:
|
| 80 |
+
|
| 81 |
+
| Check | Question | Why it matters |
|
| 82 |
+
| --- | --- | --- |
|
| 83 |
+
| Pocket matching | Does the hinge fragment fit the receptor pocket? | Better pocket complementarity improves potency. |
|
| 84 |
+
| Lipophilic match | Is LogP near the target pocket's hydrophobic comfort zone around `3.0`? | Too much lipophilicity can increase toxicity; too little can weaken binding. |
|
| 85 |
+
| Polarity match | Is TPSA near a useful range around `85.0`? | Polarity affects binding, permeability, and clash risk. |
|
| 86 |
+
|
| 87 |
+
This gives the environment fast receptor-aware feedback in milliseconds, which is important for RL.
|
| 88 |
+
|
| 89 |
+
## Training Story
|
| 90 |
+
|
| 91 |
+
The training pipeline has two stages:
|
| 92 |
+
|
| 93 |
+
1. **SFT warm start**
|
| 94 |
+
2. **RL with verifier rewards**
|
| 95 |
+
|
| 96 |
+
### Base model
|
| 97 |
+
|
| 98 |
+
The model used for the main run is:
|
| 99 |
|
| 100 |
+
```text
|
| 101 |
+
unsloth/Qwen3.5-2B
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
```
|
| 103 |
|
| 104 |
+
The raw base model was not reliable enough for the environment at first. It often failed to produce the exact structured JSON actions that MolForge expects, and it did not consistently respect the specialist-agent interaction format.
|
| 105 |
+
|
| 106 |
+
So the first step was a small SFT warm start. This stage is not meant to teach the model the optimal chemistry. It teaches the model how to speak the environment's action language:
|
| 107 |
+
|
| 108 |
+
- valid JSON actions
|
| 109 |
+
- correct role/action pairing
|
| 110 |
+
- correct molecule slots and fragments
|
| 111 |
+
- concise rationales
|
| 112 |
+
- evidence fields based only on visible observations
|
| 113 |
+
- expected-effect fields such as potency up/down or toxicity up/down
|
| 114 |
+
- valid specialist messages where needed
|
| 115 |
+
|
| 116 |
+
### Training Results
|
| 117 |
+
After SFT, the policy is trained with GRPO-style RL against MolForge itself. During training, the model explores the 256-combination molecular edit space, receiving rewards for molecule quality, evidence coverage, and budget discipline.
|
| 118 |
+
|
| 119 |
+

|
| 120 |
+

|
| 121 |
+
|
| 122 |
+
### Performance Comparison: SFT vs. RL
|
| 123 |
+
|
| 124 |
+
| Difficulty | Before (SFT Model) | After RL Training | Improvement |
|
| 125 |
+
| :--- | :---: | :---: | :---: |
|
| 126 |
+
| **Easy** | 0.1167 | 0.1295 | **+10.9%** |
|
| 127 |
+
| **Medium** | 0.1167 | 0.1278 | **+9.5%** |
|
| 128 |
+
| **Hard** | 0.0800 | 0.0866 | **+8.3%** |
|
| 129 |
+
|
| 130 |
+
As shown in the reward curve and logs, the model successfully learns to navigate the scientific constraints, moving from early exploration to consistent, verifier-backed molecule submissions. For strict evaluation, the environment switches back to `assay_gated` mode.
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
## Reward Design: Shaping Scientific Behavior
|
| 142 |
+
|
| 143 |
+
The reward function mixes coarse shaping with sparse terminal bonuses to promote rigorous scientific exploration:
|
| 144 |
+
|
| 145 |
+
- **Coarse Feedback**: Edit feedback avoids exposing exact hidden deltas, forcing the model to rely on assays for decision-making.
|
| 146 |
+
- **Information Gain**: Rewards for running useful assays that provide new, evidence-based signal.
|
| 147 |
+
- **Coordination & Governance**: Rewards for correct specialist reviews, proposal discipline, and multi-agent consensus.
|
| 148 |
+
- **Scientific Penalties**: Deductions for invalid actions, repeated states, wasteful assay repetition, and submitting without sufficient potency/safety support.
|
| 149 |
+
- **Terminal Scoring**: A large bonus for submitting a molecule that beats the baseline while satisfying all hard constraints.
|
| 150 |
+
|
| 151 |
+
### Strategic Training Modes
|
| 152 |
+
|
| 153 |
+
MolForge uses two distinct reward settings to balance training and evaluation:
|
| 154 |
+
|
| 155 |
+
1. **Curriculum Mode (Training)**: Adds bounded warmup rewards for evidence collection and "near-miss" episodes. It also adds a small **missed-nomination penalty** when a strong evidence package is ready but the agent lets the deadline pass without submitting. This acts as "breadcrumbs" for RL, helping smaller models navigate sparse reward landscapes.
|
| 156 |
+
2. **Assay-Gated Mode (Evaluation)**: The strict, official hackathon mode. If the agent does not formally `submit` the candidate before the budget is exhausted, the final score is exactly `0.0`. No partial credit is given for just gathering evidence.
|
| 157 |
+
|
| 158 |
+
`final_score` remains the single headline scalar for RL/evaluation. While `candidate_score` and `progress_score` are used for diagnostic observability, the environment is designed so that evidence collection alone cannot look like success; it must lead to a valid submission.
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
## Why This Project Matters
|
| 163 |
+
|
| 164 |
+
MolForge is designed to test a deeper kind of AI capability than simple answer generation. The model must work inside a scientific feedback loop where actions are checked, evidence costs money, unsafe decisions can be blocked, and the final answer only matters if the path to that answer is experimentally justified.
|
| 165 |
+
|
| 166 |
+
The strongest part of the project is that the LLM is not trusted by default. It has to earn trust through verifier-backed decisions.
|
| 167 |
+
|
| 168 |
+
## Deep Dive: What Makes MolForge Special?
|
| 169 |
+
|
| 170 |
+
### 1. Verifier-Based Evaluation
|
| 171 |
+
In many LLM systems, the model itself is used as a judge, reviewer, or evaluator. MolForge flips that pattern. The LLM is the scientist being judged, not the judge. It is held accountable by real-world verifiers like **RDKit**, **TDC**, and molecular simulation engines. This ensures that the model's progress is grounded in chemical and biological reality, not just persuasive language.
|
| 172 |
+
|
| 173 |
+
### 2. Chemical & Molecular Simulations
|
| 174 |
+
MolForge doesn't just predict outcomes; it utilizes multiple simulation layers to ground the model's decisions:
|
| 175 |
+
* **Chemical Plausibility (RDKit):** Decides if the molecule generated by the LLM (via edits) can actually exist in chemical reality. [Visit RDKit](https://www.rdkit.org)
|
| 176 |
+
* **Medical Outcomes (TDC):** Predicts the most probable medical outcomes and properties using the [Therapeutics Data Commons](https://tdcommons.ai).
|
| 177 |
+
* **Heuristic Docking Score:** A fast, physics-inspired simulation that updates **potency** in milliseconds based on three rules:
|
| 178 |
+
1. **Pocket Matching:** Does the fragment structurally fit the target receptor pocket (e.g., KRAS G12C)?
|
| 179 |
+
2. **Lipophilic Match:** Is the LogP near the ideal **3.0** to sit comfortably in the hydrophobic pocket?
|
| 180 |
+
3. **Polarity Match:** Is the TPSA near the ideal **85.0** to avoid repulsive polar clashes?
|
| 181 |
+
|
| 182 |
+
### 3. Self-Correction & Improvement Loop
|
| 183 |
+
MolForge is an iterative environment. After each proposed molecular modification, the model receives a structured review from the verifiers. This feedback allows the agent to recognize liabilities (like toxicity or low potency) and correct them in the next step. This creates a genuine **self-improvement loop** within each episode.
|
| 184 |
+
|
| 185 |
+
### 4. Decomposed Reward Architecture
|
| 186 |
+
The reward function is not a single "black box" scalar. We use a multi-step reward system where small-scale rewards are designed for every individual action—research, molecular edits, and inter-agent coordination. While we may output a single total reward for training simplicity (especially for the hackathon), the decomposed components allow for massive observability into which sections of the workflow are lacking.
|
| 187 |
+
|
| 188 |
+
### 5. Scientific Model Improvement
|
| 189 |
+
We use real verifier feedback to drive model improvement. By providing constant, verifiable reviews, we train the model to improve its designs based on evidence. This moves the model away from simple pattern matching and toward a more rigorous, evidence-based design process.
|
| 190 |
+
|
| 191 |
+
### 6. Strategic Training Modes: Curriculum vs. Assay-Gated
|
| 192 |
+
To solve the "sparse reward" problem common in RL, MolForge uses two distinct modes:
|
| 193 |
+
* **Curriculum Mode (Training):** If a model fails to submit but showed good scientific behavior, it receives "Partial Credit" (up to +0.75). It gets points for gathering evidence and designing promising molecules. These "breadcrumbs" teach the model how to explore before it discovers the terminal submission bonus.
|
| 194 |
+
* **Assay-Gated Mode (Evaluation):** This is the strict, official mode used for hackathon grading. There is **zero partial credit**. If the model fails to explicitly `submit` a high-potency, safe molecule before the budget runs out, its score is exactly `0.0`.
|
| 195 |
+
|
| 196 |
+
### 7. Multi-Agent Governance
|
| 197 |
+
Drug discovery is a team effort. MolForge implements a multi-agent system where specialized roles (Lead Chemist, Toxicologist, Assay Planner) review each other's moves, plans, and executions. Crucially, these agents **share information and coordinate** between themselves to decide the next plan of action, ensuring that every decision undergoes a rigorous "peer review" process before execution.
|
| 198 |
+
|
| 199 |
+
---
|
| 200 |
+
|
| 201 |
+
## Final Takeaway
|
| 202 |
+
|
| 203 |
+
MolForge is special because it treats the LLM as a trainable research agent inside a controlled scientific environment, not as an oracle. The model is judged by chemistry and biomedical verifiers, corrected by specialist feedback, constrained by assay budget, and scored by a reward system that can explain where the policy succeeded or failed.
|
| 204 |
+
|
| 205 |
+
The important pieces work together:
|
| 206 |
+
|
| 207 |
+
- **Verifier-first evaluation:** RDKit, TDC-style signals, and docking-style simulation judge the model's actions.
|
| 208 |
+
- **Multi-agent review:** specialist roles create checks and balances around each decision.
|
| 209 |
+
- **Self-improvement loop:** every action produces feedback that the next action can respond to.
|
| 210 |
+
- **Decomposed rewards:** the environment tracks molecule quality, evidence, budget, coordination, and safety separately.
|
| 211 |
+
- **Curriculum to strict evaluation:** training can use partial-credit breadcrumbs, while final evaluation remains unforgiving.
|
| 212 |
+
- **Dynamic molecular search:** the model explores 256 fragment combinations across three starting scientific scenarios instead of memorizing one answer.
|
| 213 |
+
|
| 214 |
+
That is the project thesis: useful scientific agents should not merely generate plausible ideas. They should operate in a loop where the world pushes back.
|
assets/Logs.png
ADDED
|
Git LFS Details
|
assets/molforge_architecture.png
ADDED
|
Git LFS Details
|
assets/reward_curve.png
ADDED
|
Git LFS Details
|
index.html
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>MolForge | Verifier-Driven RL for Drug Discovery</title>
|
| 7 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 8 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700;800&family=Outfit:wght@400;600;800&display=swap" rel="stylesheet">
|
| 10 |
+
<style>
|
| 11 |
+
:root {
|
| 12 |
+
--primary: #6366f1;
|
| 13 |
+
--primary-glow: rgba(99, 102, 241, 0.5);
|
| 14 |
+
--secondary: #8b5cf6;
|
| 15 |
+
--bg: #0f172a;
|
| 16 |
+
--card-bg: rgba(30, 41, 59, 0.7);
|
| 17 |
+
--text: #f8fafc;
|
| 18 |
+
--text-dim: #94a3b8;
|
| 19 |
+
--glass: rgba(255, 255, 255, 0.03);
|
| 20 |
+
--glass-border: rgba(255, 255, 255, 0.1);
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
* {
|
| 24 |
+
margin: 0;
|
| 25 |
+
padding: 0;
|
| 26 |
+
box-sizing: border-box;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
body {
|
| 30 |
+
font-family: 'Inter', sans-serif;
|
| 31 |
+
background-color: var(--bg);
|
| 32 |
+
background-image:
|
| 33 |
+
radial-gradient(circle at 20% 20%, rgba(99, 102, 241, 0.15) 0%, transparent 40%),
|
| 34 |
+
radial-gradient(circle at 80% 80%, rgba(139, 92, 246, 0.15) 0%, transparent 40%);
|
| 35 |
+
color: var(--text);
|
| 36 |
+
line-height: 1.6;
|
| 37 |
+
overflow-x: hidden;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.container {
|
| 41 |
+
max-width: 1100px;
|
| 42 |
+
margin: 0 auto;
|
| 43 |
+
padding: 0 2rem;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
/* Hero Section */
|
| 47 |
+
header {
|
| 48 |
+
height: 90vh;
|
| 49 |
+
display: flex;
|
| 50 |
+
flex-direction: column;
|
| 51 |
+
justify-content: center;
|
| 52 |
+
align-items: center;
|
| 53 |
+
text-align: center;
|
| 54 |
+
position: relative;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.badge {
|
| 58 |
+
background: var(--glass);
|
| 59 |
+
border: 1px solid var(--glass-border);
|
| 60 |
+
padding: 0.5rem 1.2rem;
|
| 61 |
+
border-radius: 99px;
|
| 62 |
+
font-size: 0.85rem;
|
| 63 |
+
font-weight: 600;
|
| 64 |
+
color: var(--primary);
|
| 65 |
+
margin-bottom: 1.5rem;
|
| 66 |
+
display: inline-block;
|
| 67 |
+
backdrop-filter: blur(10px);
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
h1 {
|
| 71 |
+
font-family: 'Outfit', sans-serif;
|
| 72 |
+
font-size: clamp(3rem, 8vw, 5.5rem);
|
| 73 |
+
font-weight: 800;
|
| 74 |
+
line-height: 1.1;
|
| 75 |
+
margin-bottom: 1.5rem;
|
| 76 |
+
background: linear-gradient(to bottom right, #fff 30%, var(--text-dim));
|
| 77 |
+
-webkit-background-clip: text;
|
| 78 |
+
-webkit-text-fill-color: transparent;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
.hero-tagline {
|
| 82 |
+
font-size: clamp(1.1rem, 3vw, 1.4rem);
|
| 83 |
+
color: var(--text-dim);
|
| 84 |
+
max-width: 700px;
|
| 85 |
+
margin-bottom: 3rem;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
.cta-group {
|
| 89 |
+
display: flex;
|
| 90 |
+
gap: 1.5rem;
|
| 91 |
+
flex-wrap: wrap;
|
| 92 |
+
justify-content: center;
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
.btn {
|
| 96 |
+
padding: 1rem 2.5rem;
|
| 97 |
+
border-radius: 12px;
|
| 98 |
+
font-weight: 700;
|
| 99 |
+
text-decoration: none;
|
| 100 |
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 101 |
+
display: inline-flex;
|
| 102 |
+
align-items: center;
|
| 103 |
+
gap: 0.5rem;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
.btn-primary {
|
| 107 |
+
background: var(--primary);
|
| 108 |
+
color: white;
|
| 109 |
+
box-shadow: 0 10px 20px -10px var(--primary-glow);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
.btn-primary:hover {
|
| 113 |
+
transform: translateY(-2px);
|
| 114 |
+
box-shadow: 0 15px 30px -10px var(--primary-glow);
|
| 115 |
+
filter: brightness(1.1);
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.btn-secondary {
|
| 119 |
+
background: var(--glass);
|
| 120 |
+
border: 1px solid var(--glass-border);
|
| 121 |
+
color: white;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
.btn-secondary:hover {
|
| 125 |
+
background: var(--glass-border);
|
| 126 |
+
transform: translateY(-2px);
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
/* Section Styling */
|
| 130 |
+
section {
|
| 131 |
+
padding: 8rem 0;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
.section-header {
|
| 135 |
+
margin-bottom: 4rem;
|
| 136 |
+
text-align: center;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
.section-header h2 {
|
| 140 |
+
font-family: 'Outfit', sans-serif;
|
| 141 |
+
font-size: 2.5rem;
|
| 142 |
+
margin-bottom: 1rem;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
.section-header p {
|
| 146 |
+
color: var(--text-dim);
|
| 147 |
+
max-width: 600px;
|
| 148 |
+
margin: 0 auto;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
/* Pillars Grid */
|
| 152 |
+
.pillars-grid {
|
| 153 |
+
display: grid;
|
| 154 |
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
| 155 |
+
gap: 2rem;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
.pillar-card {
|
| 159 |
+
background: var(--card-bg);
|
| 160 |
+
border: 1px solid var(--glass-border);
|
| 161 |
+
padding: 2.5rem;
|
| 162 |
+
border-radius: 24px;
|
| 163 |
+
transition: all 0.4s ease;
|
| 164 |
+
backdrop-filter: blur(12px);
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
.pillar-card:hover {
|
| 168 |
+
transform: translateY(-10px);
|
| 169 |
+
border-color: var(--primary);
|
| 170 |
+
box-shadow: 0 20px 40px -20px rgba(0,0,0,0.5);
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
.pillar-icon {
|
| 174 |
+
font-size: 2rem;
|
| 175 |
+
margin-bottom: 1.5rem;
|
| 176 |
+
background: var(--glass);
|
| 177 |
+
width: 60px;
|
| 178 |
+
height: 60px;
|
| 179 |
+
display: flex;
|
| 180 |
+
align-items: center;
|
| 181 |
+
justify-content: center;
|
| 182 |
+
border-radius: 16px;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
.pillar-card h3 {
|
| 186 |
+
font-size: 1.4rem;
|
| 187 |
+
margin-bottom: 1rem;
|
| 188 |
+
color: var(--primary);
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
.pillar-card p {
|
| 192 |
+
color: var(--text-dim);
|
| 193 |
+
font-size: 0.95rem;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
/* Visuals Section */
|
| 197 |
+
.visual-container {
|
| 198 |
+
background: var(--card-bg);
|
| 199 |
+
border: 1px solid var(--glass-border);
|
| 200 |
+
border-radius: 32px;
|
| 201 |
+
padding: 3rem;
|
| 202 |
+
margin-bottom: 4rem;
|
| 203 |
+
overflow: hidden;
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
.visual-container img {
|
| 207 |
+
width: 100%;
|
| 208 |
+
height: auto;
|
| 209 |
+
border-radius: 16px;
|
| 210 |
+
box-shadow: 0 20px 50px rgba(0,0,0,0.4);
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.visual-label {
|
| 214 |
+
display: block;
|
| 215 |
+
text-align: center;
|
| 216 |
+
margin-top: 1.5rem;
|
| 217 |
+
color: var(--text-dim);
|
| 218 |
+
font-weight: 500;
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
/* Results Table */
|
| 222 |
+
.table-wrapper {
|
| 223 |
+
overflow-x: auto;
|
| 224 |
+
background: var(--glass);
|
| 225 |
+
border-radius: 20px;
|
| 226 |
+
border: 1px solid var(--glass-border);
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
table {
|
| 230 |
+
width: 100%;
|
| 231 |
+
border-collapse: collapse;
|
| 232 |
+
text-align: left;
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
th, td {
|
| 236 |
+
padding: 1.5rem;
|
| 237 |
+
border-bottom: 1px solid var(--glass-border);
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
th {
|
| 241 |
+
background: rgba(255,255,255,0.05);
|
| 242 |
+
font-weight: 700;
|
| 243 |
+
text-transform: uppercase;
|
| 244 |
+
font-size: 0.75rem;
|
| 245 |
+
letter-spacing: 0.1em;
|
| 246 |
+
color: var(--text-dim);
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
.improvement {
|
| 250 |
+
color: #10b981;
|
| 251 |
+
font-weight: 800;
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
/* POMDP Info */
|
| 255 |
+
.pomdp-box {
|
| 256 |
+
display: grid;
|
| 257 |
+
grid-template-columns: 1fr 1fr;
|
| 258 |
+
gap: 2rem;
|
| 259 |
+
margin-top: 3rem;
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
.state-card {
|
| 263 |
+
background: var(--glass);
|
| 264 |
+
padding: 2rem;
|
| 265 |
+
border-radius: 20px;
|
| 266 |
+
border-left: 4px solid var(--primary);
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
.state-card h4 {
|
| 270 |
+
margin-bottom: 1rem;
|
| 271 |
+
color: var(--text);
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
/* Footer */
|
| 275 |
+
footer {
|
| 276 |
+
padding: 6rem 0;
|
| 277 |
+
text-align: center;
|
| 278 |
+
border-top: 1px solid var(--glass-border);
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
.footer-links {
|
| 282 |
+
display: flex;
|
| 283 |
+
justify-content: center;
|
| 284 |
+
gap: 2rem;
|
| 285 |
+
margin-bottom: 2rem;
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
.footer-links a {
|
| 289 |
+
color: var(--text-dim);
|
| 290 |
+
text-decoration: none;
|
| 291 |
+
font-weight: 600;
|
| 292 |
+
transition: color 0.3s;
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
.footer-links a:hover {
|
| 296 |
+
color: var(--primary);
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
@media (max-width: 768px) {
|
| 300 |
+
.pomdp-box {
|
| 301 |
+
grid-template-columns: 1fr;
|
| 302 |
+
}
|
| 303 |
+
h1 { font-size: 3rem; }
|
| 304 |
+
.pillars-grid { grid-template-columns: 1fr; }
|
| 305 |
+
}
|
| 306 |
+
</style>
|
| 307 |
+
</head>
|
| 308 |
+
<body>
|
| 309 |
+
|
| 310 |
+
<div class="container">
|
| 311 |
+
<!-- Hero Section -->
|
| 312 |
+
<header>
|
| 313 |
+
<div class="badge">OpenEnv Hackathon 2026</div>
|
| 314 |
+
<h1>MolForge</h1>
|
| 315 |
+
<p class="hero-tagline">A verifier-driven reinforcement learning environment for oncology drug discovery, where the LLM is the scientist, not the judge.</p>
|
| 316 |
+
<div class="cta-group">
|
| 317 |
+
<a href="https://colab.research.google.com/drive/1c6npGkGNbbbd8XFNeS6zInBpopLnJ4W4?usp=sharing" target="_blank" class="btn btn-primary">
|
| 318 |
+
Launch Training Notebook
|
| 319 |
+
</a>
|
| 320 |
+
<a href="#pillars" class="btn btn-secondary">Explore the Pillars</a>
|
| 321 |
+
</div>
|
| 322 |
+
</header>
|
| 323 |
+
|
| 324 |
+
<!-- POMDP Section -->
|
| 325 |
+
<section id="architecture">
|
| 326 |
+
<div class="section-header">
|
| 327 |
+
<h2>Scientific Architecture</h2>
|
| 328 |
+
<p>MolForge operates as a Partially Observable Markov Decision Process (POMDP), forcing models to operate under real-world uncertainty.</p>
|
| 329 |
+
</div>
|
| 330 |
+
|
| 331 |
+
<div class="visual-container">
|
| 332 |
+
<img src="assets/molforge_architecture.png" alt="MolForge Architecture">
|
| 333 |
+
<span class="visual-label">Closed-loop scientific feedback architecture</span>
|
| 334 |
+
</div>
|
| 335 |
+
|
| 336 |
+
<div class="pomdp-box">
|
| 337 |
+
<div class="state-card">
|
| 338 |
+
<h4>Hidden Reality</h4>
|
| 339 |
+
<p>The ground-truth scoring for potency, safety, and synthesizability. Includes late-stage mutation traps that only evidence can reveal.</p>
|
| 340 |
+
</div>
|
| 341 |
+
<div class="state-card">
|
| 342 |
+
<h4>Visible Evidence</h4>
|
| 343 |
+
<p>Noisy assay reports from RDKit and TDC, remaining budget, and structured feedback from the governance board.</p>
|
| 344 |
+
</div>
|
| 345 |
+
</div>
|
| 346 |
+
</section>
|
| 347 |
+
|
| 348 |
+
<!-- Pillars Section -->
|
| 349 |
+
<section id="pillars">
|
| 350 |
+
<div class="section-header">
|
| 351 |
+
<h2>The Seven Pillars</h2>
|
| 352 |
+
<p>Beyond simple molecule generation: a complete medicinal chemistry workflow optimizer.</p>
|
| 353 |
+
</div>
|
| 354 |
+
|
| 355 |
+
<div class="pillars-grid">
|
| 356 |
+
<div class="pillar-card">
|
| 357 |
+
<div class="pillar-icon">🧪</div>
|
| 358 |
+
<h3>Verifier-First</h3>
|
| 359 |
+
<p>The LLM is held accountable by RDKit and TDC simulation engines. It must justify every decision with verifiable data.</p>
|
| 360 |
+
</div>
|
| 361 |
+
<div class="pillar-card">
|
| 362 |
+
<div class="pillar-icon">🧬</div>
|
| 363 |
+
<h3>Physics Grounded</h3>
|
| 364 |
+
<p>Heuristic docking scores simulate pocket matching, lipophilic fit, and polarity clash in milliseconds.</p>
|
| 365 |
+
</div>
|
| 366 |
+
<div class="pillar-card">
|
| 367 |
+
<div class="pillar-icon">🔄</div>
|
| 368 |
+
<h3>Self-Correction</h3>
|
| 369 |
+
<p>A structured loop where agents receive reviews on their edits and iteratively repair candidates.</p>
|
| 370 |
+
</div>
|
| 371 |
+
<div class="pillar-card">
|
| 372 |
+
<div class="pillar-icon">📊</div>
|
| 373 |
+
<h3>Decomposed Rewards</h3>
|
| 374 |
+
<p>Fine-grained observability into research, edits, and coordination—not just a single vague scalar.</p>
|
| 375 |
+
</div>
|
| 376 |
+
<div class="pillar-card">
|
| 377 |
+
<div class="pillar-icon">🔬</div>
|
| 378 |
+
<h3>Evidence-Based</h3>
|
| 379 |
+
<p>Constant, verifiable reviews drive the model toward sound scientific design rather than pattern matching.</p>
|
| 380 |
+
</div>
|
| 381 |
+
<div class="pillar-card">
|
| 382 |
+
<div class="pillar-icon">🎓</div>
|
| 383 |
+
<h3>Curriculum Learning</h3>
|
| 384 |
+
<p>Partial credit "breadcrumbs" for early RL exploration, transitioning to strict evaluation for grading.</p>
|
| 385 |
+
</div>
|
| 386 |
+
<div class="pillar-card">
|
| 387 |
+
<div class="pillar-icon">🤝</div>
|
| 388 |
+
<h3>Governance</h3>
|
| 389 |
+
<p>Multi-agent specialist board reviews every plan and execution to ensure rigor and safety.</p>
|
| 390 |
+
</div>
|
| 391 |
+
</div>
|
| 392 |
+
</section>
|
| 393 |
+
|
| 394 |
+
<!-- Results Section -->
|
| 395 |
+
<section id="results">
|
| 396 |
+
<div class="section-header">
|
| 397 |
+
<h2>Training & Performance</h2>
|
| 398 |
+
<p>Comparing the Supervised Fine-Tuning (SFT) baseline against the final GRPO-trained policy.</p>
|
| 399 |
+
</div>
|
| 400 |
+
|
| 401 |
+
<div class="visual-container">
|
| 402 |
+
<img src="assets/reward_curve.png" alt="Reward Curve">
|
| 403 |
+
<span class="visual-label">Learning progression from sparse rewards to consistent submissions</span>
|
| 404 |
+
</div>
|
| 405 |
+
|
| 406 |
+
<div class="table-wrapper">
|
| 407 |
+
<table>
|
| 408 |
+
<thead>
|
| 409 |
+
<tr>
|
| 410 |
+
<th>Scenario Difficulty</th>
|
| 411 |
+
<th>Before (SFT)</th>
|
| 412 |
+
<th>After (RL)</th>
|
| 413 |
+
<th>Improvement</th>
|
| 414 |
+
</tr>
|
| 415 |
+
</thead>
|
| 416 |
+
<tbody>
|
| 417 |
+
<tr>
|
| 418 |
+
<td><strong>Level 0: Easy</strong></td>
|
| 419 |
+
<td>0.1167</td>
|
| 420 |
+
<td>0.1295</td>
|
| 421 |
+
<td class="improvement">+10.9%</td>
|
| 422 |
+
</tr>
|
| 423 |
+
<tr>
|
| 424 |
+
<td><strong>Level 1: Medium</strong></td>
|
| 425 |
+
<td>0.1167</td>
|
| 426 |
+
<td>0.1278</td>
|
| 427 |
+
<td class="improvement">+9.5%</td>
|
| 428 |
+
</tr>
|
| 429 |
+
<tr>
|
| 430 |
+
<td><strong>Level 2: Hard</strong></td>
|
| 431 |
+
<td>0.0800</td>
|
| 432 |
+
<td>0.0866</td>
|
| 433 |
+
<td class="improvement">+8.3%</td>
|
| 434 |
+
</tr>
|
| 435 |
+
</tbody>
|
| 436 |
+
</table>
|
| 437 |
+
</div>
|
| 438 |
+
|
| 439 |
+
<div style="margin-top: 4rem;">
|
| 440 |
+
<img src="assets/Logs.png" alt="Training Logs" style="width: 100%; border-radius: 20px; border: 1px solid var(--glass-border);">
|
| 441 |
+
<span class="visual-label">Detailed action telemetry and governance history</span>
|
| 442 |
+
</div>
|
| 443 |
+
</section>
|
| 444 |
+
|
| 445 |
+
<!-- Footer -->
|
| 446 |
+
<footer>
|
| 447 |
+
<div class="footer-links">
|
| 448 |
+
<a href="https://github.com/Adhitya-Vardhan/molt_lab" target="_blank">GitHub Repository</a>
|
| 449 |
+
<a href="https://huggingface.co/Adhitya122/molforge-grpo-oncology" target="_blank">Model Card</a>
|
| 450 |
+
<a href="https://colab.research.google.com/drive/1c6npGkGNbbbd8XFNeS6zInBpopLnJ4W4?usp=sharing" target="_blank">Colab Notebook</a>
|
| 451 |
+
</div>
|
| 452 |
+
<p style="color: var(--text-dim); font-size: 0.9rem;">Built for the OpenEnv Hackathon 2026</p>
|
| 453 |
+
</footer>
|
| 454 |
+
</div>
|
| 455 |
+
|
| 456 |
+
</body>
|
| 457 |
+
</html>
|
molforge_grpo_official_submission.ipynb
CHANGED
|
@@ -54,7 +54,7 @@
|
|
| 54 |
"os.environ[\"MOLFORGE_REWARD_MODE\"] = \"curriculum\"\n",
|
| 55 |
"os.environ[\"MOLFORGE_TRAINING_RANDOMIZATION\"] = \"1\"\n",
|
| 56 |
"\n",
|
| 57 |
-
"RL_MAX_STEPS =
|
| 58 |
"NUM_GENERATIONS = 2\n",
|
| 59 |
"PER_DEVICE_BATCH = 2\n",
|
| 60 |
"GRAD_ACCUM = 4\n",
|
|
@@ -69,7 +69,9 @@
|
|
| 69 |
"PLOT_DIR = OUTPUT_DIR / \"plots\"\n",
|
| 70 |
"\n",
|
| 71 |
"OUTPUT_DIR.mkdir(parents=True, exist_ok=True)\n",
|
| 72 |
-
"PLOT_DIR.mkdir(parents=True, exist_ok=True)"
|
|
|
|
|
|
|
| 73 |
]
|
| 74 |
},
|
| 75 |
{
|
|
@@ -88,70 +90,77 @@
|
|
| 88 |
"outputs": [],
|
| 89 |
"source": [
|
| 90 |
"import json\n",
|
|
|
|
| 91 |
"from typing import Any, Dict, Tuple\n",
|
| 92 |
-
"from inference_common import
|
| 93 |
-
" MolForgeAction,\n",
|
| 94 |
-
" attach_reasoning_fields,\n",
|
| 95 |
-
" attach_team_messages,\n",
|
| 96 |
-
" extract_json,\n",
|
| 97 |
-
")\n",
|
| 98 |
"from server.molforge_environment import MolForgeEnvironment\n",
|
| 99 |
-
"
|
|
|
|
| 100 |
"\n",
|
| 101 |
"def replay_to_state(record: dict[str, Any]) -> MolForgeEnvironment:\n",
|
| 102 |
" env = MolForgeEnvironment()\n",
|
| 103 |
-
"
|
| 104 |
-
"
|
| 105 |
-
"
|
|
|
|
|
|
|
|
|
|
| 106 |
" return env\n",
|
| 107 |
"\n",
|
| 108 |
-
"def evaluate_completion(prompt_str
|
| 109 |
-
" diagnostics = {\"valid_json\": False}\n",
|
| 110 |
" try:\n",
|
| 111 |
" action_dict = extract_json(completion_str)\n",
|
| 112 |
" action = MolForgeAction(**action_dict)\n",
|
|
|
|
| 113 |
" except Exception:\n",
|
| 114 |
-
" return -1.
|
| 115 |
"\n",
|
| 116 |
-
" diagnostics[\"valid_json\"] = True\n",
|
| 117 |
" env = replay_to_state(record)\n",
|
| 118 |
-
" \n",
|
| 119 |
-
" # Create empty observation and attach reasoning\n",
|
| 120 |
" observation = env._build_observation(reward=0.0, done=False, reward_components=[])\n",
|
| 121 |
" action = attach_team_messages(observation, attach_reasoning_fields(observation, action))\n",
|
| 122 |
-
" \n",
|
| 123 |
-
" # Step the OpenEnv environment\n",
|
| 124 |
" next_observation = env.step(action)\n",
|
| 125 |
-
" reward = float(next_observation.reward)\n",
|
| 126 |
-
" grader_scores = next_observation.metadata.get(\"terminal_grader_scores\", {})\n",
|
| 127 |
" \n",
|
| 128 |
-
" # --- ANTI-REWARD
|
| 129 |
-
"
|
| 130 |
-
"
|
| 131 |
-
"
|
| 132 |
-
"
|
| 133 |
-
"
|
| 134 |
-
"
|
| 135 |
-
"
|
| 136 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
"\n",
|
| 138 |
-
"
|
| 139 |
-
"
|
| 140 |
-
"
|
| 141 |
-
"
|
| 142 |
-
"
|
| 143 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
"\n",
|
| 145 |
"def molforge_reward_func(prompts, completions, **kwargs) -> list[float]:\n",
|
| 146 |
" rewards = []\n",
|
| 147 |
-
"
|
| 148 |
-
"
|
| 149 |
-
"
|
| 150 |
-
" prompt_str = prompt_list[-1][\"content\"] if isinstance(prompt_list, list) else str(prompt_list)\n",
|
| 151 |
-
" completion_str = completion[0][\"content\"] if isinstance(completion, list) else str(completion)\n",
|
| 152 |
-
" reward, _ = evaluate_completion(prompt_str, completion_str, record)\n",
|
| 153 |
" rewards.append(reward)\n",
|
| 154 |
-
"
|
|
|
|
|
|
|
| 155 |
]
|
| 156 |
},
|
| 157 |
{
|
|
@@ -169,9 +178,9 @@
|
|
| 169 |
"source": [
|
| 170 |
"from unsloth import FastLanguageModel\n",
|
| 171 |
"\n",
|
| 172 |
-
"# Set this to your SFT checkpoint\n",
|
| 173 |
-
"#
|
| 174 |
-
"SFT_ADAPTER_PATH = \"/
|
| 175 |
"\n",
|
| 176 |
"print(\"Loading model and applying Unsloth optimizations...\")\n",
|
| 177 |
"model, tokenizer = FastLanguageModel.from_pretrained(\n",
|
|
@@ -202,47 +211,91 @@
|
|
| 202 |
"metadata": {},
|
| 203 |
"outputs": [],
|
| 204 |
"source": [
|
| 205 |
-
"from trl import GRPOConfig, GRPOTrainer\n",
|
| 206 |
"from datasets import Dataset\n",
|
| 207 |
"from scripts.generate_sft_compact_policy_v4_dataset import compact_action_payload, COMPACT_ACTION_SYSTEM_PROMPT\n",
|
|
|
|
|
|
|
| 208 |
"\n",
|
| 209 |
-
"
|
| 210 |
-
"
|
| 211 |
-
"
|
| 212 |
-
"
|
| 213 |
-
"
|
| 214 |
-
"
|
| 215 |
-
"
|
| 216 |
-
"
|
| 217 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
" \"prompt\": [\n",
|
| 219 |
" {\"role\": \"system\", \"content\": COMPACT_ACTION_SYSTEM_PROMPT},\n",
|
| 220 |
-
" {\"role\": \"user\", \"content\":
|
| 221 |
" ],\n",
|
| 222 |
-
" \"record\":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
" })\n",
|
| 224 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
"\n",
|
| 226 |
-
"
|
|
|
|
|
|
|
| 227 |
"\n",
|
| 228 |
-
"
|
| 229 |
-
"
|
| 230 |
-
"
|
| 231 |
-
"
|
| 232 |
-
"
|
| 233 |
-
"
|
| 234 |
-
"
|
| 235 |
-
"
|
| 236 |
-
"
|
| 237 |
-
"
|
| 238 |
-
"
|
| 239 |
-
"
|
| 240 |
-
"
|
| 241 |
-
" report_to
|
| 242 |
-
" log_completions
|
| 243 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
"\n",
|
| 245 |
-
"# Initialize Trainer\n",
|
| 246 |
"trainer = GRPOTrainer(\n",
|
| 247 |
" model=model,\n",
|
| 248 |
" reward_funcs=molforge_reward_func,\n",
|
|
@@ -256,7 +309,7 @@
|
|
| 256 |
"\n",
|
| 257 |
"print(f\"Training complete. Saving adapters to {ADAPTER_SAVE_DIR}\")\n",
|
| 258 |
"trainer.save_model(str(ADAPTER_SAVE_DIR))\n",
|
| 259 |
-
"tokenizer.save_pretrained(str(ADAPTER_SAVE_DIR))"
|
| 260 |
]
|
| 261 |
}
|
| 262 |
],
|
|
|
|
| 54 |
"os.environ[\"MOLFORGE_REWARD_MODE\"] = \"curriculum\"\n",
|
| 55 |
"os.environ[\"MOLFORGE_TRAINING_RANDOMIZATION\"] = \"1\"\n",
|
| 56 |
"\n",
|
| 57 |
+
"RL_MAX_STEPS = 300\n",
|
| 58 |
"NUM_GENERATIONS = 2\n",
|
| 59 |
"PER_DEVICE_BATCH = 2\n",
|
| 60 |
"GRAD_ACCUM = 4\n",
|
|
|
|
| 69 |
"PLOT_DIR = OUTPUT_DIR / \"plots\"\n",
|
| 70 |
"\n",
|
| 71 |
"OUTPUT_DIR.mkdir(parents=True, exist_ok=True)\n",
|
| 72 |
+
"PLOT_DIR.mkdir(parents=True, exist_ok=True)\n",
|
| 73 |
+
"LOG_DIR = OUTPUT_DIR / \"logs\"\n",
|
| 74 |
+
"LOG_DIR.mkdir(parents=True, exist_ok=True)\n"
|
| 75 |
]
|
| 76 |
},
|
| 77 |
{
|
|
|
|
| 90 |
"outputs": [],
|
| 91 |
"source": [
|
| 92 |
"import json\n",
|
| 93 |
+
"import time\n",
|
| 94 |
"from typing import Any, Dict, Tuple\n",
|
| 95 |
+
"from inference_common import MolForgeAction, attach_reasoning_fields, attach_team_messages, extract_json\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
"from server.molforge_environment import MolForgeEnvironment\n",
|
| 97 |
+
"\n",
|
| 98 |
+
"COMPLETION_LOG = LOG_DIR / \"completion_diagnostics.jsonl\"\n",
|
| 99 |
"\n",
|
| 100 |
"def replay_to_state(record: dict[str, Any]) -> MolForgeEnvironment:\n",
|
| 101 |
" env = MolForgeEnvironment()\n",
|
| 102 |
+
" if record.get(\"randomized\"): os.environ[\"MOLFORGE_TRAINING_RANDOMIZATION\"] = \"1\"\n",
|
| 103 |
+
" os.environ[\"MOLFORGE_RAND_SEED\"] = str(record.get(\"random_seed\", \"rl\"))\n",
|
| 104 |
+
" observation = env.reset()\n",
|
| 105 |
+
" for action_payload in record.get(\"pre_actions\", []):\n",
|
| 106 |
+
" action = MolForgeAction(**action_payload)\n",
|
| 107 |
+
" observation = env.step(attach_team_messages(observation, attach_reasoning_fields(observation, action)))\n",
|
| 108 |
" return env\n",
|
| 109 |
"\n",
|
| 110 |
+
"def evaluate_completion(prompt_str, completion_str, record) -> Tuple[float, dict]:\n",
|
|
|
|
| 111 |
" try:\n",
|
| 112 |
" action_dict = extract_json(completion_str)\n",
|
| 113 |
" action = MolForgeAction(**action_dict)\n",
|
| 114 |
+
" valid_json = True\n",
|
| 115 |
" except Exception:\n",
|
| 116 |
+
" return -1.5, {\"valid_json\": False, \"action_type\": \"invalid\"}\n",
|
| 117 |
"\n",
|
|
|
|
| 118 |
" env = replay_to_state(record)\n",
|
|
|
|
|
|
|
| 119 |
" observation = env._build_observation(reward=0.0, done=False, reward_components=[])\n",
|
| 120 |
" action = attach_team_messages(observation, attach_reasoning_fields(observation, action))\n",
|
|
|
|
|
|
|
| 121 |
" next_observation = env.step(action)\n",
|
|
|
|
|
|
|
| 122 |
" \n",
|
| 123 |
+
" # --- ANTI-REWARD HACKING FILTER ---\n",
|
| 124 |
+
" # We manually sum only the scientific reward components, ignoring \"chatter\" rewards\n",
|
| 125 |
+
" filtered_reward = 0.0\n",
|
| 126 |
+
" keep_components = {\n",
|
| 127 |
+
" \"edit_delta\", \"submission_quality\", \"hard_constraints\", \"baseline_gate\",\n",
|
| 128 |
+
" \"submission_evidence\", \"curriculum_terminal_progress\", \"curriculum_evidence_gate\"\n",
|
| 129 |
+
" }\n",
|
| 130 |
+
" penalties = {\"invalid_action\", \"budget_exhausted\", \"step_limit\", \"policy_veto\", \"loop_penalty\"}\n",
|
| 131 |
+
" \n",
|
| 132 |
+
" for component in next_observation.reward_components:\n",
|
| 133 |
+
" if component.name in keep_components:\n",
|
| 134 |
+
" filtered_reward += component.value\n",
|
| 135 |
+
" elif component.name in penalties:\n",
|
| 136 |
+
" filtered_reward += component.value\n",
|
| 137 |
"\n",
|
| 138 |
+
" # Add a mandatory time pressure penalty for every step\n",
|
| 139 |
+
" filtered_reward -= 0.15 \n",
|
| 140 |
+
" \n",
|
| 141 |
+
" grader_scores = next_observation.metadata.get(\"terminal_grader_scores\", {})\n",
|
| 142 |
+
" \n",
|
| 143 |
+
" # Extra multipliers for reaching the goal\n",
|
| 144 |
+
" if action.action_type == \"submit\" and grader_scores.get(\"submission_score\", 0) > 0:\n",
|
| 145 |
+
" filtered_reward += float(grader_scores[\"submission_score\"]) * 4.0\n",
|
| 146 |
+
" \n",
|
| 147 |
+
" reward = round(filtered_reward, 4)\n",
|
| 148 |
+
" \n",
|
| 149 |
+
" return reward, {\n",
|
| 150 |
+
" \"valid_json\": True, \"action_type\": action.action_type, \"reward\": reward, \n",
|
| 151 |
+
" \"done\": next_observation.done, \"scores\": grader_scores, \n",
|
| 152 |
+
" \"raw_completion\": completion_str, \"timestamp\": time.time()\n",
|
| 153 |
+
" }\n",
|
| 154 |
"\n",
|
| 155 |
"def molforge_reward_func(prompts, completions, **kwargs) -> list[float]:\n",
|
| 156 |
" rewards = []\n",
|
| 157 |
+
" for i in range(len(completions)):\n",
|
| 158 |
+
" record = {\"pre_actions\": kwargs[\"record\"][i][\"pre_actions\"] if \"record\" in kwargs else []}\n",
|
| 159 |
+
" reward, diagnostics = evaluate_completion(\"\", completions[i][0][\"content\"], record)\n",
|
|
|
|
|
|
|
|
|
|
| 160 |
" rewards.append(reward)\n",
|
| 161 |
+
" with open(COMPLETION_LOG, \"a\") as f:\n",
|
| 162 |
+
" f.write(json.dumps(diagnostics) + \"\\n\")\n",
|
| 163 |
+
" return rewards\n"
|
| 164 |
]
|
| 165 |
},
|
| 166 |
{
|
|
|
|
| 178 |
"source": [
|
| 179 |
"from unsloth import FastLanguageModel\n",
|
| 180 |
"\n",
|
| 181 |
+
"# Set this to your SFT checkpoint Deployed to hugging face space \n",
|
| 182 |
+
"# SFT trained on only to mimic the response behavioiur of the model (structured responses visit the hf blog for more detailed explanation )\n",
|
| 183 |
+
"SFT_ADAPTER_PATH = \"Adhitya122/qwen3_5_2b_molforge_sft_v4\"\n",
|
| 184 |
"\n",
|
| 185 |
"print(\"Loading model and applying Unsloth optimizations...\")\n",
|
| 186 |
"model, tokenizer = FastLanguageModel.from_pretrained(\n",
|
|
|
|
| 211 |
"metadata": {},
|
| 212 |
"outputs": [],
|
| 213 |
"source": [
|
|
|
|
| 214 |
"from datasets import Dataset\n",
|
| 215 |
"from scripts.generate_sft_compact_policy_v4_dataset import compact_action_payload, COMPACT_ACTION_SYSTEM_PROMPT\n",
|
| 216 |
+
"from inference_common import heuristic_team_action\n",
|
| 217 |
+
"import random\n",
|
| 218 |
"\n",
|
| 219 |
+
"def build_dynamic_prompts(episodes=50, max_turns=5) -> Dataset:\n",
|
| 220 |
+
" \"\"\"Generates training prompts by playing the environment with a heuristic expert.\"\"\"\n",
|
| 221 |
+
" print(f\"Generating {episodes} episodes of dynamic prompts...\")\n",
|
| 222 |
+
" records = []\n",
|
| 223 |
+
" env = MolForgeEnvironment()\n",
|
| 224 |
+
" \n",
|
| 225 |
+
" for _ in range(episodes):\n",
|
| 226 |
+
" observation = env.reset()\n",
|
| 227 |
+
" pre_actions = []\n",
|
| 228 |
+
" \n",
|
| 229 |
+
" for _ in range(max_turns):\n",
|
| 230 |
+
" if observation.done:\n",
|
| 231 |
+
" break\n",
|
| 232 |
+
" \n",
|
| 233 |
+
" # Capture the current state as a prompt\n",
|
| 234 |
+
" prompt_payload = compact_action_payload(observation)\n",
|
| 235 |
+
" records.append({\n",
|
| 236 |
" \"prompt\": [\n",
|
| 237 |
" {\"role\": \"system\", \"content\": COMPACT_ACTION_SYSTEM_PROMPT},\n",
|
| 238 |
+
" {\"role\": \"user\", \"content\": json.dumps(prompt_payload)}\n",
|
| 239 |
" ],\n",
|
| 240 |
+
" \"record\": {\n",
|
| 241 |
+
" \"scenario_id\": observation.scenario_id,\n",
|
| 242 |
+
" \"difficulty\": observation.difficulty,\n",
|
| 243 |
+
" \"step_index\": observation.step_index,\n",
|
| 244 |
+
" \"pre_actions\": list(pre_actions),\n",
|
| 245 |
+
" \"randomized\": True,\n",
|
| 246 |
+
" \"random_seed\": \"dynamic-rl\"\n",
|
| 247 |
+
" }\n",
|
| 248 |
" })\n",
|
| 249 |
+
" \n",
|
| 250 |
+
" # Use expert to move to the next state\n",
|
| 251 |
+
" action = heuristic_team_action(observation)\n",
|
| 252 |
+
" observation = env.step(action)\n",
|
| 253 |
+
" pre_actions.append({\"action_type\": action.action_type, \"acting_role\": action.acting_role})\n",
|
| 254 |
+
" \n",
|
| 255 |
+
" random.shuffle(records)\n",
|
| 256 |
+
" return Dataset.from_list(records)\n",
|
| 257 |
+
"\n",
|
| 258 |
+
"# Generate the dataset dynamically (no .jsonl needed!)\n",
|
| 259 |
+
"dataset = build_dynamic_prompts(episodes=20, max_turns=6)\n",
|
| 260 |
+
"print(f\"Dynamic dataset created with {len(dataset)} prompt states.\")\n"
|
| 261 |
+
]
|
| 262 |
+
},
|
| 263 |
+
{
|
| 264 |
+
"cell_type": "code",
|
| 265 |
+
"execution_count": null,
|
| 266 |
+
"metadata": {},
|
| 267 |
+
"outputs": [],
|
| 268 |
+
"source": [
|
| 269 |
+
"from trl import GRPOConfig, GRPOTrainer\n",
|
| 270 |
+
"import inspect\n",
|
| 271 |
+
"import torch\n",
|
| 272 |
"\n",
|
| 273 |
+
"# Check for BF16 support (T4 does not support it, A100/L4 do)\n",
|
| 274 |
+
"has_bf16 = torch.cuda.is_bf16_supported()\n",
|
| 275 |
+
"print(f\"GPU supports BF16: {has_bf16}\")\n",
|
| 276 |
"\n",
|
| 277 |
+
"config_kwargs = {\n",
|
| 278 |
+
" \"output_dir\": str(OUTPUT_DIR),\n",
|
| 279 |
+
" \"learning_rate\": LEARNING_RATE,\n",
|
| 280 |
+
" \"per_device_train_batch_size\": PER_DEVICE_BATCH,\n",
|
| 281 |
+
" \"gradient_accumulation_steps\": GRAD_ACCUM,\n",
|
| 282 |
+
" \"max_prompt_length\": MAX_PROMPT_LENGTH,\n",
|
| 283 |
+
" \"max_completion_length\": MAX_COMPLETION_LENGTH,\n",
|
| 284 |
+
" \"num_generations\": NUM_GENERATIONS,\n",
|
| 285 |
+
" \"max_steps\": RL_MAX_STEPS,\n",
|
| 286 |
+
" \"logging_steps\": 1,\n",
|
| 287 |
+
" \"save_steps\": 25,\n",
|
| 288 |
+
" \"bf16\": has_bf16,\n",
|
| 289 |
+
" \"fp16\": not has_bf16,\n",
|
| 290 |
+
" \"report_to\": \"none\",\n",
|
| 291 |
+
" \"log_completions\": True,\n",
|
| 292 |
+
"}\n",
|
| 293 |
+
"\n",
|
| 294 |
+
"supported_params = inspect.signature(GRPOConfig.__init__).parameters\n",
|
| 295 |
+
"filtered_kwargs = {k: v for k, v in config_kwargs.items() if k in supported_params}\n",
|
| 296 |
+
"\n",
|
| 297 |
+
"training_args = GRPOConfig(**filtered_kwargs)\n",
|
| 298 |
"\n",
|
|
|
|
| 299 |
"trainer = GRPOTrainer(\n",
|
| 300 |
" model=model,\n",
|
| 301 |
" reward_funcs=molforge_reward_func,\n",
|
|
|
|
| 309 |
"\n",
|
| 310 |
"print(f\"Training complete. Saving adapters to {ADAPTER_SAVE_DIR}\")\n",
|
| 311 |
"trainer.save_model(str(ADAPTER_SAVE_DIR))\n",
|
| 312 |
+
"tokenizer.save_pretrained(str(ADAPTER_SAVE_DIR))\n"
|
| 313 |
]
|
| 314 |
}
|
| 315 |
],
|