spark.client.useSpark
function useSpark<T extends Record<string, unknown>>(torchFn: () => void | Promise<void>): T & { ctrl: SparkController; ready: boolean; error: Error | null }React hook to connect to a Spark worker for interactive ML training.
This hook launches a Web Worker running your torch function and provides reactive bindings to the values and functions you expose. The hook handles:
- Creating the worker on first use
- Reactive updates when exposed values change
- RPC calls for exposed functions
- Pause/resume/stop control
- Hot code reload
Worker Lifecycle: The worker is created once and persists across:
- React component re-renders
- UI hot module reloads (HMR)
- Hook re-runs
Only explicit
resetWorker()or page reload destroys it.
Value Access: Exposed values are accessed via .value property:
const s = spark.use(torch);
s.loss.value // Get current value
s.loss.value = 0.5 // Set value (sends update to worker)
s.metrics.value.train // Nested objects workFunction Calls: Exposed async functions are called directly:
const s = spark.use(torch);
const result = await s.train(); // Call async function
const prediction = await s.predict(input);Control: Use the ctrl object to pause/resume/stop training:
const s = spark.use(torch);
s.ctrl.pause(); // Pause at next checkpoint
s.ctrl.resume(); // Resume from pause
s.ctrl.stop(); // Stop execution
s.ctrl.status // 'idle' | 'running' | 'paused' | 'stopped'Status: Check readiness and errors:
const s = spark.use(torch);
{s.ready && <p>Worker ready!</p>}
{s.error && <p>Error: {s.error.message}</p>}- The worker persists across component unmounts. Call
resetWorker()if you need to completely restart training with a fresh worker instance. - The torch function runs asynchronously in a worker. Avoid accessing React context, hooks, or component state from within it. Use closure variables and spark.expose() to communicate state back to React.
Parameters
torchFn() => void | Promise<void>- Function to run in worker. Should call spark.expose() at end.
Returns
T & { ctrl: SparkController; ready: boolean; error: Error | null }– Reactive proxy with: - [exposedKey]: Values (with .value property) and functions - ctrl: SparkController for pause/resume/stop - ready: Boolean indicating if worker initialized - error: Error object if initialization failed, null otherwiseExamples
// Define torch function (can edit while training)
function torch() {
const model = spark.persist('model', () =>
nn.Sequential([
nn.Linear(784, 128),
nn.ReLU(),
nn.Linear(128, 10)
])
);
let loss = 0;
let epoch = 0;
let accuracy = 0;
const history = { loss: [], accuracy: [] };
async function train(epochs = 100) {
for (let e = 0; e < epochs; e++) {
epoch = e;
let batch_loss = 0;
let correct = 0;
for (const { x, y } of data.train.batch(64)) {
await spark.checkpoint(); // Allow UI to pause/stop
// Training step
const logits = model(x);
const batch_criterion = criterion(logits, y);
batch_criterion.backward();
optimizer.step();
optimizer.zero_grad();
batch_loss = batch_criterion.item();
loss = batch_loss; // Updates UI in real-time
const pred = logits.argmax(1);
correct += pred.eq(y).sum().item();
}
accuracy = correct / data.size.train;
history.loss.push(loss);
history.accuracy.push(accuracy);
}
}
async function predict(images) {
const logits = model(images);
return logits.argmax(1).toArray();
}
// Expose to React
spark.expose({ train, predict, loss, epoch, accuracy, history });
}
// React component
export default function App() {
const s = spark.use(torch);
if (s.error) {
return <div>Error: {s.error.message}</div>;
}
if (!s.ready) {
return <div>Loading...</div>;
}
return (
<div>
<h1>MNIST Trainer</h1>
<p>Epoch: {s.epoch.value}</p>
<p>Loss: {s.loss.value.toFixed(4)}</p>
<p>Accuracy: {(s.accuracy.value * 100).toFixed(1)}%</p>
<button
onClick={() => s.train()}
disabled={s.ctrl.status === 'running'}
>
Train
</button>
{s.ctrl.status === 'running' && (
<>
<button onClick={() => s.ctrl.pause()}>Pause</button>
<button onClick={() => s.ctrl.stop()}>Stop</button>
</>
)}
{s.ctrl.status === 'paused' && (
<button onClick={() => s.ctrl.resume()}>Resume</button>
)}
<LineChart
data={{
loss: s.history.value.loss,
accuracy: s.history.value.accuracy,
}}
/>
</div>
);
}See Also
- resetWorker - Destroy and recreate the worker
- requestHotReload - Update code without restarting
- setWorkerScriptUrl - Configure worker script location