Quazim0t0 commited on
Commit
a2d7d73
·
verified ·
1 Parent(s): ac304b9

Match Carbon FNS BpLogitsProcessor decoding

Browse files
Files changed (1) hide show
  1. daisychain.py +36 -21
daisychain.py CHANGED
@@ -173,19 +173,28 @@ class DaisyChain:
173
  return out
174
 
175
  # ---- FNS base-pair-level decode + score (Carbon's factorized-nucleotide approach) ----
176
- def _bp_marginals(self, logits):
177
- """Marginalize a 6-mer logit vector into six 4-way per-position base distributions [6,4]."""
178
- p = F.softmax(logits[self._kmer_ids], dim=-1) # [4096] over the full 6-mers only
 
 
 
 
 
 
 
 
179
  bp = torch.zeros(6, 4, device=logits.device)
180
  for pos in range(6):
181
  bp[pos].scatter_add_(0, self._base_at[pos], p)
182
  return bp
183
 
184
  @torch.no_grad()
185
- def generate_baselevel_stream(self, domain, length=180, temperature=1.0, top_p=0.9, prompt=""):
186
- """Base-pair-level generation: at each step marginalize the 6-mer softmax to six 4-way
187
- nucleotide distributions and sample each base with temperature/top-p (Carbon FNS-style).
188
- Deciding each base over 4 options not 6 bases at once over 4096 — avoids whole-6-mer loops."""
 
189
  m = self.models[domain]
190
  p = clean(prompt) if prompt else ""
191
  p = p[-1020:]; p = p[len(p) % 6:]
@@ -194,16 +203,11 @@ class DaisyChain:
194
  out = ""
195
  while len(out) < length:
196
  logits = m(input_ids=t).logits[0, -1].float() / max(temperature, 1e-6)
197
- bp = self._bp_marginals(logits) # [6,4]
198
- six = []
199
- for pos in range(6):
200
- probs = bp[pos]
201
- s, si = torch.sort(probs, descending=True)
202
- rm = torch.cumsum(s, -1) > top_p
203
- rm[1:] = rm[:-1].clone(); rm[0] = False
204
- q = probs.clone(); q[si[rm]] = 0; q = q / q.sum()
205
- six.append(self._idx2base[int(torch.multinomial(q, 1))])
206
- six = "".join(six)
207
  t = torch.cat([t, torch.tensor([[self._kmer_id_of[six]]], device=self.dev)], dim=1)
208
  out += six
209
  yield out[:length]
@@ -211,10 +215,15 @@ class DaisyChain:
211
  @torch.no_grad()
212
  def score(self, domain, seq):
213
  """Carbon `score_sequence` equivalent: mean per-base log-prob of the observed sequence under
214
- the base-level (marginalized) distribution. Higher = more likely. bits/base = -score/ln2."""
 
215
  m = self.models[domain]
216
- s = clean(seq); s = s[len(s) % 6:]
217
- if len(s) < 12:
 
 
 
 
218
  return float("nan")
219
  ids = [self.bos] + self.tok.encode(s, add_special_tokens=False)
220
  t = torch.tensor([ids], device=self.dev)
@@ -222,8 +231,14 @@ class DaisyChain:
222
  b2i = {"A": 0, "T": 1, "C": 2, "G": 3}
223
  tot, n = 0.0, 0
224
  for i in range(len(ids) - 1):
 
 
225
  bp = self._bp_marginals(logits[i].float()) # [6,4] predicted bases of next 6-mer
226
  nxt = s[i * 6:(i + 1) * 6]
227
  for pos, ch in enumerate(nxt):
 
 
 
 
228
  tot += math.log(max(float(bp[pos, b2i[ch]]), 1e-12)); n += 1
229
- return tot / n # mean per-base logp
 
173
  return out
174
 
175
  # ---- FNS base-pair-level decode + score (Carbon's factorized-nucleotide approach) ----
176
+ def _bp_marginals(self, logits, top_p=1.0):
177
+ """Marginalize a 6-mer logit vector into six 4-way per-position base distributions [6,4].
178
+ Matching Carbon's _BPLogitsProcessor: any top-p filtering happens at the 6-MER level
179
+ (before marginalizing), not per-base."""
180
+ kl = logits[self._kmer_ids] # [4096] 6-mer logits
181
+ if top_p < 1.0: # nucleus on the 6-mers first
182
+ sk, si = torch.sort(kl, descending=True)
183
+ rm = torch.cumsum(F.softmax(sk, -1), -1) > top_p
184
+ rm[1:] = rm[:-1].clone(); rm[0] = False
185
+ kl = kl.clone(); kl[si[rm]] = float("-inf")
186
+ p = F.softmax(kl, dim=-1)
187
  bp = torch.zeros(6, 4, device=logits.device)
188
  for pos in range(6):
189
  bp[pos].scatter_add_(0, self._base_at[pos], p)
190
  return bp
191
 
192
  @torch.no_grad()
193
+ def generate_baselevel_stream(self, domain, length=180, temperature=1.0, top_p=0.9,
194
+ prompt="", greedy=False):
195
+ """Base-pair generation exactly as Carbon's FNS BpLogitsProcessor: apply temperature +
196
+ top-p at the 6-mer level, marginalize the filtered 6-mer softmax to six 4-way base
197
+ distributions, then pick each base by multinomial (sampling) or argmax (greedy)."""
198
  m = self.models[domain]
199
  p = clean(prompt) if prompt else ""
200
  p = p[-1020:]; p = p[len(p) % 6:]
 
203
  out = ""
204
  while len(out) < length:
205
  logits = m(input_ids=t).logits[0, -1].float() / max(temperature, 1e-6)
206
+ bp = self._bp_marginals(logits, top_p=(1.0 if greedy else top_p)) # [6,4]
207
+ if greedy:
208
+ six = "".join(self._idx2base[int(bp[pos].argmax())] for pos in range(6))
209
+ else:
210
+ six = "".join(self._idx2base[int(torch.multinomial(bp[pos], 1))] for pos in range(6))
 
 
 
 
 
211
  t = torch.cat([t, torch.tensor([[self._kmer_id_of[six]]], device=self.dev)], dim=1)
212
  out += six
213
  yield out[:length]
 
215
  @torch.no_grad()
216
  def score(self, domain, seq):
217
  """Carbon `score_sequence` equivalent: mean per-base log-prob of the observed sequence under
218
+ the base-level (marginalized) distribution. Higher = more likely. bits/base = -score/ln2.
219
+ Right-pads to a multiple of 6 with 'A' (Carbon's convention) and scores only real bases."""
220
  m = self.models[domain]
221
+ s = clean(seq)
222
+ orig = len(s)
223
+ r = len(s) % 6
224
+ if r:
225
+ s = s + "A" * (6 - r) # right-pad like score_sequence
226
+ if orig < 12:
227
  return float("nan")
228
  ids = [self.bos] + self.tok.encode(s, add_special_tokens=False)
229
  t = torch.tensor([ids], device=self.dev)
 
231
  b2i = {"A": 0, "T": 1, "C": 2, "G": 3}
232
  tot, n = 0.0, 0
233
  for i in range(len(ids) - 1):
234
+ if i * 6 >= orig: # don't score the padding
235
+ break
236
  bp = self._bp_marginals(logits[i].float()) # [6,4] predicted bases of next 6-mer
237
  nxt = s[i * 6:(i + 1) * 6]
238
  for pos, ch in enumerate(nxt):
239
+ if i * 6 + pos >= orig: # padding base — don't score
240
+ break
241
+ if ch not in b2i: # skip N / ambiguous bases
242
+ continue
243
  tot += math.log(max(float(bp[pos, b2i[ch]]), 1e-12)); n += 1
244
+ return tot / max(n, 1) # mean per-base logp