Skip to content

Releases: playidea-lab/borch

borch-ts 0.6.1 · pyborch 1.14.1 — eleven silent wrong answers fixed, each with a check that failed before

Choose a tag to compare

@playidea-lab playidea-lab released this 27 Sep 10:19

borch-ts 0.6.1 on npm · pyborch 1.14.1 on PyPI. A correctness release: every fix below answered wrongly with nothing said, and each now has a check that failed on the code before it. Additive; no breaking change. Upgrade from 0.6.0.

npm install borch-ts@0.6.1        # the CDN line @0.6 picks it up
pip install pyborch==1.14.1

Wrong answers fixed

where what happened in 0.6.0 measured before the fix
compiled(model) then model.call(x) eagerly the first call's dry run cached weight packs it never wrote; the eager forward read them max |Δ| 8.1e-3 against the eval forward (metal-3)
a second bias on the same conv weight, under noGrad the pack was keyed on the weight alone and baked the first bias in max |Δ| 1.5
AdamW/Adam/SGD with a frozen parameter the fused arena stepped every parameter, gradient or not — decay and momentum moved frozen weights 1.5e-3 in three steps
the arena after addParamGroup, or after a weight written between steps the per-parameter path restarted the moments; the arena overwrote the outside write max |Δ| 2.7e-2 and 6.7e-1
checkpoint / streamTrainStep with dropout the recompute drew new masks: loss from one mask, gradient from another max |Δgrad| 8.1 and 10.0
the page-wide dropout seed made inside whatever scope ran the first dropout and pooled with it — manualSeed stopped reproducing found while fixing the row above
idle kernel tuning its scope, held across awaits, closed a scope the page opened meanwhile the page's tensor refused as "belongs to a closed scope"
a step re-recorded by the tuner the objects the first call returned were released reading them threw
fusedAttention with a head of 128 on a 32 KiB device (Apple included) the key block halved to 16 and the merge ran past it rows 8–15 of every round wrong
f32ToF16Bits NaN packed as inf, 70000 as NaN, ties rounded up 179,866 of 400,000 patterns differed from Float16Array
streamTrainSequence({ offload: true }), second step rebuilt its blocks from evicted window slots and destroyed the window buffer 3 WebGPU faults

Refused now instead of answered wrongly

A checkpoint input written in place before the backward (torch's version check) · a trainable tensor the loss or a block reads that lossParams / params does not name · a reflect/replicate/circular Conv2d wrapped by LoRAConv2d · fusedAttention on a device without its workgroup storage · the int8 convolution where the device's workgroup storage, subgroup size, or this browser's WGSL compiler (Chrome before 151) cannot take it — the f32 kernels answer there.

Also: the eager pack cache keeps one pack per weight rather than one per input shape; kernel-tuning decisions are keyed by adapter, browser version and library version, so a browser update or a new release tunes again; the tuner times only its own candidates; compiled({ check: true, fuse: false }) no longer refuses a correctly tuned training step; a block that throws gives its window slots back; a failed kernel tuning falls back to the rule's kernels with one sentence; ONNX export no longer triggers the eval-under-grad advice; applyLora's documentation says what it does not freeze.

For agents

The package now ships AGENTS.md with a whole page that trains a CNN from jsDelivr (also at https://playidea-lab.github.io/borch/site/recipes/train-cnn.html, opened on a real GPU every night), and llms.txt says when to pick borch over ONNX Runtime Web.

Checked

golden:ts 4057/4057 · binding 4753/4753 · core 4701/4701 · capture 29/29 on apple/metal-3 and nvidia/lovelace (26/26 on nvidia/blackwell before the last four) · device 67/67 · lessons 72/72 · pytest 1510. The RTX 5080's int8 static forward is 0.99 ms at batch 16 and 92.50 % on the held-out images, as in 0.6.0.

borch-ts 0.6.0 · pyborch 1.14.0 — kernels chosen by measurement, int8 static, the first call off the critical path

Choose a tag to compare

@playidea-lab playidea-lab released this 22 Sep 00:22

borch-ts 0.6.0 on npm · pyborch 1.14.0 on PyPI · bimm-ts 0.13.0. One commit, one wheel, one bundle; both additive, no breaking change found.

npm install borch-ts@0.6.0        # or: import { init } from "https://cdn.jsdelivr.net/npm/borch-ts@0.6/+esm"
pip install pyborch==1.14.0       # borch (numpy core) · borch_webgpu (Pyodide/JupyterLite on WebGPU) · borchvision · borch_cpu

What is new since 0.5.0 (2026-09-15) — 207 commits

The training step and the inference forward are chosen by measurement, per card. The first recording of a compiled(step) or compiled(model) times the kernel candidates for every convolution on the adapter it is running on and keeps the decisions (localStorage, per origin); a second recording of the same step times nothing. The candidates include the per-card answers this cycle found — the scalar staged convolution, the wide direct block, quarter-splits, the int8 pieces — so the same code runs the 5080's kernels on the 5080 and Metal's on Metal.

The first call is off the critical path. The first call records with the rule's picks and answers; the candidates compile side by side in a dry run and are timed afterwards, in idle time, and a pure step is silently re-recorded before its next call. On a laptop's Direct3D 12 the inference forward's first call went 1.4 s → 162 ms; Chrome keeps the compiled pipelines for the next visit (51 ms the second session). Compiled.firstCall reports what it cost.

int8 convolutions on subgroup matrices, with static scales — compiled(model, { int8: true }). On the RTX 5080 the ResNet-18 forward at batch 16 is 0.96 ms against 1.63 f32, at the same top-1 on 1,000 held-out CIFAR-10 images (92.50 % vs 92.40 %). Where the card has no int8 subgroup configuration the rule is not taken.

The eager path repacks a weight only when it was written (write epochs on the device, a pack cache beside them), and the readback is kicked where the browser needs it (Device.readbackKicks).

The library says five things first (Device.advise, once each, Device.advice = false silences): an inference loop with no scope; an eval model run under gradient mode; a matmul off the eights the subgroup kernels want; a laptop on battery; a first call over 200 ms and where the browser keeps its kernels.

Also: isGradEnabled(); fusedAttention (built and gated, measured slower than the composed chain, kept but not routed); ORT Web's own f16 and int8 files in the comparison; the ViT-Tiny/16 comparison through bimm-ts; the D3D12 adapter measured end to end.

Measured, 2026-09-22, this build

Every number below is printed by a runner that refuses a software adapter and names the machine; the losing rows are on the page. https://playidea-lab.github.io/borch/site/compare.html

ResNet-18 (CIFAR) training step, ms, batch 16 borch.ts TF.js 4.22 jax-js 0.1.25 Burn 0.21
Apple M4 Max (metal-3) 19.3 86.5 68.1 264.7
RTX 4090 (Vulkan, Chrome 153) 10.0–10.5 67.5 67.3 115.7
RTX 5080 (Vulkan, Chrome 151) 10.7–11.3 75.0 72.2 —
inference forward, ms, batch 1 / 16 borch.ts captured ONNX Runtime Web 1.29 f32
ResNet-18, metal-3 1.09 / 4.12 4.35 / 5.30 (f16 file: 3.29 / 4.26 — a draw at 16)
ResNet-18, RTX 4090 0.58 / 1.65 (int8 static 0.56 / 0.83) 3.70 / 3.61
ViT-Tiny/16, metal-3 2.22 / 10.64 5.46 / 19.77
ViT-Tiny/16, RTX 4090 1.67 / 4.88 8.12 / 11.41

Golden: 4,057 / 4,057 (borch.ts), 4,753 / 4,753 (binding), capture 23 / 23, device 59 / 59, site 54 / 54.

What it is not

Not PyTorch. No CUDA, no distributed, no mixed precision, no torch.compile, no LLM decode, no f16 compute kernels (why ORT's f16 file draws on Metal). Chrome with WebGPU is the requirement today; Safari and Firefox are unmeasured. A Chrome before 151 refuses the int8 convolution's WGSL on a uniformity rule and the library prints no number rather than a wrong one.

For a coding agent

llms.txt and AGENTS.md at the site root say which of the three entry points to use, when to pick this over ORT Web (a page that trains) and the ten rules that save a rewrite; site/assets/api-index.json says whether a name exists. In Claude Code: /plugin marketplace add playidea-lab/borch then /plugin install borch@borch.

The ledgers with the predictions written before the runs, and which were wrong: docs/COMPILER.md, docs/FIRST.md, docs/INT8.md, docs/GEMM.md, docs/INFER.md, docs/BOOK.md ("How fast it is").

browsertorch v1.3.0

Choose a tag to compare

@playidea-lab playidea-lab released this 13 Aug 00:37

브라우저(Pyodide)에서 도는 PyTorch. 이전 이름 nanotorch.

이것이 아닌 것

PyTorch 가 아니다 — 표면의 11%, 코드로는 0.1%.
CUDA · 분산 · 혼합정밀도 · torch.compile · 사전학습 가중치는 영원히 없다.

할 수 있는 말

입문 튜토리얼 코드가 임포트만 바꿔 같은 값을 낸다.

  • T1 값·기울기 100% · T2 오류 12/12 · T3 표현 15/15
  • dtype 승격 112/112 · 저장소 공유 13/13 · 넓은 표면 58/58
  • 흔한 API 이름 144/144 · 통합 시나리오 6/6 · 테스트 161개

이름

minitorch·nanotorch·edutorch 와 무관하다. 셋 다 이미 있는 프로젝트이고,
'작은 교육용 torch' 라는 같은 자리를 겨눈다. 이쪽의 다른 점은 브라우저에서 돈다는 것이다.

nanotorch v1.2.2

Choose a tag to compare

@playidea-lab playidea-lab released this 12 Aug 22:35

브라우저(Pyodide)에서 PyTorch 문법을 연습하기 위한 numpy 기반 축소판.

진짜 torch 와의 대조:

  • T1 값·기울기 100% · T2 오류 12/12 · T3 표현 15/15
  • dtype 승격 112/112 · 저장소 공유 13/13 · 넓은 표면 58/58
  • 흔한 API 이름 144/144 · 통합 시나리오 6/6
  • 테스트 161개

거절: CUDA · 사전학습 가중치 · 혼합정밀도 · 분산 · torch.compile