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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@frost-beta/mlx

v0.1.0

Published

Node-API bindings for MLX

Downloads

132

Readme

node-mlx

A machine learning framework for Node.js, based on MLX.

This project is not affiliated with Apple, you can support the development by sponsoring me.

Supported platforms

GPU support:

  • Macs with Apple Silicon

CPU support:

  • x64 Macs
  • x64/arm64 Linux

Examples

Usage

import mlx from '@frost-beta/mlx';
const {core: mx, nn} = mlx;

const model = new nn.Sequential(
  new nn.Sequential(new nn.Linear(2, 10), nn.relu),
  new nn.Sequential(new nn.Linear(10, 10), new nn.ReLU()),
  new nn.Linear(10, 1),
  mx.sigmoid,
);
const y = model.forward(mx.random.normal([32, 2]));
console.log(y);

APIs

There is currently no documentations for JavaScript APIs, please check the TypeScript definitions for available APIs, and MLX's official website for documentations.

The JavaScript APIs basically duplicate the official Python APIs by converting the API names from snake_case to camelCase. For example the mx.not_equal Python API is renamed to mx.notEqual in JavaScript.

There are a few exceptions due to limitations of JavaScript:

  • JavaScript numbers are always floating-point values, so the default dtype of mx.array(42) is mx.float32 instead of mx.int32.
  • The mx.var API is renamed to mx.variance.
  • Operator overloading does not work, use mx.add(a, b) instead of a + b.
  • Indexing via [] operator does not work, use array.item and array.itemPut_ methods instead (the _ suffix means inplace operation).
  • delete array does nothing, you must wait for garbage collection to get the array's memory freed or use mx.dispose.
  • The Module instances can not be used as functions, the forward method must be used instead.

Unimplemented features

Some features are not supported yet and will be implemented in future:

  • The distributed module has not been implemented.
  • The mx.custom_function API has not been implemented.
  • The custom Metal kernel has not been implemented.
  • It is not supported using JavaScript Array as index.
  • The function passed to mx.vmap must have all parameters being mx.array.
  • The captured inputs/outputs parameters of mx.compile has not been implemented.
  • When creating a mx.array from JavaScript Array, the Array must only include primitive values.
  • The APIs only accept plain parameters, e.g. mx.uniform(0, 1, [2, 2]). Named parameter calls like mx.uniform({shape: [2, 2]}) has not been implemented.
  • The .npz tensor format is not supported yet.

JavaScript-only APIs

There are a few new APIs in node-mlx, for solving JavaScript-only problems.

Constructor of mx.array

You can pass following JavaScript types to mx.array:

  • number/boolean/mx.Complex
  • Array<T>
  • new Array(length) - Creates a 1D array filled with 0.
  • Buffer - Same with UInt8Array.
  • Int8Array/Uint8Array/Int16Array/Uint16Array/Int32Array/Uint32Array/Float32Array

mx.array.toTypedArray

While it is possible to use the tolist() method of mx.array to convert the array to JavaScript Array, a copy of data is invovled.

For 1D arrays whose dtype has a representation in JavaScript TypedArray, you can use the toTypedArray() method to expose the data to JavaScript without copying.

const buffer: Uint8Array = mx.array([1, 2, 3, 4], mx.uint8);

mx.asyncEval

The mx.eval API is synchronous that the main thread would be blocked waiting for the result, which breaks the assumption of Node.js that nothing should block in main thread, and results in a hanging process that not responding to anything, including tries to end the process with Ctrl+C.

The mx.asyncEval API is the asynchronous version that returns a Promise that can be awaited for. It is useful when the program runs a large model and you want to make the app alive to user interactions while doing computations.

const y = model.forward(x);
await mx.asyncEval(y);

mx.tidy

This is the same with tf.tidy API of TensorFlow.js, which cleans up all intermediate tensors allocated in the passed functions except for the returned ones.

let result = mx.tidy(() => {
  return model.forward(x);
});

In addition, it also works with async functions.

await mx.tidy(async () => { ... });

mx.dispose

This is the same with tf.dispose API of TensorFlow.js, which cleans up all the tensors found in the object.

mx.dispose({ a: mx.array([1, 2, 3, 4]) });
mx.dispose(mx.array([1]), mx.array([2]));

Complex numbers

There is no built-in complex numbers in JavaScript, and we use objects to represent them:

interface Complex {
  re: number;
  im: number;
}

You can also use the mx.Complex(real, imag?) helper to create complex numbers.

Indexing

Slice in JavaScript is represented as object:

interface Slice {
  start: number | null;
  stop: number | null;
  step: number | null;
}

You can also use the mx.Slice(start?, stop?, step?) helper to create slices.

The JavaScript standard does not allow using ... as values. To use ellipsis as index, use string "..." instead.

When using arrays as indices, make sure a integer dtype is specified because the default dtype is float32, for example a.index(mx.array([ 1, 2, 3 ], mx.uint32)).

Here are some examples of translating Python indexing code to JavaScript:

Getters

| Python | JavaScript | |--------------------------------------|----------------------------------------------------| | array[None] | array.index(null) | | array[Ellipsis, ...] | array.index('...', '...') | | array[1, 2] | array.index(1, 2) | | array[True, False] | array.index(true, false) | | array[1::2] | array.index(mx.Slice(1, None, 2)) | | array[mx.array([1, 2])] | array.index(mx.array([1, 2], mx.int32)) | | array[..., 0, True, 1::2] | array.index('...', 0, true, mx.Slice(1, null, 2) |

Setters

| Python | JavaScript | |--------------------------------------|--------------------------------------------------------------| | array[None] = 1 | array.indexPut_(null, 1) | | array[Ellipsis, ...] = 1 | array.indexPut_(['...', '...'], 1) | | array[1, 2] = 1 | array.indexPut_([1, 2], 1) | | array[True, False] = 1 | array.indexPut_([true, false], 1) | | array[1::2] = 1 | array.indexPut_(mx.Slice(1, null, 2), 1) | | array[mx.array([1, 2])] = 1 | array.indexPut_(mx.array([1, 2], mx.int32), 1) | | array[..., 0, True, 1::2] = 1 | array.indexPut_(['...', 0, true, mx.Slice(1, null, 2)], 1) |

Translating between Python/JavaScript index types

| Python | JavaScript | |----------------------|------------------------------| | None | null | | Ellipsis | "..." | | ... | "..." | | 123 | 123 | | True | true | | False | false | | : or :: | mx.Slice() | | 1: or 1:: | mx.Slice(1) | | :3 or :3: | mx.Slice(null, 3) | | ::2 | mx.Slice(null, null, 2) | | 1:3 | mx.Slice(1, 3) | | 1::2 | mx.Slice(1, null, 2) | | :3:2 | mx.Slice(null, 3, 2) | | 1:3:2 | mx.Slice(1, 3, 2) | | mx.array([1, 2]) | mx.array([1, 2], mx.int32) |

Building

For building on platforms other than Macs with Apple Silicon, you must have blas installed.

# Linux
sudo apt-get install -y libblas-dev liblapack-dev liblapacke-dev
# x64 Mac
brew install openblas

This project is mixed with C++ and TypeScript code, and uses cmake-js to build the native code.

git clone --recursive https://github.com/frost-beta/node-mlx.git
cd node-mlx
npm install
npm run build -p 8
npm run test

Releasing

The prebuilt binaries are uploaded to the GitHub Releases, when installing node-mlx from npm registry, the prebuilt binaries will always be downloaded and there is no fallback for building from source code.

The version string is always 0.0.1-dev in package.json, which means local development, and npm package can only be published via GitHub workflow by pushing a new tag.

Versioning

Before matching the features and stability of the official Python APIs, this project will stay on 0.0.x for npm versions.

Syncing with upstream

The tests and most TypeScript source code were converted from the Python code of the official MLX project, when updating the deps/mlx submodule, review every new commit and make sure changes to Python APIs/tests/implementations are also reflected in this repo.