Building
Training a model in the browser
Scellis runs the full training loop — autograd, optimizers, schedulers, mixed precision, PEFT — on your own GPU, in a browser tab, offline after first load. Let's be precise about the claim: a tab does not outrun a rented datacenter GPU, and Scellis never says it does. What you get is zero install, computation that stays on your device, free local compute on every tier, and runs that are reproducible by construction. This page walks the path end to end: configure, budget, run, checkpoint, resume.
Configure in Model View, not in scattered settings#
Everything that defines a training run lives on the model itself, in Model View — the full-screen editor you enter by double-clicking a model node. The same model appears there three ways at once — graph, form, and code in SML, the Scellis Model Language — three projections of one underlying record, kept in sync within a frame. The training pipeline is part of that record:
model: user/ada/tiny_transformer@3
optimizer: { block_id: training/optimizer/adamw, lr: 3.0e-4, weight_decay: 0.01 }
scheduler: { block_id: training/scheduler/warmup_cosine, warmup_steps: 500 }
loss: { block_id: training/loss/cross_entropy, label_smoothing: 0.1 }
epochs: 10
batch_size: 32
grad_clip: { max_norm: 1.0 }
precision: { policy: mixed_f16, master_weights: f32, loss_scaling: dynamic }
determinism_tier: prod # debug | prod | fast — never silently defaulted
seed: 42 # counter-based RNG, checkpointed with the runNothing here hides in global app settings, and that has a consequence worth underlining: share the model and the whole pipeline travels with it — whoever opens it gets your training setup, not just your architecture. Note the last two lines. The determinism tier is an explicit, visible choice, and the seed feeds a counter-based RNG whose state is checkpointed with the run.
The built-in component stack#
The component stack ships as built-in catalog content — a starting set, never a ceiling. Every entry is swappable, and every category is yours to extend through the same authoring path the built-ins took; your authored optimizer appears in these pickers exactly like a built-in one.
| Category | Built-in components |
|---|---|
| Optimizers (9) | Adam · AdamW · SGD · LAMB · Adafactor · Lion · Sophia · Int8Adam · Prodigy |
| Losses (15) | MSE · L1 · BCE · CrossEntropy · KLDivergence · PoissonNLL · GaussianNLL · Focal · Huber · Dice · IoU · Contrastive · CosineEmbedding · InfoNCE · TripletMargin |
| LR schedulers (14) | Step · MultiStep · Exponential · CosineAnnealing (+WarmRestarts) · OneCycle · ReduceOnPlateau · WarmupCosine · Linear · Polynomial · Cyclic · Sequential · Chained · Lambda — plus an LR range-test finder |
| PEFT adapters (6) | LoRA · QLoRA · PrefixTuning · BitFit · IA3 · PromptTuning |
| Gradient ops (8) | Clipping, AMP scaling, accumulation / micro-batching, activation checkpointing, and more |
| Regularization (5) | Mixup · CutMix · Label Smoothing · Weight Decay · Spectral Norm |
| Weight init + averaging | 11 initializer schemes · EMA / SWA |
| Metrics (10) | Accuracy · F1 · Precision · Recall · ConfusionMatrix · Perplexity · AUROC · AUPRC · ECE · Brier |
One honest footnote on PEFT: the LoRA family lowers into the compiled op-graph; prefix and prompt tuning run on the eager path today — a declared state, not a hidden one.
A memory plan before the first step#
Before anything executes, the planner computes the full memory story: parameters, activations, gradients, optimizer state, and working memory — plus the f32 master copy of the weights under mixed precision. Those classes are co-resident, so it is their SUM, the peak, that must fit; nested loops multiply visibly. The peak is checked against your device's real limits and comes back as one of three verdicts: green — it fits; yellow — it fits, with warnings; red — refused, and the refusal names the fix. Reduce the batch size, enable activation checkpointing, change precision: each remediation arrives with its estimated savings, so you decide with numbers rather than by trial and error.
The same honesty covers capacity. Pooling more devices multiplies throughput, not memory: a model whose parameters and optimizer state exceed one device's VRAM cannot train there at any batch size — the planner says so explicitly and points to the PEFT and sharded paths instead (QLoRA alone cuts the fine-tuning floor by roughly 4–8×). How work spreads across devices — and what that honestly buys — is the distributed compute story.
What actually executes#
Your model compiles down an inspectable IR ladder to WGSL compute kernels; nothing bypasses it. The training loop itself is host-driven, which is what makes data-dependent control flow ordinary: NaN-skip on overflowed steps, plateau-triggered LR drops, early stopping. Mixed precision is a real numeric policy — dynamic loss scaling, f32 master weights, reductions accumulated in f32 — with the numeric ceiling stated rather than papered over: WGSL has no bf16 (f16 with loss scaling is the honest browser path) and no f64 on the GPU (true f64 lives on the WASM oracle). Around the loop, the compiler does the unglamorous work: automatic tiling around WebGPU's 128 MB binding cap, a first-class flash-attention lowering, and activation rematerialization where you asked for it.
Fusion honesty#
Kernel fusion fires on the fast-tier inference forward. The training backward runs on the eager tape: the gradient is correct, not fused. The expected eager-versus-fused overhead band is measured on the conformance corpus and republished with each engine release — never an invented constant.
Checkpoints resume exactly#
Every checkpoint is a content-addressed bundle:
{
"weights": "sha256:9c1e07…",
"optimizer_state": "sha256:4b77aa…",
"rng_state": { "algorithm": "philox4x32", "counter": 18432000 },
"epoch": 7,
"step": 21875,
"metrics": { "loss": 0.4181, "val_accuracy": 0.887 }
}Because the RNG is counter-based, the same seed and counter produce the same draws on any backend, with named sub-streams for dropout, shuffling, and augmentation — so resuming a checkpoint continues training exactly, not approximately. The guarantees are tiered and told to you plainly: on debug, two runs on the same device are bitwise identical; on prod, agreement across devices holds within the declared tolerance; cross-device bit-exactness is the CPU oracle path. Runs, dataset versions (referenced by hash — see data and connectors), checkpoints, and metrics are all provenance records, so comparing experiments is a query over records that already exist — the substance of reproducibility, and the reason reproducing a result is a URL, not an archaeology project.
Fine-tune on the same surface#
A trained model opens back into the same Model View — and so does an imported one, through ONNX, safetensors, and PyTorch state-dict codecs that are content, not engine features. Freeze what you trust, attach PEFT adapters layer by layer, swap a head, send it back to training. There is no separate fine-tuning product: one surface, one planner, one run path.
The long run, honestly#
Browsers throttle background tabs, and Scellis says so instead of pretending: backgrounding a training tab announces the slowdown loudly, and the product offers a consent-gated screen wake lock and a detached run window to keep a long run foregrounded. A crashed tab reconciles to an explicit “interrupted” state with one-action resume from the last checkpoint. And there is no unattended mode to oversell — a closed tab computes nothing.
Note
No usable WebGPU? Whole workflows still run on a slower CPU path behind a loud, persistent banner — “running on CPU — N× slower, large-model training limited”. A device with neither still opens every model for composing and inspecting. The full map of what is bounded and why lives on honest limits.