Default generation = base-pair (FNS), marginalized per-base sampling
Browse files
app.py
CHANGED
|
@@ -1,480 +1,480 @@
|
|
| 1 |
-
"""
|
| 2 |
-
DaisyChain β interactive routing demo (HuggingFace Space).
|
| 3 |
-
|
| 4 |
-
Paste DNA; the learned router reads how *surprised* each ~74M specialist is (bits/base)
|
| 5 |
-
plus its hidden state and hands the sequence to its home specialist β then that specialist
|
| 6 |
-
streams a continuation live. Styled after the Modular-Mind panel: animated routing cards,
|
| 7 |
-
a first-run loading notice, live token streaming. Every handler is a generator.
|
| 8 |
-
"""
|
| 9 |
-
import html as _h
|
| 10 |
-
import os
|
| 11 |
-
import json
|
| 12 |
-
import math
|
| 13 |
-
|
| 14 |
-
import gradio as gr
|
| 15 |
-
|
| 16 |
-
# ZeroGPU: @spaces.GPU allocates a GPU only for the decorated call. Falls back to a no-op
|
| 17 |
-
# decorator when `spaces` isn't installed (local / plain CPU).
|
| 18 |
-
try:
|
| 19 |
-
import spaces
|
| 20 |
-
_gpu = spaces.GPU
|
| 21 |
-
except Exception:
|
| 22 |
-
def _gpu(fn=None, **kw):
|
| 23 |
-
return fn if callable(fn) else (lambda f: f)
|
| 24 |
-
|
| 25 |
-
from daisychain import DaisyChain
|
| 26 |
-
|
| 27 |
-
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 28 |
-
MODEL_REPO = os.environ.get("DAISYCHAIN_REPO", "DaisyChainAI/daisychain-genomics")
|
| 29 |
-
DEVICE = os.environ.get("DAISYCHAIN_DEVICE", "cpu")
|
| 30 |
-
|
| 31 |
-
# code + tokenizer + router are bundled here; pull the big specialist weights from the
|
| 32 |
-
# (gated) model repo on first launch using the HF_TOKEN Space secret. No silent
|
| 33 |
-
# swallow β if the download fails we want a visible error, not a broken-but-running app.
|
| 34 |
-
if not os.path.exists(os.path.join(HERE, "eukaryote", "model.safetensors")):
|
| 35 |
-
from huggingface_hub import snapshot_download
|
| 36 |
-
snapshot_download(MODEL_REPO, local_dir=HERE,
|
| 37 |
-
token=os.environ.get("HF_TOKEN"),
|
| 38 |
-
allow_patterns=["*/model.safetensors", "tokenizer.json", "router2.pt"])
|
| 39 |
-
|
| 40 |
-
_DC = {"m": None} # lazy-loaded so CUDA is never touched at import
|
| 41 |
-
_WARMED = {"done": False} # so the "loading" notice only shows on the first run
|
| 42 |
-
|
| 43 |
-
EMOJI = {"eukaryote": "𧬠Eukaryote", "prokaryote": "π¦ Prokaryote",
|
| 44 |
-
"mrna": "π mRNA", "mrna_splice": "βοΈ mRNA-splice"}
|
| 45 |
-
# deeper, paper-friendly tones (vs the old neon dark-theme hues)
|
| 46 |
-
COLOR = {"eukaryote": "#5b4bb0", "prokaryote": "#1f7a99",
|
| 47 |
-
"mrna": "#b03a63", "mrna_splice": "#317f3f"}
|
| 48 |
-
DESC = DaisyChain.DESCRIPTIONS
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
def _moe():
|
| 52 |
-
if _DC["m"] is None:
|
| 53 |
-
_DC["m"] = DaisyChain(root=HERE, device=DEVICE)
|
| 54 |
-
return _DC["m"]
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
# ---- HTML rendering ----------------------------------------------------------------
|
| 58 |
-
# Editorial "scientific-paper" aesthetic borrowed from Carbon's demo (cream paper, ink,
|
| 59 |
-
# green accent, mono uppercase headers) but made our own: a daisy-gold second accent and
|
| 60 |
-
# the chain-of-specialists motif. Tokens live in :root so the cards/bars all stay in sync.
|
| 61 |
-
_CSS = """<style>
|
| 62 |
-
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
| 63 |
-
:root{--paper:#f7f5ee;--paper2:#f2efe2;--ink:#1f1f1d;--muted:#8c918b;--hairline:#d6d3c4;
|
| 64 |
-
--green:#317f3f;--green2:#2a5931;--gold:#c79a2e;--gold2:#9c7714}
|
| 65 |
-
.dcx{font-family:"Inter","Helvetica Neue",sans-serif;font-weight:300;color:var(--ink);
|
| 66 |
-
font-size:13px;line-height:1.7;margin:4px 0}
|
| 67 |
-
.dcx .mono{font-family:"JetBrains Mono",ui-monospace,monospace}
|
| 68 |
-
.dcx .note{background:var(--paper2);border:1px solid var(--hairline);border-radius:4px;
|
| 69 |
-
padding:12px 14px;color:#5a5a55;font-size:13px}
|
| 70 |
-
.dcx .h{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:12px;font-weight:500;
|
| 71 |
-
letter-spacing:.16em;text-transform:uppercase;color:var(--ink);margin:6px 0 12px;
|
| 72 |
-
padding-bottom:8px;border-bottom:1px solid var(--hairline)}
|
| 73 |
-
.dcx .p{color:var(--muted)}
|
| 74 |
-
.dcx .g{color:var(--green2);font-weight:600}
|
| 75 |
-
.dcx .chain{display:flex;gap:0;align-items:stretch;flex-wrap:wrap;margin:8px 0}
|
| 76 |
-
.dcx .link{align-self:center;color:var(--hairline);font-size:18px;margin:0 2px 22px;font-weight:700}
|
| 77 |
-
.dcx .card{flex:1;min-width:180px;background:var(--paper);border:1px solid var(--hairline);
|
| 78 |
-
border-radius:4px;padding:12px 13px;position:relative;overflow:hidden;transition:box-shadow .3s,border-color .3s}
|
| 79 |
-
.dcx .card .nm{font-family:"JetBrains Mono",ui-monospace,monospace;font-weight:500;font-size:12px;
|
| 80 |
-
letter-spacing:.06em;text-transform:uppercase}
|
| 81 |
-
.dcx .card .meta{color:var(--muted);font-size:11px;margin-top:4px;min-height:28px;line-height:1.45}
|
| 82 |
-
.dcx .card .bar{height:6px;background:var(--paper2);border:1px solid var(--hairline);border-radius:99px;margin-top:9px;overflow:hidden}
|
| 83 |
-
.dcx .card .fill{height:100%;border-radius:99px;animation:dcxw .7s ease}
|
| 84 |
-
.dcx .card .pct{font-size:10px;color:var(--muted);margin-top:5px;letter-spacing:.04em;text-transform:uppercase;font-family:"JetBrains Mono",monospace}
|
| 85 |
-
.dcx .badge{position:absolute;top:9px;right:10px;font-family:"JetBrains Mono",monospace;font-size:9px;
|
| 86 |
-
font-weight:700;letter-spacing:.12em;padding:3px 8px;border-radius:99px;color:var(--paper);background:var(--green)}
|
| 87 |
-
@keyframes dcxw{from{width:0}}
|
| 88 |
-
.dcx .gen{background:var(--paper2);border:1px solid var(--hairline);border-radius:4px;padding:13px 15px;
|
| 89 |
-
margin:12px 0;font-size:14px;line-height:1.8;font-family:"JetBrains Mono",ui-monospace,monospace;word-break:break-all}
|
| 90 |
-
.dcx .caret{display:inline-block;width:8px;height:16px;border-radius:1px;background:var(--green);
|
| 91 |
-
margin-left:2px;vertical-align:text-bottom;animation:dcxb .8s steps(1) infinite}
|
| 92 |
-
@keyframes dcxb{50%{opacity:0}}
|
| 93 |
-
.dcx .sub{color:var(--muted);font-size:12px;line-height:1.6;margin-top:10px}
|
| 94 |
-
.dcx .stats{display:flex;gap:0;flex-wrap:wrap;margin:12px 0;border:1px solid var(--hairline);border-radius:4px;overflow:hidden}
|
| 95 |
-
.dcx .stat{flex:1;min-width:120px;text-align:center;background:var(--paper);padding:16px 10px;border-right:1px solid var(--hairline)}
|
| 96 |
-
.dcx .stat:last-child{border-right:none}
|
| 97 |
-
.dcx .stat .v{font-family:"JetBrains Mono",monospace;font-size:26px;font-weight:700;line-height:1;color:var(--green2)}
|
| 98 |
-
.dcx .stat .l{font-size:10px;color:var(--muted);margin-top:8px;letter-spacing:.06em;text-transform:uppercase;line-height:1.4}
|
| 99 |
-
.dcx table{border-collapse:collapse;width:100%;margin:10px 0;font-size:12.5px}
|
| 100 |
-
.dcx th,.dcx td{border:1px solid var(--hairline);padding:8px 11px;text-align:left}
|
| 101 |
-
.dcx th{background:var(--paper2);color:var(--ink);font-family:"JetBrains Mono",monospace;font-weight:500;
|
| 102 |
-
font-size:10px;letter-spacing:.1em;text-transform:uppercase}
|
| 103 |
-
.dcx td.n{text-align:right;font-variant-numeric:tabular-nums;font-family:"JetBrains Mono",monospace}
|
| 104 |
-
/* --- live 2D double-helix (our own take on a base-by-base DNA viz) --- */
|
| 105 |
-
.dcx .helixwrap{background:var(--paper2);border:1px solid var(--hairline);border-radius:4px;padding:8px 12px;margin:12px 0;overflow-x:auto}
|
| 106 |
-
.dcx .helix-svg{display:block;margin:1px 0}
|
| 107 |
-
.dcx .hx-strand{fill:none;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;opacity:.5}
|
| 108 |
-
.dcx .hx-strand.back{opacity:.24}
|
| 109 |
-
.dcx .hx-rung{stroke:var(--hairline);stroke-width:1}
|
| 110 |
-
.dcx .hx-rung.user{stroke:#e6e3d6}
|
| 111 |
-
.dcx .hx-base{font-family:"JetBrains Mono",monospace;font-size:9px;font-weight:700;text-anchor:middle;dominant-baseline:central}
|
| 112 |
-
.dcx .hx-legend{display:flex;gap:13px;flex-wrap:wrap;font-family:"JetBrains Mono",monospace;font-size:9px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);margin:2px 0 4px}
|
| 113 |
-
.dcx .hx-dot{display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:5px;vertical-align:middle}
|
| 114 |
-
/* --- tokenization tracks (our take on carbon-tokenization-02): per-base + 6-mer --- */
|
| 115 |
-
.dcx .seqtrack{font-family:"JetBrains Mono",monospace;font-size:11px;background:var(--paper);border:1px solid var(--hairline);border-radius:4px;padding:8px 10px;display:flex;flex-wrap:wrap;gap:1px;margin:8px 0 2px}
|
| 116 |
-
.dcx .seqb{display:inline-flex;align-items:center;justify-content:center;width:18px;height:20px;border-radius:2px;color:#fff;font-weight:500}
|
| 117 |
-
.dcx .seqb.A{background:#1A7A40}.dcx .seqb.T{background:#b00020}.dcx .seqb.C{background:#2c5aa0}.dcx .seqb.G{background:#b8862c}
|
| 118 |
-
.dcx .tkrow{display:flex;gap:14px;align-items:baseline;margin:12px 0 2px;flex-wrap:wrap}
|
| 119 |
-
.dcx .tklabel{font-family:"JetBrains Mono",monospace;font-size:9.5px;font-weight:500;color:#5b5b56;text-transform:uppercase;letter-spacing:1.6px}
|
| 120 |
-
.dcx .tkstat{font-family:"JetBrains Mono",monospace;font-size:10px;color:var(--muted);letter-spacing:.5px}
|
| 121 |
-
.dcx .tkstat .n{font-weight:600;color:var(--green2)}
|
| 122 |
-
.dcx .tokrow{display:flex;flex-wrap:wrap;align-items:center;gap:0}
|
| 123 |
-
.dcx .tok{display:inline-flex;align-items:center;font-family:"JetBrains Mono",monospace;font-size:11px;letter-spacing:.5px;padding:4px 8px;margin:2px;border:1px solid #ccc;border-radius:3px;background:#fff;color:var(--ink)}
|
| 124 |
-
.dcx .tok.kmer{background:rgba(49,127,63,.10);border-color:rgba(49,127,63,.5);color:var(--green2);font-weight:500}
|
| 125 |
-
/* --- editorial hero banner (dotted texture + faint green edge stripes) --- */
|
| 126 |
-
.dc-banner{position:relative;overflow:hidden;border:1px solid var(--hairline);border-radius:6px;margin:2px 0 6px;
|
| 127 |
-
background:radial-gradient(circle at 22% 32%,rgba(0,0,0,.06),transparent 1px),
|
| 128 |
-
radial-gradient(circle at 78% 64%,rgba(0,0,0,.055),transparent 1px),
|
| 129 |
-
linear-gradient(90deg,rgba(49,127,63,.04),transparent 32%,transparent 68%,rgba(199,154,46,.05)),
|
| 130 |
-
var(--paper);
|
| 131 |
-
background-size:7px 7px,11px 11px,auto,auto}
|
| 132 |
-
.dc-binner{position:relative;padding:22px 26px;font-family:"JetBrains Mono",ui-monospace,monospace}
|
| 133 |
-
.dc-ident{display:flex;align-items:center;gap:11px;margin-bottom:16px}
|
| 134 |
-
.dc-mark{font-size:30px;line-height:1}
|
| 135 |
-
.dc-title{font-size:13px;font-weight:700;letter-spacing:.2em;text-transform:uppercase;color:var(--ink)}
|
| 136 |
-
.dc-path{font-size:10px;font-weight:400;letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-top:2px}
|
| 137 |
-
.dc-word{font-family:"JetBrains Mono",monospace;font-size:40px;font-weight:700;letter-spacing:-.01em;color:var(--ink);line-height:1.05;margin:4px 0 6px}
|
| 138 |
-
.dc-word .dot{color:var(--gold)}
|
| 139 |
-
.dc-tag{font-family:"Inter",sans-serif;font-weight:300;font-size:13px;color:#5a5a55;max-width:560px;line-height:1.6}
|
| 140 |
-
.dc-motif{display:flex;align-items:center;gap:4px;flex-wrap:wrap;margin-top:18px}
|
| 141 |
-
.dc-chip{font-family:"JetBrains Mono",monospace;font-size:10px;letter-spacing:.06em;text-transform:uppercase;
|
| 142 |
-
background:var(--paper2);border:1px solid var(--hairline);border-radius:99px;padding:5px 11px;color:var(--ink)}
|
| 143 |
-
.dc-tie{color:var(--green);font-size:13px;font-weight:700}
|
| 144 |
-
.dc-rule{height:1px;background:var(--hairline);margin:14px 0}
|
| 145 |
-
</style>"""
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
def _wrap(body):
|
| 149 |
-
return _CSS + "<div class='dcx'>" + body + "</div>"
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
def _esc(s):
|
| 153 |
-
return _h.escape(s or "").replace("\n", "<br>")
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
def _notice(action="Routing"):
|
| 157 |
-
if not _WARMED["done"]:
|
| 158 |
-
try:
|
| 159 |
-
gr.Info("First run β loading the four ~74M specialists (~20β40s on CPU). After this it's quick.")
|
| 160 |
-
except Exception:
|
| 161 |
-
pass
|
| 162 |
-
return _wrap(f"<div class='note'>β³ Loading the four ~74M specialists + {action.lower()}β¦ "
|
| 163 |
-
"first run can take ~20β40s on CPU; every run after is fast.</div>")
|
| 164 |
-
return _wrap(f"<div class='note'>β³ {action}β¦</div>")
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
def _msg(title, body):
|
| 168 |
-
return _wrap(f"<div class='note'><b>{title}</b><br>{body}</div>")
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
def _cards(bpb, winner=None):
|
| 172 |
-
"""One animated card per specialist: surprise (bits/base), confidence bar, winner badge + glow.
|
| 173 |
-
bpb values may be None (not computed yet). Lower bits/base = more 'at home' = fuller bar."""
|
| 174 |
-
cells = []
|
| 175 |
-
doms = list(bpb.keys())
|
| 176 |
-
for i, n in enumerate(doms):
|
| 177 |
-
c = COLOR.get(n, "#9b59b6")
|
| 178 |
-
v = bpb[n]
|
| 179 |
-
win = (n == winner)
|
| 180 |
-
conf = max(0.0, min(1.0, (2.02 - v) / 0.5)) if v is not None else 0.0 # ~1.52..2.02 -> 1..0
|
| 181 |
-
style = f"border-color:{c};box-shadow:0 0 16px {c}40" if win else ""
|
| 182 |
-
badge = f"<span class='badge' style='background:{c}'>ROUTED β</span>" if win else ""
|
| 183 |
-
meta = (f"{DESC.get(n,'')}<br>{v:.3f} bits/base (lower = more at home)"
|
| 184 |
-
if v is not None else f"{DESC.get(n,'')}<br>β¦")
|
| 185 |
-
bar = (f"<div class='bar'><div class='fill' style='width:{conf*100:.1f}%;background:{c}'></div></div>"
|
| 186 |
-
f"<div class='pct'>confidence {conf*100:.0f}%</div>") if v is not None else \
|
| 187 |
-
"<div class='bar'></div><div class='pct'>β¦</div>"
|
| 188 |
-
cells.append(
|
| 189 |
-
f"<div class='card' style='{style}'>{badge}"
|
| 190 |
-
f"<div class='nm' style='color:{c}'>{EMOJI.get(n, n)}</div>"
|
| 191 |
-
f"<div class='meta'>{meta}</div>{bar}</div>")
|
| 192 |
-
if i < len(doms) - 1:
|
| 193 |
-
cells.append("<div class='link'>β¬</div>")
|
| 194 |
-
return "<div class='chain'>" + "".join(cells) + "</div>"
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
def _gen_box(prompt, gen, live=False):
|
| 198 |
-
caret = "<span class='caret'></span>" if live else ""
|
| 199 |
-
return (f"<div class='gen'><span class='p'>{_esc(prompt)}</span>"
|
| 200 |
-
f"<span class='g'>{_esc(gen)}</span>{caret}</div>")
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
# ---- live 2D double-helix --------------------------------------------------------
|
| 204 |
-
# A base-by-base SVG double-helix (two sine-wave backbones + per-base rungs, each base
|
| 205 |
-
# letter on the top strand with its WatsonβCrick complement on the bottom). Built in
|
| 206 |
-
# Python so it streams inside our existing generator; ours = cream palette, our own
|
| 207 |
-
# nucleotide colors, strands tinted by the routed specialist's accent.
|
| 208 |
-
_COMP = {"A": "T", "T": "A", "C": "G", "G": "C", "N": "N"}
|
| 209 |
-
# shared nucleotide palette (matches our tokenization track): A green, T red, C blue, G amber
|
| 210 |
-
_BASE_COL = {"A": "#1A7A40", "T": "#b00020", "C": "#2c5aa0", "G": "#b8862c"}
|
| 211 |
-
_USER_COL = "#bdbaa9"
|
| 212 |
-
_HX_SP, _HX_AMP, _HX_YC, _HX_ROWH, _HX_TURN, _HX_PERROW = 14, 12, 22, 48, 10.5, 46
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
def _hx_strand(n, sign):
|
| 216 |
-
pts = []
|
| 217 |
-
for s in range(n * 4 + 1):
|
| 218 |
-
t = s / 4
|
| 219 |
-
x = t * _HX_SP + _HX_SP / 2
|
| 220 |
-
ang = (t + 0.5) * 2 * math.pi / _HX_TURN
|
| 221 |
-
y = _HX_YC + sign * _HX_AMP * math.sin(ang)
|
| 222 |
-
pts.append(f"{x:.1f},{y:.1f}")
|
| 223 |
-
return " ".join(pts)
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
def _hx_row(bases, start, user_len, accent):
|
| 227 |
-
n = len(bases)
|
| 228 |
-
w = n * _HX_SP + 6
|
| 229 |
-
out = [f"<svg class='helix-svg' width='{w}' height='{_HX_ROWH}' "
|
| 230 |
-
f"viewBox='0 0 {w} {_HX_ROWH}' xmlns='http://www.w3.org/2000/svg'>",
|
| 231 |
-
f"<polyline class='hx-strand' style='stroke:{accent}' points='{_hx_strand(n,1)}'/>",
|
| 232 |
-
f"<polyline class='hx-strand back' style='stroke:{accent}' points='{_hx_strand(n,-1)}'/>"]
|
| 233 |
-
for i in range(n):
|
| 234 |
-
x = i * _HX_SP + _HX_SP / 2
|
| 235 |
-
ang = (i + 0.5) * 2 * math.pi / _HX_TURN
|
| 236 |
-
yt = _HX_YC + _HX_AMP * math.sin(ang); yb = _HX_YC - _HX_AMP * math.sin(ang)
|
| 237 |
-
kind = "user" if start + i < user_len else "gen"
|
| 238 |
-
out.append(f"<line class='hx-rung {kind}' x1='{x:.1f}' y1='{yt:.1f}' x2='{x:.1f}' y2='{yb:.1f}'/>")
|
| 239 |
-
for i in range(n):
|
| 240 |
-
x = i * _HX_SP + _HX_SP / 2
|
| 241 |
-
ang = (i + 0.5) * 2 * math.pi / _HX_TURN
|
| 242 |
-
yt = _HX_YC + _HX_AMP * math.sin(ang); yb = _HX_YC - _HX_AMP * math.sin(ang)
|
| 243 |
-
b = bases[i]; comp = _COMP.get(b, "N")
|
| 244 |
-
gen = start + i >= user_len
|
| 245 |
-
col = _BASE_COL.get(b, "#999") if gen else _USER_COL
|
| 246 |
-
ccol = _BASE_COL.get(comp, "#999") if gen else _USER_COL
|
| 247 |
-
op = "1" if gen else ".6"
|
| 248 |
-
out.append(f"<text class='hx-base' x='{x:.1f}' y='{yt:.1f}' style='fill:{col};opacity:{op}'>{b}</text>")
|
| 249 |
-
out.append(f"<text class='hx-base' x='{x:.1f}' y='{yb:.1f}' style='fill:{ccol};opacity:{op}'>{comp}</text>")
|
| 250 |
-
out.append("</svg>")
|
| 251 |
-
return "".join(out)
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
def _helix(prompt, gen, accent, live=False):
|
| 255 |
-
bases = [c for c in (prompt + gen) if c in "ACGTN"]
|
| 256 |
-
user_len = len([c for c in prompt if c in "ACGTN"])
|
| 257 |
-
rows, i = [], 0
|
| 258 |
-
while i < len(bases):
|
| 259 |
-
rows.append(_hx_row(bases[i:i + _HX_PERROW], i, user_len, accent))
|
| 260 |
-
i += _HX_PERROW
|
| 261 |
-
caret = "<span class='caret'></span>" if live else ""
|
| 262 |
-
legend = ("<div class='hx-legend'>"
|
| 263 |
-
+ "".join(f"<span><span class='hx-dot' style='background:{_BASE_COL[b]}'></span>{b}</span>" for b in "ACGT")
|
| 264 |
-
+ f"<span><span class='hx-dot' style='background:{_USER_COL}'></span>your input</span></div>")
|
| 265 |
-
return f"<div class='helixwrap'>{''.join(rows) or ' '}{caret}</div>{legend}"
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
def _seq_track(gen):
|
| 269 |
-
"""Per-base colored track of the generated bases (our take on the tokenization demo)."""
|
| 270 |
-
cells = "".join(f"<span class='seqb {c}'>{c}</span>" for c in gen if c in "ACGT")
|
| 271 |
-
return f"<div class='seqtrack'>{cells or ' '}</div>"
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
def _kmer_strip(gen):
|
| 275 |
-
"""The generated sequence cut into our model's actual non-overlapping 6-mer tokens."""
|
| 276 |
-
s = "".join(c for c in gen if c in "ACGTN")
|
| 277 |
-
toks = [s[i:i + 6] for i in range(0, len(s) - len(s) % 6, 6)]
|
| 278 |
-
chips = "".join(f"<span class='tok kmer'>{t}</span>" for t in toks)
|
| 279 |
-
head = ("<div class='tkrow'><span class='tklabel'>6-mer tokens</span>"
|
| 280 |
-
f"<span class='tkstat'>tokens <span class='n'>{len(toks)}</span></span>"
|
| 281 |
-
f"<span class='tkstat'>bases <span class='n'>{len(s)}</span></span>"
|
| 282 |
-
"<span class='tkstat'>6 bases / token</span></div>")
|
| 283 |
-
return head + f"<div class='tokrow'>{chips}</div>"
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
FOOTER = ("Four ~74M DNA/RNA specialists (β295M total, <b>under Carbon-500M</b>), each distilled "
|
| 287 |
-
"per-domain from Carbon-500M. A learned router reads every specialist's surprise + hidden "
|
| 288 |
-
"state and routes to the home specialist β held-out routing accuracy <b>99.8%</b>. Only one "
|
| 289 |
-
"specialist runs per query (~7Γ cheaper than the 500M monolith).")
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
# ---- handler ----------------------------------------------------------------------
|
| 293 |
-
@_gpu(duration=120)
|
| 294 |
-
def route_run(seq, n_bases, do_gen, decode="auto"):
|
| 295 |
-
yield _notice("Routing & generating")
|
| 296 |
-
seq = (seq or "").strip()
|
| 297 |
-
if len(seq) < 18:
|
| 298 |
-
yield _msg("𧬠Enter a DNA sequence", "Paste at least 18 bases (A/C/G/T) β try an example below.")
|
| 299 |
-
return
|
| 300 |
-
dc = _moe()
|
| 301 |
-
doms = dc.domains
|
| 302 |
-
bpb = {d: None for d in doms}
|
| 303 |
-
# progressively reveal each specialist's surprise (the chain lighting up)
|
| 304 |
-
sc, hd = dc._scores_hidden(seq)
|
| 305 |
-
for d in doms:
|
| 306 |
-
bpb[d] = sc[d] / 6 / 0.6931
|
| 307 |
-
yield _wrap("<div class='h'>π Sending the sequence down the chainβ¦</div>" + _cards(bpb))
|
| 308 |
-
home, _ = dc.route(seq)
|
| 309 |
-
c = COLOR.get(home, "#9b59b6")
|
| 310 |
-
head = (f"<div class='h'>π§ Routed to <span style='color:{c}'>{EMOJI.get(home, home)}</span>"
|
| 311 |
-
f" β the specialist most at home with your sequence</div>" + _cards(bpb, winner=home))
|
| 312 |
-
if do_gen:
|
| 313 |
-
# decoding:
|
| 314 |
-
#
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
else
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
yield _wrap(head + hxhead + _helix(ctx, gen, c, live=True)
|
| 331 |
-
+ _seq_track(gen) + _kmer_strip(gen)
|
| 332 |
-
+ rawhdr + _gen_box(ctx, gen, live=True))
|
| 333 |
-
_WARMED["done"] = True
|
| 334 |
-
gennote = ("<div class='sub'>π§ͺ <b>Generation is exploratory</b> β these ~74M specialists are "
|
| 335 |
-
"trained on a slice of the corpus, so sampled DNA is low-complexity (and splice / "
|
| 336 |
-
"bacterial domains are genuinely AT-rich). The <b>routing</b> and per-base likelihood "
|
| 337 |
-
"are the result here, not Carbon-level generation.</div>")
|
| 338 |
-
yield _wrap(head + hxhead + _helix(ctx, gen, c, live=False)
|
| 339 |
-
+ _seq_track(gen) + _kmer_strip(gen)
|
| 340 |
-
+ rawhdr + _gen_box(ctx, gen, live=False)
|
| 341 |
-
+ gennote + f"<div class='sub'>{FOOTER}</div>")
|
| 342 |
-
else:
|
| 343 |
-
_WARMED["done"] = True
|
| 344 |
-
yield _wrap(head + f"<div class='sub'>{FOOTER}</div>")
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
STATS_HTML = _wrap(
|
| 348 |
-
"<div class='h'>π DaisyChain vs Carbon-500M β the fair baseline</div>"
|
| 349 |
-
"<div class='stats'>"
|
| 350 |
-
"<div class='stat'><div class='v' style='color:#37b24d'>99.8%</div>"
|
| 351 |
-
"<div class='l'>routing accuracy<br>(held-out)</div></div>"
|
| 352 |
-
"<div class='stat'><div class='v' style='color:#7c5cff'>β295M</div>"
|
| 353 |
-
"<div class='l'>total params<br>(4 Γ ~74M) < Carbon-500M</div></div>"
|
| 354 |
-
"<div class='stat'><div class='v' style='color:#22b8cf'>~7Γ</div>"
|
| 355 |
-
"<div class='l'>cheaper per query<br>(one 74M specialist active)</div></div>"
|
| 356 |
-
"</div>"
|
| 357 |
-
"<table><tr><th>metric</th><th>DaisyChain</th><th>Carbon-500M</th></tr>"
|
| 358 |
-
"<tr><td>Likelihood β bits/base (β better)</td><td class='n'>1.79</td><td class='n'>1.75</td></tr>"
|
| 359 |
-
"<tr><td>Seq-recovery, eukaryote (β better)</td><td class='n'>31.0%</td><td class='n'>42.2%</td></tr>"
|
| 360 |
-
"<tr><td>Seq-recovery, bacteria (β better)</td><td class='n'>36.5%</td><td class='n'>49.5%</td></tr>"
|
| 361 |
-
"</table>"
|
| 362 |
-
"<div class='sub'>Four ~74M specialists (β295M total, <b>under Carbon-500M</b>); only one runs per "
|
| 363 |
-
"query, so it's ~7Γ cheaper per token. Behind the 500M / 1T-token monolith but within striking "
|
| 364 |
-
"distance β the gap is concentrated in the structured domains (mRNA, bacteria) and keeps closing "
|
| 365 |
-
"with more per-domain training. Same protocols as Carbon's eval suite (sequence recovery; per-base "
|
| 366 |
-
"likelihood). Carbon-500M is the right yardstick for a sub-500M modular set, not the 3B flagship.</div>")
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
BANNER = _CSS + """
|
| 370 |
-
<div class='dcx'><div class='dc-banner'><div class='dc-binner'>
|
| 371 |
-
<div class='dc-ident'>
|
| 372 |
-
<span class='dc-mark'>πΌ</span>
|
| 373 |
-
<div>
|
| 374 |
-
<div class='dc-title'>DAISYCHAIN</div>
|
| 375 |
-
<div class='dc-path'>DAISYCHAINAI / GENOMICS Β· ROUTED DNA SPECIALISTS</div>
|
| 376 |
-
</div>
|
| 377 |
-
</div>
|
| 378 |
-
<div class='dc-word'>DaisyChain<span class='dot'>.</span></div>
|
| 379 |
-
<div class='dc-tag'>A modular genomic mind. Four dense ~74M DNA/RNA specialists
|
| 380 |
-
(≈295M total, <b>under Carbon-500M</b>), each distilled per-domain from Carbon-500M.
|
| 381 |
-
A learned router reads how <i>surprised</i> each specialist is by your sequence
|
| 382 |
-
(bits/base) plus its hidden state, then hands the work to its home specialist β
|
| 383 |
-
held-out routing accuracy <b>99.8%</b>. Watch it route in real time.</div>
|
| 384 |
-
<div class='dc-motif'>
|
| 385 |
-
<span class='dc-chip'>𧬠Eukaryote</span><span class='dc-tie'>β</span>
|
| 386 |
-
<span class='dc-chip'>π¦ Prokaryote</span><span class='dc-tie'>β</span>
|
| 387 |
-
<span class='dc-chip'>π mRNA</span><span class='dc-tie'>β</span>
|
| 388 |
-
<span class='dc-chip'>βοΈ mRNA-splice</span>
|
| 389 |
-
</div>
|
| 390 |
-
</div></div></div>"""
|
| 391 |
-
|
| 392 |
-
# Light editorial chrome for the Gradio shell so the cream paper extends to the whole page.
|
| 393 |
-
# We pin Gradio's theme CSS variables (for BOTH light and .dark) to the paper palette so the
|
| 394 |
-
# text never renders white-on-cream when a visitor's browser/Space defaults to dark mode.
|
| 395 |
-
_PAGE_CSS = """
|
| 396 |
-
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
| 397 |
-
.gradio-container, .gradio-container.dark, .dark, body, body.dark{
|
| 398 |
-
--body-background-fill:#f7f5ee!important;
|
| 399 |
-
--background-fill-primary:#f7f5ee!important;
|
| 400 |
-
--background-fill-secondary:#f2efe2!important;
|
| 401 |
-
--block-background-fill:#fbfaf4!important;
|
| 402 |
-
--block-label-background-fill:#f2efe2!important;
|
| 403 |
-
--input-background-fill:#fffdf6!important;
|
| 404 |
-
--body-text-color:#1f1f1d!important;
|
| 405 |
-
--body-text-color-subdued:#5a5a55!important;
|
| 406 |
-
--block-title-text-color:#1f1f1d!important;
|
| 407 |
-
--block-label-text-color:#5a5a55!important;
|
| 408 |
-
--block-info-text-color:#5a5a55!important;
|
| 409 |
-
--border-color-primary:#d6d3c4!important;
|
| 410 |
-
--neutral-50:#f7f5ee!important;
|
| 411 |
-
--table-even-background-fill:#fbfaf4!important;
|
| 412 |
-
--table-odd-background-fill:#f2efe2!important;
|
| 413 |
-
--table-row-focus:#eef3e9!important;
|
| 414 |
-
--table-text-color:#1f1f1d!important;
|
| 415 |
-
background:#f7f5ee!important;
|
| 416 |
-
color:#1f1f1d!important;
|
| 417 |
-
}
|
| 418 |
-
/* Examples dataset table rows (were rendering black in dark mode) */
|
| 419 |
-
.gradio-container .gr-samples-table, .gradio-container [class*='dataset'] table,
|
| 420 |
-
.gradio-container [class*='dataset'] td, .gradio-container [class*='dataset'] tr,
|
| 421 |
-
.gradio-container [class*='dataset'] tbody{background:#fbfaf4!important;color:#1f1f1d!important}
|
| 422 |
-
.gradio-container [class*='dataset'] tr:nth-child(even) td{background:#f2efe2!important}
|
| 423 |
-
/* Radio / checkbox options (were rendering black in dark mode) */
|
| 424 |
-
.gradio-container [class*='radio'] label, .gradio-container fieldset label,
|
| 425 |
-
.gradio-container [class*='checkbox'] label, .gradio-container .wrap label{
|
| 426 |
-
background:#fbfaf4!important;color:#1f1f1d!important;border:1px solid #d6d3c4!important}
|
| 427 |
-
.gradio-container [class*='radio'] label *, .gradio-container fieldset label *,
|
| 428 |
-
.gradio-container [class*='checkbox'] label *{color:#1f1f1d!important}
|
| 429 |
-
.gradio-container input[type=radio],.gradio-container input[type=checkbox]{accent-color:#317f3f!important}
|
| 430 |
-
.gradio-container{font-family:"Inter","Helvetica Neue",sans-serif!important;max-width:1080px!important}
|
| 431 |
-
/* force any Gradio-rendered label / markdown / example text to ink, never white */
|
| 432 |
-
.gradio-container label, .gradio-container .prose, .gradio-container p,
|
| 433 |
-
.gradio-container span, .gradio-container td, .gradio-container th,
|
| 434 |
-
.gradio-container .gr-text-input, .gradio-container input, .gradio-container textarea{color:#1f1f1d!important}
|
| 435 |
-
.gradio-container input::placeholder, .gradio-container textarea::placeholder{color:#9c9989!important}
|
| 436 |
-
footer{display:none!important}
|
| 437 |
-
.gr-button-primary, button.primary{background:#317f3f!important;border:1px solid #2a5931!important;color:#f7f5ee!important;
|
| 438 |
-
font-family:"JetBrains Mono",monospace!important;letter-spacing:.08em!important;text-transform:uppercase!important;font-size:12px!important}
|
| 439 |
-
.gr-button-primary:hover, button.primary:hover{background:#2a5931!important}
|
| 440 |
-
.gr-button-primary *, button.primary *{color:#f7f5ee!important}
|
| 441 |
-
"""
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
def build():
|
| 445 |
-
theme = gr.themes.Default(primary_hue="green", neutral_hue="stone",
|
| 446 |
-
font=[gr.themes.GoogleFont("Inter"), "sans-serif"],
|
| 447 |
-
font_mono=[gr.themes.GoogleFont("JetBrains Mono"), "monospace"])
|
| 448 |
-
# force the light palette so text is never white-on-cream if a visitor defaults to dark mode
|
| 449 |
-
_force_light = ("() => { const u = new URL(window.location.href);"
|
| 450 |
-
" if (u.searchParams.get('__theme') !== 'light') {"
|
| 451 |
-
" u.searchParams.set('__theme','light'); window.location.replace(u.href); } }")
|
| 452 |
-
with gr.Blocks(title="DaisyChain β modular genomic mind", theme=theme, css=_PAGE_CSS,
|
| 453 |
-
js=_force_light) as demo:
|
| 454 |
-
gr.HTML(BANNER)
|
| 455 |
-
with gr.Row():
|
| 456 |
-
seq = gr.Textbox(label="DNA SEQUENCE", lines=3, scale=4,
|
| 457 |
-
placeholder="ACGT⦠(eukaryotic, bacterial, mRNA, or splice-site DNA)")
|
| 458 |
-
n = gr.Slider(60, 300, value=90, step=30, label="GENERATE BASES", scale=1)
|
| 459 |
-
with gr.Row():
|
| 460 |
-
gen_ck = gr.Checkbox(value=True, label="stream a continuation from the routed specialist")
|
| 461 |
-
decode = gr.Radio(["
|
| 462 |
-
label="DECODING", scale=1,
|
| 463 |
-
info="
|
| 464 |
-
btn = gr.Button("π Route through the DaisyChain", variant="primary")
|
| 465 |
-
out = gr.HTML(_wrap("<div class='h'>The chain Β· paste a sequence to light it up</div>"
|
| 466 |
-
+ _cards({d: None for d in DaisyChain.DESCRIPTIONS})))
|
| 467 |
-
btn.click(route_run, [seq, n, gen_ck, decode], out)
|
| 468 |
-
try:
|
| 469 |
-
ex = json.load(open(os.path.join(HERE, "examples.json")))
|
| 470 |
-
gr.Examples([[v, 90, True, "
|
| 471 |
-
inputs=[seq, n, gen_ck, decode],
|
| 472 |
-
label="Example sequences (one per domain)")
|
| 473 |
-
except Exception:
|
| 474 |
-
pass
|
| 475 |
-
gr.HTML(STATS_HTML)
|
| 476 |
-
return demo
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
if __name__ == "__main__":
|
| 480 |
-
build().launch()
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
DaisyChain β interactive routing demo (HuggingFace Space).
|
| 3 |
+
|
| 4 |
+
Paste DNA; the learned router reads how *surprised* each ~74M specialist is (bits/base)
|
| 5 |
+
plus its hidden state and hands the sequence to its home specialist β then that specialist
|
| 6 |
+
streams a continuation live. Styled after the Modular-Mind panel: animated routing cards,
|
| 7 |
+
a first-run loading notice, live token streaming. Every handler is a generator.
|
| 8 |
+
"""
|
| 9 |
+
import html as _h
|
| 10 |
+
import os
|
| 11 |
+
import json
|
| 12 |
+
import math
|
| 13 |
+
|
| 14 |
+
import gradio as gr
|
| 15 |
+
|
| 16 |
+
# ZeroGPU: @spaces.GPU allocates a GPU only for the decorated call. Falls back to a no-op
|
| 17 |
+
# decorator when `spaces` isn't installed (local / plain CPU).
|
| 18 |
+
try:
|
| 19 |
+
import spaces
|
| 20 |
+
_gpu = spaces.GPU
|
| 21 |
+
except Exception:
|
| 22 |
+
def _gpu(fn=None, **kw):
|
| 23 |
+
return fn if callable(fn) else (lambda f: f)
|
| 24 |
+
|
| 25 |
+
from daisychain import DaisyChain
|
| 26 |
+
|
| 27 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 28 |
+
MODEL_REPO = os.environ.get("DAISYCHAIN_REPO", "DaisyChainAI/daisychain-genomics")
|
| 29 |
+
DEVICE = os.environ.get("DAISYCHAIN_DEVICE", "cpu")
|
| 30 |
+
|
| 31 |
+
# code + tokenizer + router are bundled here; pull the big specialist weights from the
|
| 32 |
+
# (gated) model repo on first launch using the HF_TOKEN Space secret. No silent
|
| 33 |
+
# swallow β if the download fails we want a visible error, not a broken-but-running app.
|
| 34 |
+
if not os.path.exists(os.path.join(HERE, "eukaryote", "model.safetensors")):
|
| 35 |
+
from huggingface_hub import snapshot_download
|
| 36 |
+
snapshot_download(MODEL_REPO, local_dir=HERE,
|
| 37 |
+
token=os.environ.get("HF_TOKEN"),
|
| 38 |
+
allow_patterns=["*/model.safetensors", "tokenizer.json", "router2.pt"])
|
| 39 |
+
|
| 40 |
+
_DC = {"m": None} # lazy-loaded so CUDA is never touched at import
|
| 41 |
+
_WARMED = {"done": False} # so the "loading" notice only shows on the first run
|
| 42 |
+
|
| 43 |
+
EMOJI = {"eukaryote": "𧬠Eukaryote", "prokaryote": "π¦ Prokaryote",
|
| 44 |
+
"mrna": "π mRNA", "mrna_splice": "βοΈ mRNA-splice"}
|
| 45 |
+
# deeper, paper-friendly tones (vs the old neon dark-theme hues)
|
| 46 |
+
COLOR = {"eukaryote": "#5b4bb0", "prokaryote": "#1f7a99",
|
| 47 |
+
"mrna": "#b03a63", "mrna_splice": "#317f3f"}
|
| 48 |
+
DESC = DaisyChain.DESCRIPTIONS
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _moe():
|
| 52 |
+
if _DC["m"] is None:
|
| 53 |
+
_DC["m"] = DaisyChain(root=HERE, device=DEVICE)
|
| 54 |
+
return _DC["m"]
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
# ---- HTML rendering ----------------------------------------------------------------
|
| 58 |
+
# Editorial "scientific-paper" aesthetic borrowed from Carbon's demo (cream paper, ink,
|
| 59 |
+
# green accent, mono uppercase headers) but made our own: a daisy-gold second accent and
|
| 60 |
+
# the chain-of-specialists motif. Tokens live in :root so the cards/bars all stay in sync.
|
| 61 |
+
_CSS = """<style>
|
| 62 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
| 63 |
+
:root{--paper:#f7f5ee;--paper2:#f2efe2;--ink:#1f1f1d;--muted:#8c918b;--hairline:#d6d3c4;
|
| 64 |
+
--green:#317f3f;--green2:#2a5931;--gold:#c79a2e;--gold2:#9c7714}
|
| 65 |
+
.dcx{font-family:"Inter","Helvetica Neue",sans-serif;font-weight:300;color:var(--ink);
|
| 66 |
+
font-size:13px;line-height:1.7;margin:4px 0}
|
| 67 |
+
.dcx .mono{font-family:"JetBrains Mono",ui-monospace,monospace}
|
| 68 |
+
.dcx .note{background:var(--paper2);border:1px solid var(--hairline);border-radius:4px;
|
| 69 |
+
padding:12px 14px;color:#5a5a55;font-size:13px}
|
| 70 |
+
.dcx .h{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:12px;font-weight:500;
|
| 71 |
+
letter-spacing:.16em;text-transform:uppercase;color:var(--ink);margin:6px 0 12px;
|
| 72 |
+
padding-bottom:8px;border-bottom:1px solid var(--hairline)}
|
| 73 |
+
.dcx .p{color:var(--muted)}
|
| 74 |
+
.dcx .g{color:var(--green2);font-weight:600}
|
| 75 |
+
.dcx .chain{display:flex;gap:0;align-items:stretch;flex-wrap:wrap;margin:8px 0}
|
| 76 |
+
.dcx .link{align-self:center;color:var(--hairline);font-size:18px;margin:0 2px 22px;font-weight:700}
|
| 77 |
+
.dcx .card{flex:1;min-width:180px;background:var(--paper);border:1px solid var(--hairline);
|
| 78 |
+
border-radius:4px;padding:12px 13px;position:relative;overflow:hidden;transition:box-shadow .3s,border-color .3s}
|
| 79 |
+
.dcx .card .nm{font-family:"JetBrains Mono",ui-monospace,monospace;font-weight:500;font-size:12px;
|
| 80 |
+
letter-spacing:.06em;text-transform:uppercase}
|
| 81 |
+
.dcx .card .meta{color:var(--muted);font-size:11px;margin-top:4px;min-height:28px;line-height:1.45}
|
| 82 |
+
.dcx .card .bar{height:6px;background:var(--paper2);border:1px solid var(--hairline);border-radius:99px;margin-top:9px;overflow:hidden}
|
| 83 |
+
.dcx .card .fill{height:100%;border-radius:99px;animation:dcxw .7s ease}
|
| 84 |
+
.dcx .card .pct{font-size:10px;color:var(--muted);margin-top:5px;letter-spacing:.04em;text-transform:uppercase;font-family:"JetBrains Mono",monospace}
|
| 85 |
+
.dcx .badge{position:absolute;top:9px;right:10px;font-family:"JetBrains Mono",monospace;font-size:9px;
|
| 86 |
+
font-weight:700;letter-spacing:.12em;padding:3px 8px;border-radius:99px;color:var(--paper);background:var(--green)}
|
| 87 |
+
@keyframes dcxw{from{width:0}}
|
| 88 |
+
.dcx .gen{background:var(--paper2);border:1px solid var(--hairline);border-radius:4px;padding:13px 15px;
|
| 89 |
+
margin:12px 0;font-size:14px;line-height:1.8;font-family:"JetBrains Mono",ui-monospace,monospace;word-break:break-all}
|
| 90 |
+
.dcx .caret{display:inline-block;width:8px;height:16px;border-radius:1px;background:var(--green);
|
| 91 |
+
margin-left:2px;vertical-align:text-bottom;animation:dcxb .8s steps(1) infinite}
|
| 92 |
+
@keyframes dcxb{50%{opacity:0}}
|
| 93 |
+
.dcx .sub{color:var(--muted);font-size:12px;line-height:1.6;margin-top:10px}
|
| 94 |
+
.dcx .stats{display:flex;gap:0;flex-wrap:wrap;margin:12px 0;border:1px solid var(--hairline);border-radius:4px;overflow:hidden}
|
| 95 |
+
.dcx .stat{flex:1;min-width:120px;text-align:center;background:var(--paper);padding:16px 10px;border-right:1px solid var(--hairline)}
|
| 96 |
+
.dcx .stat:last-child{border-right:none}
|
| 97 |
+
.dcx .stat .v{font-family:"JetBrains Mono",monospace;font-size:26px;font-weight:700;line-height:1;color:var(--green2)}
|
| 98 |
+
.dcx .stat .l{font-size:10px;color:var(--muted);margin-top:8px;letter-spacing:.06em;text-transform:uppercase;line-height:1.4}
|
| 99 |
+
.dcx table{border-collapse:collapse;width:100%;margin:10px 0;font-size:12.5px}
|
| 100 |
+
.dcx th,.dcx td{border:1px solid var(--hairline);padding:8px 11px;text-align:left}
|
| 101 |
+
.dcx th{background:var(--paper2);color:var(--ink);font-family:"JetBrains Mono",monospace;font-weight:500;
|
| 102 |
+
font-size:10px;letter-spacing:.1em;text-transform:uppercase}
|
| 103 |
+
.dcx td.n{text-align:right;font-variant-numeric:tabular-nums;font-family:"JetBrains Mono",monospace}
|
| 104 |
+
/* --- live 2D double-helix (our own take on a base-by-base DNA viz) --- */
|
| 105 |
+
.dcx .helixwrap{background:var(--paper2);border:1px solid var(--hairline);border-radius:4px;padding:8px 12px;margin:12px 0;overflow-x:auto}
|
| 106 |
+
.dcx .helix-svg{display:block;margin:1px 0}
|
| 107 |
+
.dcx .hx-strand{fill:none;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;opacity:.5}
|
| 108 |
+
.dcx .hx-strand.back{opacity:.24}
|
| 109 |
+
.dcx .hx-rung{stroke:var(--hairline);stroke-width:1}
|
| 110 |
+
.dcx .hx-rung.user{stroke:#e6e3d6}
|
| 111 |
+
.dcx .hx-base{font-family:"JetBrains Mono",monospace;font-size:9px;font-weight:700;text-anchor:middle;dominant-baseline:central}
|
| 112 |
+
.dcx .hx-legend{display:flex;gap:13px;flex-wrap:wrap;font-family:"JetBrains Mono",monospace;font-size:9px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);margin:2px 0 4px}
|
| 113 |
+
.dcx .hx-dot{display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:5px;vertical-align:middle}
|
| 114 |
+
/* --- tokenization tracks (our take on carbon-tokenization-02): per-base + 6-mer --- */
|
| 115 |
+
.dcx .seqtrack{font-family:"JetBrains Mono",monospace;font-size:11px;background:var(--paper);border:1px solid var(--hairline);border-radius:4px;padding:8px 10px;display:flex;flex-wrap:wrap;gap:1px;margin:8px 0 2px}
|
| 116 |
+
.dcx .seqb{display:inline-flex;align-items:center;justify-content:center;width:18px;height:20px;border-radius:2px;color:#fff;font-weight:500}
|
| 117 |
+
.dcx .seqb.A{background:#1A7A40}.dcx .seqb.T{background:#b00020}.dcx .seqb.C{background:#2c5aa0}.dcx .seqb.G{background:#b8862c}
|
| 118 |
+
.dcx .tkrow{display:flex;gap:14px;align-items:baseline;margin:12px 0 2px;flex-wrap:wrap}
|
| 119 |
+
.dcx .tklabel{font-family:"JetBrains Mono",monospace;font-size:9.5px;font-weight:500;color:#5b5b56;text-transform:uppercase;letter-spacing:1.6px}
|
| 120 |
+
.dcx .tkstat{font-family:"JetBrains Mono",monospace;font-size:10px;color:var(--muted);letter-spacing:.5px}
|
| 121 |
+
.dcx .tkstat .n{font-weight:600;color:var(--green2)}
|
| 122 |
+
.dcx .tokrow{display:flex;flex-wrap:wrap;align-items:center;gap:0}
|
| 123 |
+
.dcx .tok{display:inline-flex;align-items:center;font-family:"JetBrains Mono",monospace;font-size:11px;letter-spacing:.5px;padding:4px 8px;margin:2px;border:1px solid #ccc;border-radius:3px;background:#fff;color:var(--ink)}
|
| 124 |
+
.dcx .tok.kmer{background:rgba(49,127,63,.10);border-color:rgba(49,127,63,.5);color:var(--green2);font-weight:500}
|
| 125 |
+
/* --- editorial hero banner (dotted texture + faint green edge stripes) --- */
|
| 126 |
+
.dc-banner{position:relative;overflow:hidden;border:1px solid var(--hairline);border-radius:6px;margin:2px 0 6px;
|
| 127 |
+
background:radial-gradient(circle at 22% 32%,rgba(0,0,0,.06),transparent 1px),
|
| 128 |
+
radial-gradient(circle at 78% 64%,rgba(0,0,0,.055),transparent 1px),
|
| 129 |
+
linear-gradient(90deg,rgba(49,127,63,.04),transparent 32%,transparent 68%,rgba(199,154,46,.05)),
|
| 130 |
+
var(--paper);
|
| 131 |
+
background-size:7px 7px,11px 11px,auto,auto}
|
| 132 |
+
.dc-binner{position:relative;padding:22px 26px;font-family:"JetBrains Mono",ui-monospace,monospace}
|
| 133 |
+
.dc-ident{display:flex;align-items:center;gap:11px;margin-bottom:16px}
|
| 134 |
+
.dc-mark{font-size:30px;line-height:1}
|
| 135 |
+
.dc-title{font-size:13px;font-weight:700;letter-spacing:.2em;text-transform:uppercase;color:var(--ink)}
|
| 136 |
+
.dc-path{font-size:10px;font-weight:400;letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-top:2px}
|
| 137 |
+
.dc-word{font-family:"JetBrains Mono",monospace;font-size:40px;font-weight:700;letter-spacing:-.01em;color:var(--ink);line-height:1.05;margin:4px 0 6px}
|
| 138 |
+
.dc-word .dot{color:var(--gold)}
|
| 139 |
+
.dc-tag{font-family:"Inter",sans-serif;font-weight:300;font-size:13px;color:#5a5a55;max-width:560px;line-height:1.6}
|
| 140 |
+
.dc-motif{display:flex;align-items:center;gap:4px;flex-wrap:wrap;margin-top:18px}
|
| 141 |
+
.dc-chip{font-family:"JetBrains Mono",monospace;font-size:10px;letter-spacing:.06em;text-transform:uppercase;
|
| 142 |
+
background:var(--paper2);border:1px solid var(--hairline);border-radius:99px;padding:5px 11px;color:var(--ink)}
|
| 143 |
+
.dc-tie{color:var(--green);font-size:13px;font-weight:700}
|
| 144 |
+
.dc-rule{height:1px;background:var(--hairline);margin:14px 0}
|
| 145 |
+
</style>"""
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def _wrap(body):
|
| 149 |
+
return _CSS + "<div class='dcx'>" + body + "</div>"
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def _esc(s):
|
| 153 |
+
return _h.escape(s or "").replace("\n", "<br>")
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def _notice(action="Routing"):
|
| 157 |
+
if not _WARMED["done"]:
|
| 158 |
+
try:
|
| 159 |
+
gr.Info("First run β loading the four ~74M specialists (~20β40s on CPU). After this it's quick.")
|
| 160 |
+
except Exception:
|
| 161 |
+
pass
|
| 162 |
+
return _wrap(f"<div class='note'>β³ Loading the four ~74M specialists + {action.lower()}β¦ "
|
| 163 |
+
"first run can take ~20β40s on CPU; every run after is fast.</div>")
|
| 164 |
+
return _wrap(f"<div class='note'>β³ {action}β¦</div>")
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def _msg(title, body):
|
| 168 |
+
return _wrap(f"<div class='note'><b>{title}</b><br>{body}</div>")
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def _cards(bpb, winner=None):
|
| 172 |
+
"""One animated card per specialist: surprise (bits/base), confidence bar, winner badge + glow.
|
| 173 |
+
bpb values may be None (not computed yet). Lower bits/base = more 'at home' = fuller bar."""
|
| 174 |
+
cells = []
|
| 175 |
+
doms = list(bpb.keys())
|
| 176 |
+
for i, n in enumerate(doms):
|
| 177 |
+
c = COLOR.get(n, "#9b59b6")
|
| 178 |
+
v = bpb[n]
|
| 179 |
+
win = (n == winner)
|
| 180 |
+
conf = max(0.0, min(1.0, (2.02 - v) / 0.5)) if v is not None else 0.0 # ~1.52..2.02 -> 1..0
|
| 181 |
+
style = f"border-color:{c};box-shadow:0 0 16px {c}40" if win else ""
|
| 182 |
+
badge = f"<span class='badge' style='background:{c}'>ROUTED β</span>" if win else ""
|
| 183 |
+
meta = (f"{DESC.get(n,'')}<br>{v:.3f} bits/base (lower = more at home)"
|
| 184 |
+
if v is not None else f"{DESC.get(n,'')}<br>β¦")
|
| 185 |
+
bar = (f"<div class='bar'><div class='fill' style='width:{conf*100:.1f}%;background:{c}'></div></div>"
|
| 186 |
+
f"<div class='pct'>confidence {conf*100:.0f}%</div>") if v is not None else \
|
| 187 |
+
"<div class='bar'></div><div class='pct'>β¦</div>"
|
| 188 |
+
cells.append(
|
| 189 |
+
f"<div class='card' style='{style}'>{badge}"
|
| 190 |
+
f"<div class='nm' style='color:{c}'>{EMOJI.get(n, n)}</div>"
|
| 191 |
+
f"<div class='meta'>{meta}</div>{bar}</div>")
|
| 192 |
+
if i < len(doms) - 1:
|
| 193 |
+
cells.append("<div class='link'>β¬</div>")
|
| 194 |
+
return "<div class='chain'>" + "".join(cells) + "</div>"
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def _gen_box(prompt, gen, live=False):
|
| 198 |
+
caret = "<span class='caret'></span>" if live else ""
|
| 199 |
+
return (f"<div class='gen'><span class='p'>{_esc(prompt)}</span>"
|
| 200 |
+
f"<span class='g'>{_esc(gen)}</span>{caret}</div>")
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
# ---- live 2D double-helix --------------------------------------------------------
|
| 204 |
+
# A base-by-base SVG double-helix (two sine-wave backbones + per-base rungs, each base
|
| 205 |
+
# letter on the top strand with its WatsonβCrick complement on the bottom). Built in
|
| 206 |
+
# Python so it streams inside our existing generator; ours = cream palette, our own
|
| 207 |
+
# nucleotide colors, strands tinted by the routed specialist's accent.
|
| 208 |
+
_COMP = {"A": "T", "T": "A", "C": "G", "G": "C", "N": "N"}
|
| 209 |
+
# shared nucleotide palette (matches our tokenization track): A green, T red, C blue, G amber
|
| 210 |
+
_BASE_COL = {"A": "#1A7A40", "T": "#b00020", "C": "#2c5aa0", "G": "#b8862c"}
|
| 211 |
+
_USER_COL = "#bdbaa9"
|
| 212 |
+
_HX_SP, _HX_AMP, _HX_YC, _HX_ROWH, _HX_TURN, _HX_PERROW = 14, 12, 22, 48, 10.5, 46
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
def _hx_strand(n, sign):
|
| 216 |
+
pts = []
|
| 217 |
+
for s in range(n * 4 + 1):
|
| 218 |
+
t = s / 4
|
| 219 |
+
x = t * _HX_SP + _HX_SP / 2
|
| 220 |
+
ang = (t + 0.5) * 2 * math.pi / _HX_TURN
|
| 221 |
+
y = _HX_YC + sign * _HX_AMP * math.sin(ang)
|
| 222 |
+
pts.append(f"{x:.1f},{y:.1f}")
|
| 223 |
+
return " ".join(pts)
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def _hx_row(bases, start, user_len, accent):
|
| 227 |
+
n = len(bases)
|
| 228 |
+
w = n * _HX_SP + 6
|
| 229 |
+
out = [f"<svg class='helix-svg' width='{w}' height='{_HX_ROWH}' "
|
| 230 |
+
f"viewBox='0 0 {w} {_HX_ROWH}' xmlns='http://www.w3.org/2000/svg'>",
|
| 231 |
+
f"<polyline class='hx-strand' style='stroke:{accent}' points='{_hx_strand(n,1)}'/>",
|
| 232 |
+
f"<polyline class='hx-strand back' style='stroke:{accent}' points='{_hx_strand(n,-1)}'/>"]
|
| 233 |
+
for i in range(n):
|
| 234 |
+
x = i * _HX_SP + _HX_SP / 2
|
| 235 |
+
ang = (i + 0.5) * 2 * math.pi / _HX_TURN
|
| 236 |
+
yt = _HX_YC + _HX_AMP * math.sin(ang); yb = _HX_YC - _HX_AMP * math.sin(ang)
|
| 237 |
+
kind = "user" if start + i < user_len else "gen"
|
| 238 |
+
out.append(f"<line class='hx-rung {kind}' x1='{x:.1f}' y1='{yt:.1f}' x2='{x:.1f}' y2='{yb:.1f}'/>")
|
| 239 |
+
for i in range(n):
|
| 240 |
+
x = i * _HX_SP + _HX_SP / 2
|
| 241 |
+
ang = (i + 0.5) * 2 * math.pi / _HX_TURN
|
| 242 |
+
yt = _HX_YC + _HX_AMP * math.sin(ang); yb = _HX_YC - _HX_AMP * math.sin(ang)
|
| 243 |
+
b = bases[i]; comp = _COMP.get(b, "N")
|
| 244 |
+
gen = start + i >= user_len
|
| 245 |
+
col = _BASE_COL.get(b, "#999") if gen else _USER_COL
|
| 246 |
+
ccol = _BASE_COL.get(comp, "#999") if gen else _USER_COL
|
| 247 |
+
op = "1" if gen else ".6"
|
| 248 |
+
out.append(f"<text class='hx-base' x='{x:.1f}' y='{yt:.1f}' style='fill:{col};opacity:{op}'>{b}</text>")
|
| 249 |
+
out.append(f"<text class='hx-base' x='{x:.1f}' y='{yb:.1f}' style='fill:{ccol};opacity:{op}'>{comp}</text>")
|
| 250 |
+
out.append("</svg>")
|
| 251 |
+
return "".join(out)
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def _helix(prompt, gen, accent, live=False):
|
| 255 |
+
bases = [c for c in (prompt + gen) if c in "ACGTN"]
|
| 256 |
+
user_len = len([c for c in prompt if c in "ACGTN"])
|
| 257 |
+
rows, i = [], 0
|
| 258 |
+
while i < len(bases):
|
| 259 |
+
rows.append(_hx_row(bases[i:i + _HX_PERROW], i, user_len, accent))
|
| 260 |
+
i += _HX_PERROW
|
| 261 |
+
caret = "<span class='caret'></span>" if live else ""
|
| 262 |
+
legend = ("<div class='hx-legend'>"
|
| 263 |
+
+ "".join(f"<span><span class='hx-dot' style='background:{_BASE_COL[b]}'></span>{b}</span>" for b in "ACGT")
|
| 264 |
+
+ f"<span><span class='hx-dot' style='background:{_USER_COL}'></span>your input</span></div>")
|
| 265 |
+
return f"<div class='helixwrap'>{''.join(rows) or ' '}{caret}</div>{legend}"
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def _seq_track(gen):
|
| 269 |
+
"""Per-base colored track of the generated bases (our take on the tokenization demo)."""
|
| 270 |
+
cells = "".join(f"<span class='seqb {c}'>{c}</span>" for c in gen if c in "ACGT")
|
| 271 |
+
return f"<div class='seqtrack'>{cells or ' '}</div>"
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def _kmer_strip(gen):
|
| 275 |
+
"""The generated sequence cut into our model's actual non-overlapping 6-mer tokens."""
|
| 276 |
+
s = "".join(c for c in gen if c in "ACGTN")
|
| 277 |
+
toks = [s[i:i + 6] for i in range(0, len(s) - len(s) % 6, 6)]
|
| 278 |
+
chips = "".join(f"<span class='tok kmer'>{t}</span>" for t in toks)
|
| 279 |
+
head = ("<div class='tkrow'><span class='tklabel'>6-mer tokens</span>"
|
| 280 |
+
f"<span class='tkstat'>tokens <span class='n'>{len(toks)}</span></span>"
|
| 281 |
+
f"<span class='tkstat'>bases <span class='n'>{len(s)}</span></span>"
|
| 282 |
+
"<span class='tkstat'>6 bases / token</span></div>")
|
| 283 |
+
return head + f"<div class='tokrow'>{chips}</div>"
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
FOOTER = ("Four ~74M DNA/RNA specialists (β295M total, <b>under Carbon-500M</b>), each distilled "
|
| 287 |
+
"per-domain from Carbon-500M. A learned router reads every specialist's surprise + hidden "
|
| 288 |
+
"state and routes to the home specialist β held-out routing accuracy <b>99.8%</b>. Only one "
|
| 289 |
+
"specialist runs per query (~7Γ cheaper than the 500M monolith).")
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
# ---- handler ----------------------------------------------------------------------
|
| 293 |
+
@_gpu(duration=120)
|
| 294 |
+
def route_run(seq, n_bases, do_gen, decode="auto"):
|
| 295 |
+
yield _notice("Routing & generating")
|
| 296 |
+
seq = (seq or "").strip()
|
| 297 |
+
if len(seq) < 18:
|
| 298 |
+
yield _msg("𧬠Enter a DNA sequence", "Paste at least 18 bases (A/C/G/T) β try an example below.")
|
| 299 |
+
return
|
| 300 |
+
dc = _moe()
|
| 301 |
+
doms = dc.domains
|
| 302 |
+
bpb = {d: None for d in doms}
|
| 303 |
+
# progressively reveal each specialist's surprise (the chain lighting up)
|
| 304 |
+
sc, hd = dc._scores_hidden(seq)
|
| 305 |
+
for d in doms:
|
| 306 |
+
bpb[d] = sc[d] / 6 / 0.6931
|
| 307 |
+
yield _wrap("<div class='h'>π Sending the sequence down the chainβ¦</div>" + _cards(bpb))
|
| 308 |
+
home, _ = dc.route(seq)
|
| 309 |
+
c = COLOR.get(home, "#9b59b6")
|
| 310 |
+
head = (f"<div class='h'>π§ Routed to <span style='color:{c}'>{EMOJI.get(home, home)}</span>"
|
| 311 |
+
f" β the specialist most at home with your sequence</div>" + _cards(bpb, winner=home))
|
| 312 |
+
if do_gen:
|
| 313 |
+
# decoding: default = base-pair (FNS) β marginalize the 6-mer softmax to six 4-way base
|
| 314 |
+
# distributions and sample each base, the same factorization Carbon uses. "argmax" forces
|
| 315 |
+
# the deterministic 6-mer best-guess (matches the recovery metric; collapses over long spans).
|
| 316 |
+
dl = (decode or "").lower()
|
| 317 |
+
greedy = "argmax" in dl
|
| 318 |
+
feed_ctx = seq # FULL context to the specialist (it frame-aligns + caps)
|
| 319 |
+
ctx = seq[-48:] # short context shown in the helix / text box
|
| 320 |
+
mode = "argmax Β· best guess" if greedy else "base-pair Β· FNS"
|
| 321 |
+
hxhead = (f"<div class='h'>𧬠{EMOJI.get(home, home)} β building the strand base-by-base "
|
| 322 |
+
f"<span style='color:var(--muted)'>({mode})</span></div>")
|
| 323 |
+
rawhdr = "<div class='h'>raw sequence β select to copy</div>"
|
| 324 |
+
if greedy:
|
| 325 |
+
stream = dc.generate_stream(home, length=int(n_bases), prompt=feed_ctx, greedy=True)
|
| 326 |
+
else:
|
| 327 |
+
stream = dc.generate_baselevel_stream(home, length=int(n_bases), temperature=1.0,
|
| 328 |
+
top_p=0.9, prompt=feed_ctx)
|
| 329 |
+
for gen in stream:
|
| 330 |
+
yield _wrap(head + hxhead + _helix(ctx, gen, c, live=True)
|
| 331 |
+
+ _seq_track(gen) + _kmer_strip(gen)
|
| 332 |
+
+ rawhdr + _gen_box(ctx, gen, live=True))
|
| 333 |
+
_WARMED["done"] = True
|
| 334 |
+
gennote = ("<div class='sub'>π§ͺ <b>Generation is exploratory</b> β these ~74M specialists are "
|
| 335 |
+
"trained on a slice of the corpus, so sampled DNA is low-complexity (and splice / "
|
| 336 |
+
"bacterial domains are genuinely AT-rich). The <b>routing</b> and per-base likelihood "
|
| 337 |
+
"are the result here, not Carbon-level generation.</div>")
|
| 338 |
+
yield _wrap(head + hxhead + _helix(ctx, gen, c, live=False)
|
| 339 |
+
+ _seq_track(gen) + _kmer_strip(gen)
|
| 340 |
+
+ rawhdr + _gen_box(ctx, gen, live=False)
|
| 341 |
+
+ gennote + f"<div class='sub'>{FOOTER}</div>")
|
| 342 |
+
else:
|
| 343 |
+
_WARMED["done"] = True
|
| 344 |
+
yield _wrap(head + f"<div class='sub'>{FOOTER}</div>")
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
STATS_HTML = _wrap(
|
| 348 |
+
"<div class='h'>π DaisyChain vs Carbon-500M β the fair baseline</div>"
|
| 349 |
+
"<div class='stats'>"
|
| 350 |
+
"<div class='stat'><div class='v' style='color:#37b24d'>99.8%</div>"
|
| 351 |
+
"<div class='l'>routing accuracy<br>(held-out)</div></div>"
|
| 352 |
+
"<div class='stat'><div class='v' style='color:#7c5cff'>β295M</div>"
|
| 353 |
+
"<div class='l'>total params<br>(4 Γ ~74M) < Carbon-500M</div></div>"
|
| 354 |
+
"<div class='stat'><div class='v' style='color:#22b8cf'>~7Γ</div>"
|
| 355 |
+
"<div class='l'>cheaper per query<br>(one 74M specialist active)</div></div>"
|
| 356 |
+
"</div>"
|
| 357 |
+
"<table><tr><th>metric</th><th>DaisyChain</th><th>Carbon-500M</th></tr>"
|
| 358 |
+
"<tr><td>Likelihood β bits/base (β better)</td><td class='n'>1.79</td><td class='n'>1.75</td></tr>"
|
| 359 |
+
"<tr><td>Seq-recovery, eukaryote (β better)</td><td class='n'>31.0%</td><td class='n'>42.2%</td></tr>"
|
| 360 |
+
"<tr><td>Seq-recovery, bacteria (β better)</td><td class='n'>36.5%</td><td class='n'>49.5%</td></tr>"
|
| 361 |
+
"</table>"
|
| 362 |
+
"<div class='sub'>Four ~74M specialists (β295M total, <b>under Carbon-500M</b>); only one runs per "
|
| 363 |
+
"query, so it's ~7Γ cheaper per token. Behind the 500M / 1T-token monolith but within striking "
|
| 364 |
+
"distance β the gap is concentrated in the structured domains (mRNA, bacteria) and keeps closing "
|
| 365 |
+
"with more per-domain training. Same protocols as Carbon's eval suite (sequence recovery; per-base "
|
| 366 |
+
"likelihood). Carbon-500M is the right yardstick for a sub-500M modular set, not the 3B flagship.</div>")
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
BANNER = _CSS + """
|
| 370 |
+
<div class='dcx'><div class='dc-banner'><div class='dc-binner'>
|
| 371 |
+
<div class='dc-ident'>
|
| 372 |
+
<span class='dc-mark'>πΌ</span>
|
| 373 |
+
<div>
|
| 374 |
+
<div class='dc-title'>DAISYCHAIN</div>
|
| 375 |
+
<div class='dc-path'>DAISYCHAINAI / GENOMICS Β· ROUTED DNA SPECIALISTS</div>
|
| 376 |
+
</div>
|
| 377 |
+
</div>
|
| 378 |
+
<div class='dc-word'>DaisyChain<span class='dot'>.</span></div>
|
| 379 |
+
<div class='dc-tag'>A modular genomic mind. Four dense ~74M DNA/RNA specialists
|
| 380 |
+
(≈295M total, <b>under Carbon-500M</b>), each distilled per-domain from Carbon-500M.
|
| 381 |
+
A learned router reads how <i>surprised</i> each specialist is by your sequence
|
| 382 |
+
(bits/base) plus its hidden state, then hands the work to its home specialist β
|
| 383 |
+
held-out routing accuracy <b>99.8%</b>. Watch it route in real time.</div>
|
| 384 |
+
<div class='dc-motif'>
|
| 385 |
+
<span class='dc-chip'>𧬠Eukaryote</span><span class='dc-tie'>β</span>
|
| 386 |
+
<span class='dc-chip'>π¦ Prokaryote</span><span class='dc-tie'>β</span>
|
| 387 |
+
<span class='dc-chip'>π mRNA</span><span class='dc-tie'>β</span>
|
| 388 |
+
<span class='dc-chip'>βοΈ mRNA-splice</span>
|
| 389 |
+
</div>
|
| 390 |
+
</div></div></div>"""
|
| 391 |
+
|
| 392 |
+
# Light editorial chrome for the Gradio shell so the cream paper extends to the whole page.
|
| 393 |
+
# We pin Gradio's theme CSS variables (for BOTH light and .dark) to the paper palette so the
|
| 394 |
+
# text never renders white-on-cream when a visitor's browser/Space defaults to dark mode.
|
| 395 |
+
_PAGE_CSS = """
|
| 396 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
| 397 |
+
.gradio-container, .gradio-container.dark, .dark, body, body.dark{
|
| 398 |
+
--body-background-fill:#f7f5ee!important;
|
| 399 |
+
--background-fill-primary:#f7f5ee!important;
|
| 400 |
+
--background-fill-secondary:#f2efe2!important;
|
| 401 |
+
--block-background-fill:#fbfaf4!important;
|
| 402 |
+
--block-label-background-fill:#f2efe2!important;
|
| 403 |
+
--input-background-fill:#fffdf6!important;
|
| 404 |
+
--body-text-color:#1f1f1d!important;
|
| 405 |
+
--body-text-color-subdued:#5a5a55!important;
|
| 406 |
+
--block-title-text-color:#1f1f1d!important;
|
| 407 |
+
--block-label-text-color:#5a5a55!important;
|
| 408 |
+
--block-info-text-color:#5a5a55!important;
|
| 409 |
+
--border-color-primary:#d6d3c4!important;
|
| 410 |
+
--neutral-50:#f7f5ee!important;
|
| 411 |
+
--table-even-background-fill:#fbfaf4!important;
|
| 412 |
+
--table-odd-background-fill:#f2efe2!important;
|
| 413 |
+
--table-row-focus:#eef3e9!important;
|
| 414 |
+
--table-text-color:#1f1f1d!important;
|
| 415 |
+
background:#f7f5ee!important;
|
| 416 |
+
color:#1f1f1d!important;
|
| 417 |
+
}
|
| 418 |
+
/* Examples dataset table rows (were rendering black in dark mode) */
|
| 419 |
+
.gradio-container .gr-samples-table, .gradio-container [class*='dataset'] table,
|
| 420 |
+
.gradio-container [class*='dataset'] td, .gradio-container [class*='dataset'] tr,
|
| 421 |
+
.gradio-container [class*='dataset'] tbody{background:#fbfaf4!important;color:#1f1f1d!important}
|
| 422 |
+
.gradio-container [class*='dataset'] tr:nth-child(even) td{background:#f2efe2!important}
|
| 423 |
+
/* Radio / checkbox options (were rendering black in dark mode) */
|
| 424 |
+
.gradio-container [class*='radio'] label, .gradio-container fieldset label,
|
| 425 |
+
.gradio-container [class*='checkbox'] label, .gradio-container .wrap label{
|
| 426 |
+
background:#fbfaf4!important;color:#1f1f1d!important;border:1px solid #d6d3c4!important}
|
| 427 |
+
.gradio-container [class*='radio'] label *, .gradio-container fieldset label *,
|
| 428 |
+
.gradio-container [class*='checkbox'] label *{color:#1f1f1d!important}
|
| 429 |
+
.gradio-container input[type=radio],.gradio-container input[type=checkbox]{accent-color:#317f3f!important}
|
| 430 |
+
.gradio-container{font-family:"Inter","Helvetica Neue",sans-serif!important;max-width:1080px!important}
|
| 431 |
+
/* force any Gradio-rendered label / markdown / example text to ink, never white */
|
| 432 |
+
.gradio-container label, .gradio-container .prose, .gradio-container p,
|
| 433 |
+
.gradio-container span, .gradio-container td, .gradio-container th,
|
| 434 |
+
.gradio-container .gr-text-input, .gradio-container input, .gradio-container textarea{color:#1f1f1d!important}
|
| 435 |
+
.gradio-container input::placeholder, .gradio-container textarea::placeholder{color:#9c9989!important}
|
| 436 |
+
footer{display:none!important}
|
| 437 |
+
.gr-button-primary, button.primary{background:#317f3f!important;border:1px solid #2a5931!important;color:#f7f5ee!important;
|
| 438 |
+
font-family:"JetBrains Mono",monospace!important;letter-spacing:.08em!important;text-transform:uppercase!important;font-size:12px!important}
|
| 439 |
+
.gr-button-primary:hover, button.primary:hover{background:#2a5931!important}
|
| 440 |
+
.gr-button-primary *, button.primary *{color:#f7f5ee!important}
|
| 441 |
+
"""
|
| 442 |
+
|
| 443 |
+
|
| 444 |
+
def build():
|
| 445 |
+
theme = gr.themes.Default(primary_hue="green", neutral_hue="stone",
|
| 446 |
+
font=[gr.themes.GoogleFont("Inter"), "sans-serif"],
|
| 447 |
+
font_mono=[gr.themes.GoogleFont("JetBrains Mono"), "monospace"])
|
| 448 |
+
# force the light palette so text is never white-on-cream if a visitor defaults to dark mode
|
| 449 |
+
_force_light = ("() => { const u = new URL(window.location.href);"
|
| 450 |
+
" if (u.searchParams.get('__theme') !== 'light') {"
|
| 451 |
+
" u.searchParams.set('__theme','light'); window.location.replace(u.href); } }")
|
| 452 |
+
with gr.Blocks(title="DaisyChain β modular genomic mind", theme=theme, css=_PAGE_CSS,
|
| 453 |
+
js=_force_light) as demo:
|
| 454 |
+
gr.HTML(BANNER)
|
| 455 |
+
with gr.Row():
|
| 456 |
+
seq = gr.Textbox(label="DNA SEQUENCE", lines=3, scale=4,
|
| 457 |
+
placeholder="ACGT⦠(eukaryotic, bacterial, mRNA, or splice-site DNA)")
|
| 458 |
+
n = gr.Slider(60, 300, value=90, step=30, label="GENERATE BASES", scale=1)
|
| 459 |
+
with gr.Row():
|
| 460 |
+
gen_ck = gr.Checkbox(value=True, label="stream a continuation from the routed specialist")
|
| 461 |
+
decode = gr.Radio(["base-pair (FNS)", "greedy (argmax)"], value="base-pair (FNS)",
|
| 462 |
+
label="DECODING", scale=1,
|
| 463 |
+
info="base-pair (FNS) = Carbon-style: each base sampled from the marginalized per-position distribution (base-pair control, no 6-mer repeat loops); argmax = deterministic best guess (matches recovery, collapses over long spans)")
|
| 464 |
+
btn = gr.Button("π Route through the DaisyChain", variant="primary")
|
| 465 |
+
out = gr.HTML(_wrap("<div class='h'>The chain Β· paste a sequence to light it up</div>"
|
| 466 |
+
+ _cards({d: None for d in DaisyChain.DESCRIPTIONS})))
|
| 467 |
+
btn.click(route_run, [seq, n, gen_ck, decode], out)
|
| 468 |
+
try:
|
| 469 |
+
ex = json.load(open(os.path.join(HERE, "examples.json")))
|
| 470 |
+
gr.Examples([[v, 90, True, "base-pair (FNS)"] for v in ex.values()],
|
| 471 |
+
inputs=[seq, n, gen_ck, decode],
|
| 472 |
+
label="Example sequences (one per domain)")
|
| 473 |
+
except Exception:
|
| 474 |
+
pass
|
| 475 |
+
gr.HTML(STATS_HTML)
|
| 476 |
+
return demo
|
| 477 |
+
|
| 478 |
+
|
| 479 |
+
if __name__ == "__main__":
|
| 480 |
+
build().launch()
|