""" DaisyChain β€” interactive routing demo (HuggingFace Space). Paste DNA; the learned router reads how *surprised* each ~74M specialist is (bits/base) plus its hidden state and hands the sequence to its home specialist β€” then that specialist streams a continuation live. Styled after the Modular-Mind panel: animated routing cards, a first-run loading notice, live token streaming. Every handler is a generator. """ import html as _h import os import json import math import gradio as gr # ZeroGPU: @spaces.GPU allocates a GPU only for the decorated call. Falls back to a no-op # decorator when `spaces` isn't installed (local / plain CPU). try: import spaces _gpu = spaces.GPU except Exception: def _gpu(fn=None, **kw): return fn if callable(fn) else (lambda f: f) from daisychain import DaisyChain HERE = os.path.dirname(os.path.abspath(__file__)) MODEL_REPO = os.environ.get("DAISYCHAIN_REPO", "DaisyChainAI/daisychain-genomics") DEVICE = os.environ.get("DAISYCHAIN_DEVICE", "cpu") # code + tokenizer + router are bundled here; pull the big specialist weights from the # (gated) model repo on first launch using the HF_TOKEN Space secret. No silent # swallow β€” if the download fails we want a visible error, not a broken-but-running app. if not os.path.exists(os.path.join(HERE, "eukaryote", "model.safetensors")): from huggingface_hub import snapshot_download snapshot_download(MODEL_REPO, local_dir=HERE, token=os.environ.get("HF_TOKEN"), allow_patterns=["*/model.safetensors", "tokenizer.json", "router2.pt"]) _DC = {"m": None} # lazy-loaded so CUDA is never touched at import _WARMED = {"done": False} # so the "loading" notice only shows on the first run EMOJI = {"eukaryote": "🧬 Eukaryote", "prokaryote": "🦠 Prokaryote", "mrna": "πŸ“œ mRNA", "mrna_splice": "βœ‚οΈ mRNA-splice"} # deeper, paper-friendly tones (vs the old neon dark-theme hues) COLOR = {"eukaryote": "#5b4bb0", "prokaryote": "#1f7a99", "mrna": "#b03a63", "mrna_splice": "#317f3f"} DESC = DaisyChain.DESCRIPTIONS def _moe(): if _DC["m"] is None: _DC["m"] = DaisyChain(root=HERE, device=DEVICE) return _DC["m"] # ---- HTML rendering ---------------------------------------------------------------- # Editorial "scientific-paper" aesthetic borrowed from Carbon's demo (cream paper, ink, # green accent, mono uppercase headers) but made our own: a daisy-gold second accent and # the chain-of-specialists motif. Tokens live in :root so the cards/bars all stay in sync. _CSS = """""" def _wrap(body): return _CSS + "
" + body + "
" def _esc(s): return _h.escape(s or "").replace("\n", "
") def _notice(action="Routing"): if not _WARMED["done"]: try: gr.Info("First run β€” loading the four ~74M specialists (~20–40s on CPU). After this it's quick.") except Exception: pass return _wrap(f"
⏳ Loading the four ~74M specialists + {action.lower()}… " "first run can take ~20–40s on CPU; every run after is fast.
") return _wrap(f"
⏳ {action}…
") def _msg(title, body): return _wrap(f"
{title}
{body}
") def _cards(bpb, winner=None): """One animated card per specialist: surprise (bits/base), confidence bar, winner badge + glow. bpb values may be None (not computed yet). Lower bits/base = more 'at home' = fuller bar.""" cells = [] doms = list(bpb.keys()) for i, n in enumerate(doms): c = COLOR.get(n, "#9b59b6") v = bpb[n] win = (n == winner) 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 style = f"border-color:{c};box-shadow:0 0 16px {c}40" if win else "" badge = f"ROUTED βœ“" if win else "" meta = (f"{DESC.get(n,'')}
{v:.3f} bits/base (lower = more at home)" if v is not None else f"{DESC.get(n,'')}
…") bar = (f"
" f"
confidence {conf*100:.0f}%
") if v is not None else \ "
…
" cells.append( f"
{badge}" f"
{EMOJI.get(n, n)}
" f"
{meta}
{bar}
") if i < len(doms) - 1: cells.append("") return "
" + "".join(cells) + "
" def _gen_box(prompt, gen, live=False): caret = "" if live else "" return (f"
{_esc(prompt)}" f"{_esc(gen)}{caret}
") # ---- live 2D double-helix -------------------------------------------------------- # A base-by-base SVG double-helix (two sine-wave backbones + per-base rungs, each base # letter on the top strand with its Watson–Crick complement on the bottom). Built in # Python so it streams inside our existing generator; ours = cream palette, our own # nucleotide colors, strands tinted by the routed specialist's accent. _COMP = {"A": "T", "T": "A", "C": "G", "G": "C", "N": "N"} # shared nucleotide palette (matches our tokenization track): A green, T red, C blue, G amber _BASE_COL = {"A": "#1A7A40", "T": "#b00020", "C": "#2c5aa0", "G": "#b8862c"} _USER_COL = "#bdbaa9" _HX_SP, _HX_AMP, _HX_YC, _HX_ROWH, _HX_TURN, _HX_PERROW = 14, 12, 22, 48, 10.5, 46 def _hx_strand(n, sign): pts = [] for s in range(n * 4 + 1): t = s / 4 x = t * _HX_SP + _HX_SP / 2 ang = (t + 0.5) * 2 * math.pi / _HX_TURN y = _HX_YC + sign * _HX_AMP * math.sin(ang) pts.append(f"{x:.1f},{y:.1f}") return " ".join(pts) def _hx_row(bases, start, user_len, accent): n = len(bases) w = n * _HX_SP + 6 out = [f"", f"", f""] for i in range(n): x = i * _HX_SP + _HX_SP / 2 ang = (i + 0.5) * 2 * math.pi / _HX_TURN yt = _HX_YC + _HX_AMP * math.sin(ang); yb = _HX_YC - _HX_AMP * math.sin(ang) kind = "user" if start + i < user_len else "gen" out.append(f"") for i in range(n): x = i * _HX_SP + _HX_SP / 2 ang = (i + 0.5) * 2 * math.pi / _HX_TURN yt = _HX_YC + _HX_AMP * math.sin(ang); yb = _HX_YC - _HX_AMP * math.sin(ang) b = bases[i]; comp = _COMP.get(b, "N") gen = start + i >= user_len col = _BASE_COL.get(b, "#999") if gen else _USER_COL ccol = _BASE_COL.get(comp, "#999") if gen else _USER_COL op = "1" if gen else ".6" out.append(f"{b}") out.append(f"{comp}") out.append("") return "".join(out) def _helix(prompt, gen, accent, live=False): bases = [c for c in (prompt + gen) if c in "ACGTN"] user_len = len([c for c in prompt if c in "ACGTN"]) rows, i = [], 0 while i < len(bases): rows.append(_hx_row(bases[i:i + _HX_PERROW], i, user_len, accent)) i += _HX_PERROW caret = "" if live else "" legend = ("
" + "".join(f"{b}" for b in "ACGT") + f"your input
") return f"
{''.join(rows) or ' '}{caret}
{legend}" def _seq_track(gen): """Per-base colored track of the generated bases (our take on the tokenization demo).""" cells = "".join(f"{c}" for c in gen if c in "ACGT") return f"
{cells or ' '}
" def _kmer_strip(gen): """The generated sequence cut into our model's actual non-overlapping 6-mer tokens.""" s = "".join(c for c in gen if c in "ACGTN") toks = [s[i:i + 6] for i in range(0, len(s) - len(s) % 6, 6)] chips = "".join(f"{t}" for t in toks) head = ("
6-mer tokens" f"tokens {len(toks)}" f"bases {len(s)}" "6 bases / token
") return head + f"
{chips}
" FOOTER = ("Four ~74M DNA/RNA specialists (β‰ˆ295M total, under Carbon-500M), each distilled " "per-domain from Carbon-500M. A learned router reads every specialist's surprise + hidden " "state and routes to the home specialist β€” held-out routing accuracy 100.0%. Only one " "specialist runs per query (~7Γ— cheaper than the 500M monolith).") # ---- handler ---------------------------------------------------------------------- @_gpu(duration=120) def route_run(seq, n_bases, do_gen, decode="auto"): yield _notice("Routing & generating") seq = (seq or "").strip() if len(seq) < 18: yield _msg("🧬 Enter a DNA sequence", "Paste at least 18 bases (A/C/G/T) β€” try an example below.") return dc = _moe() doms = dc.domains bpb = {d: None for d in doms} # progressively reveal each specialist's surprise (the chain lighting up) sc, hd = dc._scores_hidden(seq) for d in doms: bpb[d] = sc[d] / 6 / 0.6931 yield _wrap("
πŸ”— Sending the sequence down the chain…
" + _cards(bpb)) home, _ = dc.route(seq) c = COLOR.get(home, "#9b59b6") head = (f"
🧭 Routed to {EMOJI.get(home, home)}" f" β€” the specialist most at home with your sequence
" + _cards(bpb, winner=home)) if do_gen: # decoding: default = base-pair (FNS) β€” marginalize the 6-mer softmax to six 4-way base # distributions and sample each base, the same factorization Carbon uses. "argmax" forces # the deterministic 6-mer best-guess (matches the recovery metric; collapses over long spans). dl = (decode or "").lower() greedy = "argmax" in dl feed_ctx = seq # FULL context to the specialist (it frame-aligns + caps) ctx = seq[-48:] # short context shown in the helix / text box mode = "base-pair Β· FNS argmax" if greedy else "base-pair Β· FNS sampled" hxhead = (f"
🧬 {EMOJI.get(home, home)} β€” building the strand base-by-base " f"({mode})
") rawhdr = "
raw sequence β€” select to copy
" # both modes use Carbon's FNS base-level decoder; greedy=argmax per base (recovery metric), # else top-p sampled per base. stream = dc.generate_baselevel_stream(home, length=int(n_bases), temperature=1.0, top_p=0.9, prompt=feed_ctx, greedy=greedy) for gen in stream: yield _wrap(head + hxhead + _helix(ctx, gen, c, live=True) + _seq_track(gen) + _kmer_strip(gen) + rawhdr + _gen_box(ctx, gen, live=True)) _WARMED["done"] = True gennote = ("
πŸ§ͺ Generation is exploratory β€” these ~74M specialists are " "trained on a slice of the corpus, so sampled DNA is low-complexity (and splice / " "bacterial domains are genuinely AT-rich). The routing and per-base likelihood " "are the result here, not Carbon-level generation.
") yield _wrap(head + hxhead + _helix(ctx, gen, c, live=False) + _seq_track(gen) + _kmer_strip(gen) + rawhdr + _gen_box(ctx, gen, live=False) + gennote + f"
{FOOTER}
") else: _WARMED["done"] = True yield _wrap(head + f"
{FOOTER}
") STATS_HTML = _wrap( "
πŸ“Š DaisyChain vs Carbon-500M β€” the fair baseline
" "
" "
100.0%
" "
routing accuracy
(held-out)
" "
β‰ˆ295M
" "
total params
(4 Γ— ~74M) < Carbon-500M
" "
~7Γ—
" "
cheaper per query
(one 74M specialist active)
" "
" "" "" "" "" "
metricDaisyChainCarbon-500M
Likelihood β€” bits/base, base-pair FNS (↓ better)1.881.79
Seq-recovery, eukaryote β€” FNS (↑ better)31.5%38.9%
Seq-recovery, bacteria β€” FNS (↑ better)40.9%54.1%
" "
Four ~74M specialists (β‰ˆ295M total, under Carbon-500M); only one runs per " "query, so it's ~7Γ— cheaper per token. Behind the 500M / 1T-token monolith but within striking " "distance β€” the gap is concentrated in the structured domains (mRNA, bacteria) and keeps closing " "with more per-domain training. Same protocols as Carbon's eval suite (sequence recovery; per-base " "likelihood). Carbon-500M is the right yardstick for a sub-500M modular set, not the 3B flagship.
") BANNER = _CSS + """
🌼
DAISYCHAIN
DAISYCHAINAI / GENOMICS Β· ROUTED DNA SPECIALISTS
DaisyChain.
A modular genomic mind. Four dense ~74M DNA/RNA specialists (≈295M total, under Carbon-500M), each distilled per-domain from Carbon-500M. A learned router reads how surprised each specialist is by your sequence (bits/base) plus its hidden state, then hands the work to its home specialist β€” held-out routing accuracy 100.0%. Watch it route in real time.
🧬 Eukaryoteβ€” 🦠 Prokaryoteβ€” πŸ“œ mRNAβ€” βœ‚οΈ mRNA-splice
""" # Light editorial chrome for the Gradio shell so the cream paper extends to the whole page. # We pin Gradio's theme CSS variables (for BOTH light and .dark) to the paper palette so the # text never renders white-on-cream when a visitor's browser/Space defaults to dark mode. _PAGE_CSS = """ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=JetBrains+Mono:wght@400;500;700&display=swap'); .gradio-container, .gradio-container.dark, .dark, body, body.dark{ --body-background-fill:#f7f5ee!important; --background-fill-primary:#f7f5ee!important; --background-fill-secondary:#f2efe2!important; --block-background-fill:#fbfaf4!important; --block-label-background-fill:#f2efe2!important; --input-background-fill:#fffdf6!important; --body-text-color:#1f1f1d!important; --body-text-color-subdued:#5a5a55!important; --block-title-text-color:#1f1f1d!important; --block-label-text-color:#5a5a55!important; --block-info-text-color:#5a5a55!important; --border-color-primary:#d6d3c4!important; --neutral-50:#f7f5ee!important; --table-even-background-fill:#fbfaf4!important; --table-odd-background-fill:#f2efe2!important; --table-row-focus:#eef3e9!important; --table-text-color:#1f1f1d!important; background:#f7f5ee!important; color:#1f1f1d!important; } /* Examples dataset table rows (were rendering black in dark mode) */ .gradio-container .gr-samples-table, .gradio-container [class*='dataset'] table, .gradio-container [class*='dataset'] td, .gradio-container [class*='dataset'] tr, .gradio-container [class*='dataset'] tbody{background:#fbfaf4!important;color:#1f1f1d!important} .gradio-container [class*='dataset'] tr:nth-child(even) td{background:#f2efe2!important} /* Radio / checkbox options (were rendering black in dark mode) */ .gradio-container [class*='radio'] label, .gradio-container fieldset label, .gradio-container [class*='checkbox'] label, .gradio-container .wrap label{ background:#fbfaf4!important;color:#1f1f1d!important;border:1px solid #d6d3c4!important} .gradio-container [class*='radio'] label *, .gradio-container fieldset label *, .gradio-container [class*='checkbox'] label *{color:#1f1f1d!important} .gradio-container input[type=radio],.gradio-container input[type=checkbox]{accent-color:#317f3f!important} .gradio-container{font-family:"Inter","Helvetica Neue",sans-serif!important;max-width:1080px!important} /* force any Gradio-rendered label / markdown / example text to ink, never white */ .gradio-container label, .gradio-container .prose, .gradio-container p, .gradio-container span, .gradio-container td, .gradio-container th, .gradio-container .gr-text-input, .gradio-container input, .gradio-container textarea{color:#1f1f1d!important} .gradio-container input::placeholder, .gradio-container textarea::placeholder{color:#9c9989!important} footer{display:none!important} .gr-button-primary, button.primary{background:#317f3f!important;border:1px solid #2a5931!important;color:#f7f5ee!important; font-family:"JetBrains Mono",monospace!important;letter-spacing:.08em!important;text-transform:uppercase!important;font-size:12px!important} .gr-button-primary:hover, button.primary:hover{background:#2a5931!important} .gr-button-primary *, button.primary *{color:#f7f5ee!important} """ def build(): theme = gr.themes.Default(primary_hue="green", neutral_hue="stone", font=[gr.themes.GoogleFont("Inter"), "sans-serif"], font_mono=[gr.themes.GoogleFont("JetBrains Mono"), "monospace"]) # force the light palette so text is never white-on-cream if a visitor defaults to dark mode _force_light = ("() => { const u = new URL(window.location.href);" " if (u.searchParams.get('__theme') !== 'light') {" " u.searchParams.set('__theme','light'); window.location.replace(u.href); } }") with gr.Blocks(title="DaisyChain β€” modular genomic mind", theme=theme, css=_PAGE_CSS, js=_force_light) as demo: gr.HTML(BANNER) with gr.Row(): seq = gr.Textbox(label="DNA SEQUENCE", lines=3, scale=4, placeholder="ACGT… (eukaryotic, bacterial, mRNA, or splice-site DNA)") n = gr.Slider(60, 300, value=90, step=30, label="GENERATE BASES", scale=1) with gr.Row(): gen_ck = gr.Checkbox(value=True, label="stream a continuation from the routed specialist") decode = gr.Radio(["base-pair (FNS)", "greedy (argmax)"], value="base-pair (FNS)", label="DECODING", scale=1, 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)") btn = gr.Button("πŸ”— Route through the DaisyChain", variant="primary") out = gr.HTML(_wrap("
The chain Β· paste a sequence to light it up
" + _cards({d: None for d in DaisyChain.DESCRIPTIONS}))) btn.click(route_run, [seq, n, gen_ck, decode], out) try: ex = json.load(open(os.path.join(HERE, "examples.json"))) gr.Examples([[v, 90, True, "base-pair (FNS)"] for v in ex.values()], inputs=[seq, n, gen_ck, decode], label="Example sequences (one per domain)") except Exception: pass gr.HTML(STATS_HTML) return demo if __name__ == "__main__": build().launch()