npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

polygrad

v0.4.2

Published

Tensor computation library with native and WASM backends

Readme

Polygrad JavaScript

JavaScript bindings for Polygrad, a C11 tensor compiler with Node, WASM, and browser runtimes.

The package uses the same C compiler core as Polygrad Python. In Node it tries the native addon first and falls back to packaged WASM. In browsers it runs the C runtime through WASM, with optional WebGPU execution.

Use this package when JavaScript owns the application flow but tensor execution should still come from the shared Polygrad compiler/runtime: Node services, browser ML tools, WebGPU demos, or packages that accept a caller-provided Polygrad runtime.

Install

npm install polygrad

Node tries to build the native addon during install. If that fails, runtime creation can still use the WASM core. To skip native compilation:

POLYGRAD_SKIP_NATIVE=1 npm install polygrad

From this repository:

cd js
npm install
node test/test_wasm.js

Quick Start

const { Tensor } = require('polygrad')

const a = Tensor.rand(3, 4)
const b = Tensor.rand(4, 5)
const c = a.dot(b).softmax(-1)

console.log(c.toArray())

Autograd:

const { Tensor } = require('polygrad')

const x = new Tensor([1.0, 2.0, 3.0], { requiresGrad: true })
const loss = x.mul(x).sum()

loss.backward()
console.log(x.grad.toArray())  // [2, 4, 6]

Explicit runtime:

const polygrad = require('polygrad')

const pg = polygrad.create({ core: 'wasm' })
const y = new pg.Tensor([1, 2, 3]).mul(2).add(1)
console.log(y.toArray())
pg.dispose()

Linear algebra:

const { Tensor } = require('polygrad')

const A = new Tensor([[4.0, 2.0], [2.0, 5.0]])
const b = new Tensor([1.0, 3.0])
const x = A.solve(b)

console.log(x.toArray())

Structured linalg methods are portable tensor-composed fallbacks. Current lstsq is solution-only for full-rank tall or square systems.

Choose A Runtime

The default JavaScript API is sync-first. polygrad.create(...) returns a PolyRuntime immediately or throws; tensor construction and graph construction are synchronous too.

Use explicit async startup when the host cannot or should not instantiate the WASM core synchronously, for example older browser limits or a fetch/streaming loader path. The async entry is not a drop-in default Tensor import because startup itself must be awaited:

const { createAsync } = require('polygrad/async')
const pg = await createAsync({ core: 'wasm' })

Runtime matrix:

| Work | Sync API | Async API | |---|---|---| | Create normal Node/browser runtime | polygrad.create(opts) | not needed | | Create through async WASM loader | throws with guidance | createAsync(opts) from polygrad/async | | Construct tensors and graph ops | new Tensor(...), x.mul(2) | not needed | | CPU/native/WASM realize/readback | x.realize(), x.toArray() | realizeAsync() / toArrayAsync() also work | | WebGPU realize/readback | throws PolyAsyncRequired | await x.realizeAsync(), await x.toArrayAsync() |

const polygrad = require('polygrad')

const pg = polygrad.create({ core: 'wasm' })
console.log(pg.core, pg.device)
pg.dispose()

Runtime selection:

| Environment | core: "auto" | core: "native" | core: "wasm" | |---|---|---|---| | Node with native addon | native | native | wasm | | Node without native addon | wasm | error | wasm | | Browser | wasm | error | wasm |

Options:

  • core: auto, native, or wasm.
  • device: auto, cpu, cuda, hip, x86, interp, wasm, or webgpu, depending on the selected core and local runtime support.

Environment variables:

POLY_CORE=wasm node app.js
POLY_DEVICE=cuda node app.js

For the native core, POLY_DEVICE is handled by the C runtime and may select cpu, cuda, hip, x86, or interp. For the WASM core, device choices are wasm, interp, and browser webgpu when available.

Call pg.dispose() when an application or long-running script is done with an explicit runtime.

Browser

With an npm-installed package and a browser bundler:

import { Tensor } from 'polygrad'

const y = new Tensor([1, 2, 3]).mul(2).add(1)
console.log(y.toArray())

For WebGPU, create an explicit runtime and use async execution/readback:

import { create } from 'polygrad'

const pg = create({ core: 'wasm', device: 'webgpu' })
const y = new pg.Tensor([1, 2, 3]).mul(2)
console.log(await y.toArrayAsync())
pg.dispose()

Bundlers should resolve the bare polygrad import to the browser bundle through the package browser export condition. Node require('polygrad') still resolves to the Node entry.

If a bundler or CDN resolver picks the Node entry by mistake, force the browser entry explicitly:

import { create } from 'polygrad/browser'

For a local checkout or manual browser bundle, build browser artifacts from the repository root:

make wasm-pkg
cd js
npm run build:browser

Outputs:

  • dist/polygrad.sync.js for a sync browser global.
  • dist/polygrad.sync.mjs for sync browser ESM.
  • dist/polygrad.async.js for an explicit async-startup browser global.
  • dist/polygrad.async.mjs for explicit async-startup browser ESM.

Browser global:

<script src="./dist/polygrad.sync.js"></script>
<script>
  const y = new polygrad.Tensor([1, 2, 3]).mul(2)
  console.log(y.toArray())
  polygrad.disposeDefault()
</script>

Local browser ESM with WebGPU:

<script type="module">
  import { create } from './dist/polygrad.sync.mjs'

  const pg = create({ core: 'wasm', device: 'webgpu' })
  const y = new pg.Tensor([1, 2, 3]).mul(2)
  console.log(await y.toArrayAsync())
  pg.dispose()
</script>

Explicit async startup bundle:

<script type="module">
  import { createAsync } from './dist/polygrad.async.mjs'

  const pg = await createAsync({ core: 'wasm' })
  const y = new pg.Tensor([1, 2, 3]).mul(2)
  console.log(y.toArray())
  pg.dispose()
</script>

Data Flow

Polygrad tensors are lazy. Build expressions freely, then call realize() or read data back.

const { Tensor } = require('polygrad')

const x = new Tensor(new Float32Array([1, 2, 3, 4]), { shape: [4] })
const y = x.mul(3).sub(1).realize()

console.log(y.toTypedArray())  // Float32Array

For repeated loops, reuse tensor buffers instead of constructing new source tensors:

const { Tensor } = require('polygrad')

const x = new Tensor(new Float32Array([1, 2, 3, 4]), { shape: [4] })
x.realize()
x.copyFrom(new Float32Array([5, 6, 7, 8]))

Use toTypedArrays() when reading several outputs together.

const { Tensor } = require('polygrad')

const x = new Tensor([1, 2, 3])
const a = x.add(1)
const b = x.mul(2)
const [aData, bData] = Tensor.toTypedArrays(a, b)

JIT And Compile

jit(fn) follows tinygrad raw Tensor JIT behavior: first call runs normally, second call captures realized schedules, later calls replay.

const { Tensor, jit, compile } = require('polygrad')

const f = jit((x) => x.add(1).realize())

f(new Tensor([1, 2, 3]))  // normal run
f(new Tensor([4, 5, 6]))  // capture
console.log(f(new Tensor([7, 8, 9])).toArray())  // replay

compile(fn, sampleInputs) warms and captures immediately:

const compiled = compile(
  (x) => x.add(1).realize(),
  [new Tensor([1, 2, 3])]
)

const out = compiled.run([new Tensor([7, 8, 9])])
console.log(out.toArray())
console.log(compiled.stats())
compiled.dispose()

Custom Kernels

Tensor.customKernel(...) mirrors tinygrad's alpha custom-kernel shape. The kernel function receives placeholder UOps and returns a SINK body. Polygrad wraps the body in CALL, returns AFTER(...) tensors, and keeps execution in the normal schedule and runtime caches.

const { Tensor, uop } = require('polygrad')

function addKernel(out, a, b) {
  out = out.flatten(); a = a.flatten(); b = b.flatten()
  const i = uop.range(out.numel(), 0)
  return out.index(i).store(a.index(i).add(b.index(i))).end(i).sink()
}

const out = Tensor.empty([4], { dtype: 'float32' })
const y = out.customKernel(
  new Tensor([1, 2, 3, 4]),
  new Tensor([10, 20, 30, 40]),
  addKernel
)[0]
console.log(y.toArray())

This is a UOp CALL extension point, not a raw program-launch API. Custom backward functions are not implemented yet.

Common API Recipes

Default runtime:

const { Tensor, jit, compile, nn } = require('polygrad')

const x = new Tensor([1, 2, 3])
console.log(x.mul(2).add(1).toArray())

Explicit runtime and device selection:

const polygrad = require('polygrad')
const pg = polygrad.create({ core: 'wasm' })

const x = new pg.Tensor([1, 2, 3])
console.log(x.mul(2).toArray())
pg.dispose()

Create tensors. Shape constructors accept either Tensor.zeros(2, 3) or Tensor.zeros([2, 3]):

const a = Tensor.zeros([2, 3])
const b = Tensor.ones([2, 3])
const c = Tensor.randn([2, 3])
const d = Tensor.arange(6).reshape(2, 3)
const e = new Tensor(new Float32Array([1, 2, 3, 4]), { shape: [2, 2] })

Math, movement, indexing:

const x = Tensor.arange(12).reshape(3, 4)
const y = x.permute(1, 0).reshape(2, 6)
const z = y.relu().sum(1)
const picked = x.gather(1, new Tensor([[0, 2], [1, 3], [0, 1]], { dtype: 'int32' }))

Autograd and optimization:

const model = new nn.Linear(4, 1)
const opt = new nn.SGD(nn.getParameters(model), { lr: 0.01 })

const x = Tensor.randn(8, 4)
const target = Tensor.randn(8, 1)

opt.zeroGrad()
const loss = model.call(x).sub(target).square().mean()
loss.backward()
opt.step()

Linear algebra:

const A = new Tensor([[4, 2], [2, 5]])
const b = new Tensor([1, 3])

console.log(A.solve(b).toArray())
console.log(A.cholesky().toArray())
console.log(A.lstsq(b).toArray())

JIT and compile:

const f = jit((x) => x.add(1).realize())
f(new Tensor([1, 2, 3]))  // run
f(new Tensor([4, 5, 6]))  // capture
console.log(f(new Tensor([7, 8, 9])).toArray())  // replay

const compiled = compile((x) => x.mul(2).realize(), [Tensor.empty(3)])
console.log(compiled.run([new Tensor([1, 2, 3])]).toArray())
compiled.dispose()

Repeated input updates:

const x = new Tensor(new Float32Array([1, 2, 3]), { shape: [3] }).realize()
const f = compile((x) => x.square().sum().realize(), [x])

console.log(f.run([x]).item())
x.copyFrom(new Float32Array([4, 5, 6]))
console.log(f.run([x]).item())
f.dispose()

Readback:

const y = new Tensor([1, 2, 3]).mul(2)
console.log(y.toArray())              // typed array on sync runtimes
console.log(y.tolist())               // nested JS arrays
console.log(y.item())                 // scalar tensors only

const [a, b] = Tensor.toTypedArrays(y, y.add(1))

WebGPU readback is explicit async:

const polygrad = require('polygrad')
const pg = polygrad.create({ core: 'wasm', device: 'webgpu' })
const y = new pg.Tensor([1, 2, 3]).mul(2)
console.log(await y.toArrayAsync())
pg.dispose()

Runtime inspection:

const polygrad = require('polygrad')

console.log(polygrad.stats())
console.log(polygrad.canRun({ op: 'add', shape: [1024] }))

canRun(...) is conservative. For some compound op/shape queries it throws when support cannot be proven statically.

API reference at a glance:

| Area | Main APIs | |---|---| | Runtime | create, createAsync, disposeDefault, stats, canRun | | Tensor creation | new Tensor(data), zeros, ones, full, rand, randn, eye, arange, empty | | Tensor math | add, sub, mul, div, exp, log, sqrt, relu, gelu, silu, softmax | | Reductions | sum, mean, max, argmax, sort, argsort, topk, var, std | | Movement/indexing | reshape, expand, permute, shrink, flip, pad, cat, stack, gather, takeAlongAxis, scatter, scatterReduce | | Linalg | dot, qr, triangularSolve, solveTriangular, cholesky, choleskySolve, solve, lstsq | | Data/readback | realize, realizeAsync, toArray, toArrayAsync, toTypedArray, toTypedArrayAsync, toTypedArrays, toTypedArraysAsync, copyFrom, updateFrom | | Compilation | jit, jitAsync, compile, compileAsync, Tensor.customKernel | | Neural nets | nn.Linear, nn.SGD, nn.Adam, nn.AdamW, nn.getParameters, nn.getStateDict |

Package Integration

If your package is built on Polygrad, accept a PolyRuntime from the caller instead of creating a hidden runtime:

function createModel({ polygrad: pg }) {
  const weight = pg.Tensor.randn([4, 2]).realize()
  const predict = pg.compile((x) => x.dot(weight).realize(), [
    pg.Tensor.empty([1, 4])
  ])

  return {
    predict: (x) => predict.run([x]),
    dispose: () => predict.dispose()
  }
}

This lets applications share one set of runtime caches, device handles, and buffer residency across packages.

Tests

From the repository root:

make test-js-native
TMPDIR=$PWD/temp/cc_tmp EM_CACHE=$PWD/temp/emscripten-cache make test-js-wasm
DISPLAY=:1 make test-browser
make test-browser-matrix

test-browser keeps the default Chromium/WebGPU path. test-browser-matrix runs the browser sync bundle against a non-WebGPU smoke matrix and skips local browser executables that are not installed. Override the matrix when you need a specific browser or older Chrome binary:

BROWSER_MATRIX="chromium,firefox,old-chrome=chromium@/path/to/chrome" make test-browser-matrix
BROWSER_MATRIX_DEVICES="auto,interp" make test-browser-matrix

Browser specs are chromium, firefox, webkit, chrome, or label=engine@/absolute/path. WebGPU coverage remains Chromium-only and lives in make test-browser.

License

MIT