Spaces:
Sleeping
Sleeping
| import { useEffect, useMemo, useState } from 'react' | |
| import './App.css' | |
| import { ClusterMap } from './components/ClusterMap' | |
| import { ControlsPanel } from './components/ControlsPanel' | |
| import { analyzeCluster } from './lib/trainingClusterModel' | |
| import { getScenarioConfig, getViewOptions } from './lib/viewOptions' | |
| import { buildWorkbenchViewModel } from './lib/workbenchPresenter' | |
| import { type WorkbenchConfig } from './lib/workbench' | |
| function App() { | |
| const viewOptions = getViewOptions() | |
| const [config, setConfig] = useState<WorkbenchConfig>(() => | |
| getScenarioConfig(viewOptions.scenario), | |
| ) | |
| const [expandedView, setExpandedView] = useState<'cluster' | null>(null) | |
| const analysis = useMemo( | |
| () => analyzeCluster(config.model, config.training, config.cluster, config.parallelism), | |
| [config], | |
| ) | |
| const viewModel = useMemo( | |
| () => buildWorkbenchViewModel(config, analysis), | |
| [analysis, config], | |
| ) | |
| useEffect(() => { | |
| if (!expandedView) { | |
| return undefined | |
| } | |
| const previousOverflow = document.body.style.overflow | |
| document.body.style.overflow = 'hidden' | |
| const handleKeyDown = (event: KeyboardEvent) => { | |
| if (event.key === 'Escape') { | |
| setExpandedView(null) | |
| } | |
| } | |
| window.addEventListener('keydown', handleKeyDown) | |
| return () => { | |
| document.body.style.overflow = previousOverflow | |
| window.removeEventListener('keydown', handleKeyDown) | |
| } | |
| }, [expandedView]) | |
| const handleConfigChange = (nextConfig: WorkbenchConfig) => { | |
| setConfig(nextConfig) | |
| } | |
| const handleReset = () => { | |
| setConfig(getScenarioConfig(viewOptions.scenario)) | |
| } | |
| const clusterView = ( | |
| <section className="map-panel"> | |
| <div className="topology-header"> | |
| <div> | |
| <p className="mini-label">Live cluster topology</p> | |
| <h2>GPU fabric map</h2> | |
| </div> | |
| <div className="topology-header-actions"> | |
| <button | |
| type="button" | |
| className="scene-button" | |
| onClick={() => setExpandedView('cluster')} | |
| > | |
| open full screen | |
| </button> | |
| </div> | |
| </div> | |
| <ClusterMap | |
| viewModel={viewModel} | |
| debugEnabled={viewOptions.debug} | |
| snapshotMode={viewOptions.snapshot} | |
| linkedFocus={null} | |
| /> | |
| </section> | |
| ) | |
| return ( | |
| <div className="workbench-shell"> | |
| <header className="app-topbar"> | |
| <div className="title-block"> | |
| <p className="mini-label">Illustrated training cluster</p> | |
| <h1>[WIP] Parallelism workbench</h1> | |
| <p className="title-copy">{viewModel.subheadline}</p> | |
| </div> | |
| <div className="status-banner status-banner-wip" data-testid="wip-banner"> | |
| <strong>Work in progress</strong> | |
| <span>This demo may contain bugs, rough edges, and logical errors in the training model.</span> | |
| </div> | |
| {!analysis.feasible ? ( | |
| <div className="status-banner status-banner-danger" data-testid="infeasible-banner"> | |
| <strong>Infeasible configuration</strong> | |
| <span>{analysis.infeasibilityReason}</span> | |
| </div> | |
| ) : null} | |
| <section className="summary-strip" aria-label="simulation summary"> | |
| <div className="summary-card summary-card-wide"> | |
| <span>Scenario</span> | |
| <strong>{viewModel.headline}</strong> | |
| <p> | |
| {config.cluster.numNodes} {config.cluster.nodeLabel ?? 'nodes'} 路 {config.cluster.gpuType.name} | |
| {' 路 '} | |
| {config.model.numLayers} layers 路 hidden {config.model.hiddenDim.toLocaleString()} | |
| </p> | |
| </div> | |
| <div className="summary-card"> | |
| <span>Throughput</span> | |
| <strong>{viewModel.summary.throughputLabel}</strong> | |
| <p>{viewModel.summary.throughputNote}</p> | |
| </div> | |
| <div className="summary-card"> | |
| <span>Active GPUs</span> | |
| <strong>{viewModel.summary.gpuLabel}</strong> | |
| <p>{viewModel.summary.gpuNote}</p> | |
| </div> | |
| <div className="summary-card"> | |
| <span>Interconnect</span> | |
| <strong>{viewModel.summary.interconnectLabel}</strong> | |
| <p>{viewModel.summary.interconnectNote}</p> | |
| </div> | |
| <div className="summary-card"> | |
| <span>Bottleneck</span> | |
| <strong>{viewModel.summary.bottleneckLabel}</strong> | |
| <p>{viewModel.summary.bottleneckNote}</p> | |
| </div> | |
| </section> | |
| </header> | |
| <ControlsPanel | |
| config={config} | |
| onChange={handleConfigChange} | |
| onReset={handleReset} | |
| viewModel={viewModel} | |
| /> | |
| <main className="analysis-stack"> | |
| {expandedView !== 'cluster' ? clusterView : null} | |
| <section className="side-card"> | |
| <div className="side-header"> | |
| <p className="mini-label">Run breakdown</p> | |
| <h3>{config.cluster.gpuType.name}</h3> | |
| </div> | |
| <div className="facts-grid"> | |
| {viewModel.facts.map((fact) => ( | |
| <div key={fact.label} className="fact-row"> | |
| <span>{fact.label}</span> | |
| <strong>{fact.value}</strong> | |
| </div> | |
| ))} | |
| </div> | |
| <div className="warning-list" aria-live="polite"> | |
| {viewModel.warnings.map((warning) => ( | |
| <div key={warning} className="warning-pill"> | |
| {warning} | |
| </div> | |
| ))} | |
| </div> | |
| </section> | |
| </main> | |
| {expandedView ? ( | |
| <div | |
| className="fullscreen-overlay" | |
| role="dialog" | |
| aria-modal="true" | |
| onClick={(event) => { | |
| if (event.target === event.currentTarget) { | |
| setExpandedView(null) | |
| } | |
| }} | |
| > | |
| <div className="fullscreen-shell"> | |
| <div className="fullscreen-toolbar"> | |
| <div> | |
| <p className="mini-label">Expanded view</p> | |
| <h2>GPU fabric map</h2> | |
| </div> | |
| <button | |
| type="button" | |
| className="scene-button" | |
| onClick={() => setExpandedView(null)} | |
| > | |
| close full screen | |
| </button> | |
| </div> | |
| <div className="fullscreen-content"> | |
| {clusterView} | |
| </div> | |
| </div> | |
| </div> | |
| ) : null} | |
| </div> | |
| ) | |
| } | |
| export default App | |