<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://kainsk1.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://kainsk1.github.io/" rel="alternate" type="text/html" /><updated>2026-04-22T18:52:05-04:00</updated><id>https://kainsk1.github.io/feed.xml</id><title type="html">Kains Kavuluri</title><subtitle>Personal homepage of Kains Kavuluri, MSc student in AI at Université de Montréal &amp; Mila — Quebec AI Institute.
</subtitle><author><name>Kains Kavuluri</name><email>kainspraveen33@gmail.com</email></author><entry><title type="html">Discrete Diffusion for Molecular Generation with RL and Natural Language Steering</title><link href="https://kainsk1.github.io/blog/2026/morpheus/" rel="alternate" type="text/html" title="Discrete Diffusion for Molecular Generation with RL and Natural Language Steering" /><published>2026-04-15T00:00:00-04:00</published><updated>2026-04-15T00:00:00-04:00</updated><id>https://kainsk1.github.io/blog/2026/morpheus</id><content type="html" xml:base="https://kainsk1.github.io/blog/2026/morpheus/"><![CDATA[<script>MathJax = { tex: { inlineMath: [['$','$']], displayMath: [['$$','$$']] } };</script>

<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>

<p>Designing drug-like molecules is, at its core, a search problem over an enormous discrete space. This post walks through how we built a modular architecture that learns the grammar of that space, then learned to steer it — first with reinforcement learning toward measurable chemical properties, then with natural language descriptions.</p>

<h2 id="the-problem-with-existing-approaches">The problem with existing approaches</h2>

<p>Traditional generative models for molecules have three recurring issues.</p>

<p>VAEs can only optimize in the continuous latent space, not directly in molecular space. GANs require expensive compute and suffer from mode collapse. Autoregressive models generate token by token left to right: if the model makes a mistake at position <em>i</em>, everything after it is already wrong — it either never closes the ring it opened, or closes it in the wrong place.</p>

<p>The validity problem is especially sharp. Standard SMILES notation encodes molecules as text strings, but most randomly sampled strings are chemically invalid: unclosed rings, violated valence rules, mismatched parentheses. Models have to learn chemistry syntax, and many fail.</p>

<h2 id="why-selfies">Why SELFIES</h2>

<p>We chose SELFIES (Self-Referencing Embedded Strings) as our molecular representation. Any SELFIES token sequence decodes to a valid molecule by construction. The grammar is designed so the decoder can always find a valid interpretation — even if the model guesses a ring count that exceeds what is chemically possible, SELFIES adjusts automatically.</p>

<p>This gives 100% chemical validity by construction, not by filtering or correction. Our closest comparison (TGM-DLM, 180M parameters) reaches only 87.1% validity even with a dedicated correction network as a second phase.</p>

<p>The tradeoff: SELFIES sequences are longer than SMILES, and ring closures use a count token that creates a dependency problem we return to in the failure analysis.</p>

<h2 id="architecture">Architecture</h2>

<p><img src="/assets/img/morpheus/image20.png" alt="Detailed architecture: contrastive pre-training, text conditioning, and diffusion backbone" /></p>

<p>The architecture has three coupled components. The <strong>diffusion backbone</strong> is a bidirectional transformer that takes noisy token sequences $\mathbf{x}_t$ at timestep $t \sim \mathcal{U}(0,1)$ and predicts the clean $\hat{x}_0$ directly (rather than predicting noise, as in continuous diffusion). Each of the 8 transformer blocks applies self-attention over the full SELFIES sequence, then cross-attention where the molecule tokens form the queries and the text embedding provides keys and values. The cross-attention output projection $W_O$ is zero-initialized: at the start of text-conditioning fine-tuning, cross-attention contributes nothing, so the backbone behaves identically to the pretrained unconditional model. This lets us add text conditioning without retraining from scratch.</p>

<p><strong>Contrastive pre-training</strong> (top-left) aligns the text and molecule embedding spaces before fine-tuning. SciBERT is trained while the molecule encoder stays frozen; both pass through learned projectors and are pulled together via InfoNCE loss. The result is a text encoder whose representation geometry matches chemical space — which matters because cross-attention uses these representations directly as keys and values.</p>

<p><strong>Text conditioning</strong> (bottom-left) then uses the contrastively-trained SciBERT, now frozen, passed through an MLP projector to match the transformer’s hidden dimension. During training, text conditioning is dropped with probability 0.1 (null token ∅ substituted), enabling classifier-free guidance at inference.</p>

<p><img src="/assets/img/morpheus/image13.png" alt="Classifier-free guidance at inference: conditioned and unconditioned passes combined with guidance scale" /></p>

<p>At inference, CFG runs two forward passes — one conditioned on the text prompt, one with the null token — and amplifies the difference by a guidance scale <em>s</em>:</p>

\[\varepsilon = \varepsilon_\varnothing + s \cdot (\varepsilon_c - \varepsilon_\varnothing)\]

<p>The guided logits $\hat{x}_0$ are what the MaskGIT sampler uses for token selection. Setting $s = 0$ recovers the unconditional model; higher $s$ steers more aggressively toward the text description at the cost of diversity. The training loss is $\mathcal{L} = (1-t) \cdot \text{CE}(\hat{x}_0, x_0)$: the $(1-t)$ weighting upweights nearly-clean timesteps, where prediction errors matter most for final sample quality.</p>

<p>We built and tested three scales on the same architecture: 4.4M parameters (hidden 256, 8 layers, 8 heads) on a MacBook for RL experiments; 27M parameters (hidden 512) for text conditioning; and 150M on a GPU cluster for the scaling study. Only the hidden dimension changes between scales.</p>

<h2 id="training-maskgit-style-discrete-diffusion">Training: MaskGIT-style discrete diffusion</h2>

<p>Training follows the MaskGIT approach. For each batch, take a real SELFIES sequence, randomly mask a fraction of tokens using a cosine schedule $\alpha_t = \cos^2!\left(\tfrac{t\pi}{2}\right)$, then predict the original tokens at masked positions using standard cross-entropy. One forward pass per batch.</p>

<p>The cosine schedule means early in training, most tokens are visible (easy task); later, most are masked (hard task). We apply timestep weighting so that positions at low <em>t</em> (nearly clean sequences) receive higher loss weight — these are the most critical for final output fidelity.</p>

<p>Generation reverses this process over 50 iterative steps:</p>

<p><img src="/assets/img/morpheus/image15.jpg" alt="MaskGIT training and generation: masking, parallel prediction, confidence-based locking" /></p>

<p>Start with all positions masked. At each step, predict all positions simultaneously, sample tokens, measure confidence (the softmax probability of the sampled token), lock the most confident positions, and re-mask the uncertain ones. Like solving a crossword puzzle: fill in the easiest clues first and use them as context for the harder ones.</p>

<p><strong>Pretraining results on ZINC250K</strong> (500 generated molecules):</p>

<table>
  <thead>
    <tr>
      <th>Metric</th>
      <th>Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Validity</td>
      <td>100%</td>
    </tr>
    <tr>
      <td>Uniqueness</td>
      <td>100%</td>
    </tr>
    <tr>
      <td>Novelty</td>
      <td>100%</td>
    </tr>
    <tr>
      <td>Diversity</td>
      <td>0.851</td>
    </tr>
    <tr>
      <td>Scaffold diversity</td>
      <td>954 / 1000</td>
    </tr>
    <tr>
      <td>Mean QED</td>
      <td>0.742</td>
    </tr>
    <tr>
      <td>QED KL vs ZINC250K</td>
      <td>0.022</td>
    </tr>
    <tr>
      <td>Lipinski pass rate</td>
      <td>99.4%</td>
    </tr>
  </tbody>
</table>

<p>100% novelty means zero overlap with the 249K training molecules — the model generalized rather than memorized. A QED KL of 0.022 means the generated distribution is statistically indistinguishable from the training distribution. But reproducing the distribution is not the same as controlling it. Mean QED of 0.742 is just the dataset average.</p>

<h2 id="rl-for-property-optimization-the-per-step-gradient-problem">RL for property optimization: the per-step gradient problem</h2>

<p>We used REINFORCE to optimize QED (a drug-likeness score from 0 to 1). The standard approach failed completely.</p>

<p>The naive recipe: generate a molecule, score it, compute the log probability of the sequence in a single forward pass at <em>t=0</em>, apply REINFORCE. This collapsed QED from 0.742 to 0.38.</p>

<p>The reason is not obvious until you think carefully about how MaskGIT generation works. In autoregressive language models, generation <em>is</em> the log probability computation — each forward pass generates the next token and produces its log probability. In MaskGIT, generation is 50 iterative steps with confidence-based token locking. A single forward pass at <em>t=0</em> evaluates the model under conditions it never sees during generation. The gradient flows through the wrong path.</p>

<p><img src="/assets/img/morpheus/image14.jpg" alt="RL v1 (naive proxy, fails) vs v2 (per-step accumulation, works)" /></p>

<p>The fix: instrument the generation loop. At each denoising step, when a token transitions from masked to committed, record its log probability. Sum across all 50 steps:</p>

\[\log P_\text{total} = \sum_k \log P_k\]

<p>The REINFORCE gradient now flows through the actual generation process. QED improved from 0.742 to 0.837.</p>

<p>This is the same principle as DDPO (Black et al., Berkeley 2023), which established per-step policy gradients for continuous diffusion in image generation. We arrived at it through debugging and found the connection afterward.</p>

<h3 id="reward-shaping">Reward shaping</h3>

<p>With the gradient problem fixed, we ran four configurations in sequence, each motivated by what we measured in the previous result:</p>

<p><img src="/assets/img/morpheus/image6.png" alt="RL progression: QED gains and distribution properties across reward configurations" /></p>

<p><strong>QED only:</strong> QED improved to 0.799, but the model concentrated property distributions into narrow bands. Scaffold diversity dropped from 95.4% to 88.6%. The model was finding higher-QED molecules by converging on a narrow region of chemical space.</p>

<p><strong>QED + molecular weight:</strong> Adding an MW reward term broadened the distribution back out. Mean MW shifted toward 330 Daltons (closer to drug-like range), LogP distribution improved, scaffold diversity recovered to 95.2%. QED reached 0.818.</p>

<p><strong>QED + MW + diversity penalty:</strong> Final QED 0.837, scaffold diversity 94%, uniqueness 93%. A 12.8% QED gain from baseline while maintaining structural variety.</p>

<p>Each constraint was added in response to a measured concern, not added speculatively.</p>

<h2 id="text-conditioning">Text conditioning</h2>

<p>RL optimizes a single scalar. Real drug design needs richer control. We added text conditioning by extending the architecture with cross-attention layers connecting a frozen text encoder to the transformer backbone, plus a learnable MLP projector bridging the encoder output dimension to the transformer hidden dimension.</p>

<p>We trained on ChEBI-20, a public dataset of 26,000 molecule-description pairs, and used classifier-free guidance (CFG) at inference time: generate one candidate conditioned on the text prompt and one unconditional, then amplify the difference by a scale factor. We drop text conditioning with 10% probability during training to enable this.</p>

<h3 id="contrastive-alignment">Contrastive alignment</h3>

<p>Before fine-tuning the diffusion model, we aligned text and molecule embedding spaces using CLIP-style contrastive learning (InfoNCE loss): pull together matched text-molecule pairs, push apart unmatched ones.</p>

<p><img src="/assets/img/morpheus/image7.png" alt="Contrastive alignment: InfoNCE matrix over text descriptions and SELFIES molecules" /></p>

\[\mathcal{L}_\text{InfoNCE} = -\frac{1}{N} \sum_i \log \frac{\exp(S_{ii} / \tau)}{\sum_j \exp(S_{ij} / \tau)}\]

<p>where $S_{ij}$ is the cosine similarity between text embedding $i$ and molecule embedding $j$. Pre-aligning the encoder to chemistry space before using it as a conditioning signal gave consistent improvements across all metrics.</p>

<h2 id="systematic-ablation">Systematic ablation</h2>

<p>Each step was motivated by the result of the previous one:</p>

<p><img src="/assets/img/morpheus/image19.jpg" alt="Encoder ablation across Morgan, MACCS, and atom-BLEU-2" /></p>

<table>
  <thead>
    <tr>
      <th>Configuration</th>
      <th>Morgan Tanimoto</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>BGE frozen (baseline)</td>
      <td>0.199</td>
    </tr>
    <tr>
      <td>BGE contrastive</td>
      <td>0.239</td>
    </tr>
    <tr>
      <td>SciBERT contrastive, 10 epochs</td>
      <td>0.251</td>
    </tr>
    <tr>
      <td>SciBERT contrastive, 20 epochs</td>
      <td>0.299</td>
    </tr>
    <tr>
      <td>+ Inference reranking (N=10)</td>
      <td>0.310</td>
    </tr>
  </tbody>
</table>

<p><strong>BGE vs SciBERT:</strong> Swapping from BGE-large (1024-dim, general English) to SciBERT (768-dim, chemistry-pretrained) improved performance despite the smaller dimension. Domain-specific pretraining beat parameter count.</p>

<p><strong>10 to 20 epochs:</strong> Validation loss was still decreasing at 10 epochs. Extending training produced the largest single-step improvement, 19%.</p>

<p><strong>CFG scale sweep:</strong> We tested CFG scale from 0.5 to 3.0. Clean bell curve with peak at 1.5.</p>

<p><img src="/assets/img/morpheus/image21.jpg" alt="CFG scale sweep: optimal at 1.5" /></p>

<p>Too low ignores the text; too high collapses diversity. The same pattern as text-to-image diffusion.</p>

<p><strong>Reranking:</strong> Generate 10 candidates per prompt, score with the contrastive encoder, keep the best. Small but free at inference time using the same model already trained.</p>

<h2 id="comparison-against-tgm-dlm">Comparison against TGM-DLM</h2>

<p>TGM-DLM (Gong et al., AAAI 2024) is a 180M parameter continuous embedding diffusion model — currently the strongest published method on ChEBI-20.</p>

<p><img src="/assets/img/morpheus/image10.png" alt="MACCS and atom-BLEU-2: Morpheus 27M vs TGM-DLM 180M" /></p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>Morpheus (27M)</th>
      <th>TGM-DLM (180M)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Morgan Tanimoto</td>
      <td>0.310</td>
      <td>0.688</td>
    </tr>
    <tr>
      <td>RDK Tanimoto</td>
      <td>0.418</td>
      <td>0.739</td>
    </tr>
    <tr>
      <td>MACCS Tanimoto</td>
      <td>0.651</td>
      <td>0.854</td>
    </tr>
    <tr>
      <td>Atom BLEU-2</td>
      <td>0.621</td>
      <td>0.826</td>
    </tr>
    <tr>
      <td>Chemical validity</td>
      <td>100%</td>
      <td>87.1%</td>
    </tr>
    <tr>
      <td>Hardware</td>
      <td>MacBook M2</td>
      <td>A100 GPU</td>
    </tr>
  </tbody>
</table>

<p>76% of MACCS and 75% of BLEU-2 at 15% of the parameter count. On validity: TGM-DLM reaches 87.1% with a dedicated correction network. Without that correction it sits at 78.9%. We do not need a correction phase.</p>

<p>The Morgan gap (45% of theirs) reflects the ring topology failure measured directly in the next section.</p>

<h2 id="failure-analysis-ring-topology">Failure analysis: ring topology</h2>

<p>The most useful result from the project is what we found when we stratified performance by molecular complexity.</p>

<p><img src="/assets/img/morpheus/image17.jpg" alt="Performance by ring count: 2.26x gap between acyclic and polycyclic molecules" /></p>

<p>On acyclic molecules (chains, fatty acids), we reach Morgan 0.451. On molecules with 3+ rings, we drop to 0.200. A 2.26x gap. We traced it to the token level:</p>

<table>
  <thead>
    <tr>
      <th>Ring count</th>
      <th>Ring count prediction accuracy</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0 rings</td>
      <td>96%</td>
    </tr>
    <tr>
      <td>1 ring</td>
      <td>57%</td>
    </tr>
    <tr>
      <td>2 rings</td>
      <td>44%</td>
    </tr>
    <tr>
      <td>3 rings</td>
      <td>32%</td>
    </tr>
    <tr>
      <td>5 rings</td>
      <td>17%</td>
    </tr>
    <tr>
      <td>6+ rings</td>
      <td>0%</td>
    </tr>
  </tbody>
</table>

<p>In SELFIES, ring closures use a count token that tells the decoder how many atoms back to bond with. Predicting that count correctly requires knowing which atoms appear in the intervening positions. In MaskGIT parallel decoding, those positions may still be masked when the count token is being predicted.</p>

<p>This is an architectural incompatibility between fully-parallel position prediction and closure tokens whose semantics depend on resolved nearby context. We ruled out implementation bugs through a five-check audit covering tokenizer integrity, truncation, round-trip verification, special tokens in count positions, and version consistency. All five checks clean.</p>

<p>We also documented three negative results: post-hoc EOS truncation hurts, iterative refinement adds nothing, and more denoising steps at inference time hurts. The interpretations of why are our best hypotheses, not directly measured.</p>

<p>The proposed fix is block diffusion: commit positions in groups so that count tokens see resolved context before being predicted. Not yet tested on molecular generation.</p>

<h2 id="what-we-learned">What we learned</h2>

<p><strong>Per-step RL gradients are necessary for diffusion.</strong> The naive single-pass proxy fails completely (QED collapses from 0.742 to 0.38). Per-step log-probability accumulation during the 50-step denoising process is necessary. Same principle as DDPO for continuous diffusion.</p>

<p><strong>Modularity pays off.</strong> We swapped text encoders three times, added RL to the same backbone, and reused the contrastive encoder for both conditioning and inference-time reranking — without retraining the generator from scratch. The zero-initialization of cross-attention output projections is what enables this.</p>

<p><strong>Representation sets the ceiling, not parameter count.</strong> At 27M we reach 75-76% of a 180M model on standard metrics. The remaining gap is discrete tokens vs. continuous embeddings, not model size.</p>

<p><strong>Domain-specific encoders beat larger generic ones.</strong> SciBERT at 768 dimensions outperformed BGE-large at 1024 dimensions consistently across every metric.</p>

<h2 id="what-is-next">What is next</h2>

<p>Both RL and text conditioning work independently on the same backbone. The natural next step combines them: generate a molecule that scores high on a disease-specific activity classifier while satisfying a text description of its chemical properties.</p>

<p>For rings, block diffusion is the most principled intervention. We are also investigating whether the failure pattern holds at 150M scale, which would confirm it is representational rather than a capacity issue.</p>

<p>The 150M scaling study is still in progress.</p>

<hr />

<p><em>Code available on <a href="https://github.com/kainsk1">GitHub</a>.</em></p>]]></content><author><name>Kains Kavuluri</name><email>kainspraveen33@gmail.com</email></author><category term="research" /><category term="rl" /><category term="drug-discovery" /><category term="diffusion" /><summary type="html"><![CDATA[A walkthrough of building a modular molecule generation model: MaskGIT-style discrete diffusion over SELFIES, reinforcement learning for property optimization, and natural language conditioning via contrastive SciBERT.]]></summary></entry><entry><title type="html">Hello, world</title><link href="https://kainsk1.github.io/blog/2026/welcome/" rel="alternate" type="text/html" title="Hello, world" /><published>2026-04-10T00:00:00-04:00</published><updated>2026-04-10T00:00:00-04:00</updated><id>https://kainsk1.github.io/blog/2026/welcome</id><content type="html" xml:base="https://kainsk1.github.io/blog/2026/welcome/"><![CDATA[<p>Welcome to the blog. I plan to use this space for three kinds of posts:</p>

<ol>
  <li><strong>Paper reviews</strong> — short reading notes on papers I find interesting.</li>
  <li><strong>Research logs</strong> — occasional updates on what I’m working on at Mila.</li>
  <li><strong>Tutorials</strong> — write-ups that might be useful to other grad students.</li>
</ol>

<p>Posts live in <code class="language-plaintext highlighter-rouge">_posts/</code> as Markdown files. Math works out of the box:</p>

\[\mathcal{L}(\theta) = \mathbb{E}_{x \sim \mathcal{D}}\big[\log p_\theta(x)\big].\]

<p>Code blocks are highlighted with Rouge:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">hello</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="k">return</span> <span class="sa">f</span><span class="s">"hello, </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s">"</span>
</code></pre></div></div>

<p>More soon.</p>]]></content><author><name>Kains Kavuluri</name><email>kainspraveen33@gmail.com</email></author><category term="meta" /><summary type="html"><![CDATA[A first post — what this blog is for.]]></summary></entry></feed>