File size: 14,063 Bytes
03677d0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | import {useId} from 'react';
import config from '../t5config_large.json';
import {mix, phase, segment, windowAt, residuePreviewCount, sharingState} from './dualStory';
import type {Example} from './types';
const blue = '#397cbb', orange = '#cf643e', ink = '#24364a';
const words = (s:string) => s.replace(/([.!?])(?=[A-Z])/g,'$1 ').split(/\s+/);
function wrap(s:string, limit:number) {
const lines:string[] = []; let line = '';
words(s).forEach(word => { if ((line+' '+word).length > limit && line) { lines.push(line); line = word; } else line += (line?' ':'')+word; });
if (line) lines.push(line);
return lines;
}
function Node({x,y,protein,decoder,focus,context=0,sharing=0}:{x:number;y:number;protein:boolean;decoder:boolean;focus:number;context?:number;sharing?:number}) {
const color = protein?orange:blue;
const rows = decoder?['Masked self-attention','Cross-attention','Feed-forward']:['Self-attention','Feed-forward'];
return <g transform={`translate(${x} ${y})`} opacity={.24+.76*focus} data-module={`${protein?'protein':'text'}-${decoder?'decoder':'encoder'}`}>
{[12,6].map(n=><rect key={n} x={n} y={-n} width={228-n} height="200" rx="12" fill="#f8fafc" stroke={color} strokeOpacity=".2"/>)}
<rect width="228" height="200" rx="12" fill={protein?'#fff5ef':'#eff6fc'} stroke={color} strokeWidth={1+focus*.6}/>
<text x="17" y="25" className="ds-micro" fill={color}>{decoder?'AUTOREGRESSIVE DECODER':'CONTEXTUAL ENCODER'}</text>
<text x="17" y="54" className="ds-node-title" fill={ink}>{protein?'Protein':'Text'} {decoder?'Decoder':'Encoder'}</text>
<text x="207" y="25" textAnchor="end" className="ds-count" fill={color}>×{decoder?config.num_decoder_layers:config.num_layers}</text>
{rows.map((label,i)=>{
const rowY = 73+i*(decoder?34:51);
return <g key={label} data-shared-attention={i===0?sharing.toFixed(4):undefined}>
{i===0&&<rect x="9" y={rowY-5} width="210" height="37" rx="8" fill={color} fillOpacity={sharing*.1} stroke={color} strokeOpacity={sharing} strokeWidth={sharing*2.5}/>}
<rect x="14" y={rowY} width="200" height="27" rx="5" fill={i===0&&sharing>.1?color:label==='Cross-attention'&&context>0?color:'#ffffff'} fillOpacity={i===0?.8-sharing*.66:label==='Cross-attention'? .14+.5*context:.8} stroke={color} strokeOpacity={label==='Cross-attention'?.5:.14}/>
<text x="25" y={rowY+19} className="ds-row" fill={label==='Cross-attention'&&context>.65?'#fff':color}>{label}</text>
{i===0&&<text x="204" y={rowY+18} textAnchor="end" className="ds-shared-tag" fill={color} opacity={sharing}>TIED</text>}
</g>;
})}
<text x="17" y="190" className="ds-small" fill={color}>Block detail · d = {config.d_model.toLocaleString('en-US')}</text>
</g>;
}
function Projection({x,y,reverse,focus}:{x:number;y:number;reverse:boolean;focus:number}) {
return <g opacity={.24+.76*focus} transform={`translate(${x} ${y})`} data-projection={reverse?'p2t':'t2p'}>
<rect width="140" height="116" rx="9" fill="#fff" stroke="#8093a7"/>
<text x="70" y="25" textAnchor="middle" className="ds-micro" fill="#5f7286">LINEAR PROJECTION</text>
<text x="70" y="54" textAnchor="middle" className="ds-projection-name" fill={ink}>{reverse?'Wₚ→ₜ':'Wₜ→ₚ'}</text>
<rect x="10" y="67" width="120" height="29" rx="4" fill="#eef3f7"/>
<path d="M0 76 H140" stroke="#a8bacb" strokeOpacity=".5"/>
<text x="70" y="139" textAnchor="middle" className="ds-small" fill="#66788b">{config.d_model} → {config.d_model}</text>
</g>;
}
function StatePacket({x,y,color,opacity=1}:{x:number;y:number;color:string;opacity?:number}) {
return <g transform={`translate(${x} ${y})`} opacity={opacity} data-kind="hidden-state">{Array.from({length:6},(_,i)=><rect key={i} x={(i%2)*7} y={Math.floor(i/2)*7} width="5" height="5" rx="1" fill={color} fillOpacity={.45+(i%3)*.25}/>)}</g>;
}
function Lane({p,reverse,overview,id}:{p:number;reverse:boolean;overview:number;id:string}) {
const encoderX=reverse?600:52, decoderX=reverse?52:600;
const y=reverse?mix(230,413,overview):mix(230,119,overview);
const alpha=reverse?Math.max(phase(p,.515,.54),overview):Math.max(1-phase(p,.493,.515),overview);
const encoded=phase(p,reverse?.568:.072,reverse?.612:.16);
const projected=phase(p,reverse?.612:.18,reverse?.666:.30);
const writing=windowAt(p,reverse?.68:.32,reverse?.825:.493);
const encoding=reverse?windowAt(p,.515,.61):1-phase(p,.16,.185);
const projecting=reverse?windowAt(p,.61,.677):windowAt(p,.18,.315);
const share=sharingState(p);
const emphasis=(protein:boolean)=>Math.max(.3,protein?1-share.textFocus*.7:1-share.proteinFocus*.7);
const eFocus=Math.max(encoding,overview*emphasis(reverse)),dFocus=Math.max(writing,projecting*.55,overview*emphasis(!reverse)),wFocus=Math.max(projecting,overview*(1-Math.max(share.textFocus,share.proteinFocus)*.7));
const shared=(protein:boolean)=>protein?Math.max(share.proteinFocus,share.both*.65):Math.max(share.textFocus,share.both*.65);
const direction=reverse?-1:1;
const start=reverse?600:280, end=reverse?280:600;
return <g opacity={alpha} data-lane={reverse?'p2t':'t2p'}>
<g opacity={.25+.75*Math.max(projecting,writing,overview)}>
<path d={`M${start} ${y+123} H${end}`} fill="none" stroke="#c9d6e2" strokeWidth="2"/>
<path d={`M${start} ${y+123} H${end}`} fill="none" stroke={`url(#${id}-flow${reverse?'-reverse':''})`} strokeWidth="2.5" pathLength="1" strokeDasharray="1" strokeDashoffset={1-Math.max(projected,overview)} markerEnd={`url(#${id}-${reverse?'blue':'orange'}-arrow)`}/>
</g>
<Node x={encoderX} y={y} protein={reverse} decoder={false} focus={eFocus} sharing={shared(reverse)}/>
<Node x={decoderX} y={y} protein={!reverse} decoder focus={dFocus} sharing={shared(!reverse)} context={projected*(1-overview)}/>
<Projection x={370} y={y+47} reverse={reverse} focus={wFocus}/>
<g opacity={(1-overview)*encoded}>
<text x="440" y={y+4} textAnchor="middle" className="ds-micro" fill="#5c7085">HIDDEN STATES</text>
<text x="440" y={y+22} textAnchor="middle" className="ds-small" fill="#748598">Illustrative vectors</text>
{Array.from({length:7},(_,i)=>{
const travel=phase(p,(reverse?.612:.18)+i*.006,(reverse?.639:.255)+i*.006);
const emerge=phase(p,(reverse?.575:.095)+i*.005,(reverse?.595:.14)+i*.005);
const x=mix(start+direction*(12+i*9),end-direction*21,travel);
return <StatePacket key={i} x={x-6} y={y+113} color={travel>.55?(reverse?blue:orange):(reverse?orange:blue)} opacity={emerge*(1-phase(travel,.88,1))}/>;
})}
{Array.from({length:5},(_,i)=><StatePacket key={i} x={reverse?294:566} y={y+75+i*20} color={reverse?blue:orange} opacity={projected*.45}/>)}
</g>
<g opacity={overview}>
<text x={reverse?828:52} y={y-(reverse?15:25)} textAnchor={reverse?'end':'start'} className="ds-lane-label" fill={reverse?blue:orange}>{reverse?'02 PROTEIN → TEXT':'01 TEXT → PROTEIN'}</text>
<text x="440" y={y+202} textAnchor="middle" className="ds-small" fill="#64788a">{reverse?'Supervise with reference text':'Supervise with reference protein'}</text>
</g>
</g>;
}
function Input({p,reverse,example}:{p:number;reverse:boolean;example:Example}) {
const color=reverse?orange:blue;
const alpha=reverse?windowAt(p,.537,.815):1-phase(p,.477,.506);
const tokens=reverse?example.protein.slice(0,16).split(''):words(example.text).slice(0,9);
let wordX=52;
return <g opacity={alpha}>
<text x="52" y="35" className="ds-micro" fill={color}>{reverse?'REFERENCE PROTEIN · INDEPENDENT INPUT':'REFERENCE FUNCTION · HSP20 FAMILY'}</text>
<rect x="52" y="52" width="776" height="92" rx="10" fill={reverse?'#fff5ee':'#edf5fc'} stroke={color} strokeOpacity=".22"/>
{reverse?<>
<text x="72" y="90" className="ds-sequence" fill={orange}>{example.protein.slice(0,48)}</text>
<text x="72" y="119" className="ds-small" fill="#986651">First 48 of {example.protein.length} reference residues · not the generated sequence</text>
</>:wrap(example.text,79).map((line,i)=><text key={i} x="72" y={78+i*23} className="ds-input-text" fill={blue}>{line}</text>)}
{tokens.map((word,i)=>{
const w=reverse?37:word.length*8+18;
const x=reverse?52+i*48:wordX; wordX+=w+6;
const t=phase(p,(reverse?.546:.025)+i*.003,(reverse?.582:.085)+i*.003);
const fade=1-phase(t,.67,1);
return <g key={i} opacity={fade} transform={`translate(${mix(x,reverse?686:145,t)} ${mix(172,307,t)-Math.sin(t*Math.PI)*16})`}>
<rect width={w} height="32" rx="4" fill={color} fillOpacity=".11" stroke={color} strokeOpacity=".4"/>
<text x={w/2} y="21" textAnchor="middle" className="ds-input-token" fill={color}>{word}</text>
</g>;
})}
<g opacity={phase(p,reverse?.60:.128,reverse?.63:.16)}>
<path d={`M${reverse?714:166} 157 V209`} stroke={color} strokeOpacity=".25" strokeDasharray="3 5"/>
<text x={reverse?696:184} y="192" textAnchor={reverse?'end':'start'} className="ds-small" fill={color}>Encoded into contextual states</text>
</g>
</g>;
}
function Output({p,reverse,example}:{p:number;reverse:boolean;example:Example}) {
const start=reverse?.694:.335, end=reverse?.792:.467;
const alpha=phase(p,start-.015,start)*(1-phase(p,reverse?.823:.487,reverse?.85:.514));
const items=reverse?words(example.generatedText):example.generatedProtein.slice(0,residuePreviewCount).split('');
const color=reverse?blue:orange, origin=reverse?166:714;
let x=68,row=0;
return <g opacity={alpha} data-output={reverse?'text':'protein'}>
<path d={`M${origin} 435 V456 Q${origin} 468 ${origin-14} 468 H66`} fill="none" stroke={color} strokeOpacity=".2"/>
<circle cx={origin} cy="438" r="4" fill={color}/>
<text x="52" y="486" className="ds-micro" fill={color}>RECORDED {reverse?'TEXT':'PROTEIN'} OUTPUT</text>
{items.map((item,i)=>{
const width=reverse?item.length*9+22:26, height=reverse?40:28;
if (reverse && x+width>813) {x=68;row++;}
const tx=reverse?x:90+(i%24)*30,ty=reverse?508+row*53:508+Math.floor(i/24)*36;
x+=width+9;
const t=segment(p,start+i*(end-start)/items.length,start+(i+1.9)*(end-start)/items.length);
const eased=1-(1-t)**3;
const lift=Math.sin(t*Math.PI)*45;
return <g key={i} data-letter={item} data-arrived={t>=1} opacity={phase(t,0,.18)} transform={`translate(${mix(origin-width/2,tx,eased)} ${mix(438,ty,eased)-lift}) scale(${mix(.6,1,eased)})`}>
<rect y="3" width={width} height={height} rx={reverse?6:4} fill={color} fillOpacity=".1"/>
<rect width={width} height={height} rx={reverse?6:4} fill={reverse?'#e7f2fc':'#fff0e5'} stroke={color} strokeOpacity=".6"/>
<text x={width/2} y={reverse?27:20} textAnchor="middle" className={reverse?'ds-word':'ds-residue'} fill={color}>{item}</text>
</g>;
})}
{!reverse&&[0,1,2].map(row=><text key={row} x="52" y={528+row*36} className="ds-output-index" fill="#a48d7f">{String(row*24+1).padStart(3,'0')}</text>)}
<text x="52" y="648" className="ds-small" fill="#687b8b">{reverse?'Word excerpts · archived prediction · illustrative timing':`First ${items.length} of ${example.generatedProtein.length} generated residues · complete sequence below`}</text>
</g>;
}
export function DualScene({progress:p,example}:{progress:number;example:Example}) {
const id=useId().replace(/:/g,'');
const overview=phase(p,.839,.894);
const share=sharingState(p);
return <svg className="dual-scene-svg" viewBox="0 0 880 680" role="img" aria-label={`Ouroboros architecture, ${p<.515?'text to protein':p<.835?'protein to text':'joint supervised learning'}`} data-frame={p.toFixed(6)}>
<defs>
<linearGradient id={`${id}-flow`}><stop stopColor={blue}/><stop offset="1" stopColor={orange}/></linearGradient>
<linearGradient id={`${id}-flow-reverse`}><stop stopColor={blue}/><stop offset="1" stopColor={orange}/></linearGradient>
{[['blue',blue],['orange',orange]].map(([name,color])=><marker key={name} id={`${id}-${name}-arrow`} viewBox="0 0 8 8" refX="7" refY="4" markerWidth="5" markerHeight="5" orient="auto-start-reverse"><path d="M0 0 L8 4 L0 8" fill="none" stroke={color}/></marker>)}
</defs>
<g opacity={overview}>
<text x="52" y="36" className="ds-micro" fill="#596e82">ONE PAIRED EXAMPLE · TWO TEACHER-FORCED TRAINING TASKS</text>
<text x="828" y="36" textAnchor="end" className="ds-small" fill="#7b8995">large-v5</text>
</g>
<Input p={p} reverse={false} example={example}/>
<Input p={p} reverse example={example}/>
<Lane p={p} reverse={false} overview={overview} id={id}/>
<Lane p={p} reverse overview={overview} id={id}/>
<Output p={p} reverse={false} example={example}/>
<Output p={p} reverse example={example}/>
<g opacity={phase(p,.895,.908)} data-sharing="within-modality">
{[false,true].map(protein=>{
const color=protein?orange:blue,edge=protein?849:31,x=protein?828:52;
const progress=protein?share.protein:share.text,focus=protein?share.proteinFocus:share.textFocus;
return <g key={String(protein)} data-sharing-modality={protein?'protein':'text'} data-emphasis={focus.toFixed(4)} opacity={.2+.8*Math.max(progress,share.both)}>
<path d={`M${x} 207 H${edge} V501 H${x}`} fill="none" stroke={color} strokeOpacity=".18" strokeWidth="2"/>
<path d={`M${x} 207 H${edge} V501 H${x}`} fill="none" stroke={color} strokeWidth={1.5+focus*1.5} pathLength="1" strokeDasharray="1" strokeDashoffset={1-progress}/>
{[207,501].map(y=><g key={y}><circle cx={x} cy={y} r={3+focus*3} fill="#f8fafc" stroke={color} strokeWidth="2"/><circle cx={x} cy={y} r={6+focus*6} fill="none" stroke={color} strokeOpacity={focus*.3}/></g>)}
<rect x={protein?600:43} y="336" width="234" height="44" rx="8" fill={protein?'#fff0e7':'#e8f2fc'} stroke={color} strokeOpacity={focus}/>
<text x={protein?717:160} y="354" textAnchor="middle" className="ds-small" fill={color}>Same {protein?'protein':'text'} self-attention weights</text>
<text x={protein?717:160} y="371" textAnchor="middle" className="ds-shared-tag" fill={color}>ENCODER ↔ DECODER · BLOCKS 1–24</text>
</g>;
})}
<text x="440" y="651" textAnchor="middle" className="ds-objective" fill={ink}>L = ½ L(text → protein) + ½ L(protein → text)</text>
</g>
</svg>;
}
|