Skip to content

li-yiyang/mlx-cl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

66 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MLX-CL

About

MLX-CL is a Common Lisp binding for MLX, together with a set of trivial data processing library.

NOTE: Github’s org-mode renderer is buggy, please refer documentation.

NOTE: Although it may not be exactly true, but every exposed function, class, variable in mlx-cl should came with feature-rich documentation. If you’ve found documentation missing or not clear, it is DEFINITELY an issue.

Tutorials

If you’re in hurry, skim through the Usage and read the documentation string of each MLX-CL function (for example, (documentation 'mlx:add 'function)).

But you could also try some Tutorial of MLX with some more specific topics.

Usage

Installation

You need: a Common Lisp distribution (tested on SBCL 2.5.8, LispWorks Personal Edition 8.0.1), Quicklisp (Common Lisp package manager), and mlx-cl under where your quicklisp can find.

A possible installation process

Here’s a possible installation process:

> mkdir -pv ~/common-lisp/
> git clone --recursive https://github.com/li-yiyang/mlx-cl.git ~/common-lisp/mlx-cl
...
> sbcl
This is SBCL 2.5.8, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
To load "trivial-indent":
  Load 1 ASDF system:
    trivial-indent
; Loading "trivial-indent"

* (ql:quickload :mlx-cl)
To load "mlx-cl":
  Load 1 ASDF system:
    mlx-cl
; Loading "mlx-cl"
..................................................
................................................
(:mlx-cl)
* (in-package :mlx-user)
#<package "MLX-USER">
* (+ 1 '(2 3 4))
array([3, 4, 5], dtype=float32)

Systems and Packages

The core functionalities are provided within system mlx-cl:
(ql:quickload :mlx-cl)

would loads the following packages:

  • mlx-cl: the core binding package, if you are about to develop new packages, you should
    (defpackage #:your-new-package-name
      (:use :mlx-cl))
        

    note that the API of mlx-cl overwrites cl’s functions, which makes (:use :cl :mlx-cl) difficult

  • mlx-cl.fft see FFT for examples
  • mlx-cl.linalg
  • mlx-cl.random see Random for examples
  • mlx-user the following documentation are assumed to be written under mlx-user package.

    The mlx-user package is defined like:

    (defpackage #:mlx-user
      (:use :mlx-cl)
      (:local-nicknames
       (:fft    :mlx-cl.fft)
       (:rnd    :mlx-cl.random)
       (:linalg :mlx-cl.linalg)))
        

There are some subsystem(s) providing additional functionalities above mlx-cl:

  • mlx-cl/image: see readme under image dir

Basic Operations

See tests in test/core/api.lisp for more examples.

Convert

  1. Convert from Lisp to mlx-array To convert between mlx-array and Lisp array (data containers), use generic function mlx-array:
    • scalar to mlx-array
      (mlx-array 2)
              
    • (mlx-array '((1 2) (3 4)))
            
    • array to mlx-array
      (mlx-array #3A(((1 2) (3 4))
                     ((5 6) (7 8))))
              

      note: use array, it’s fast.

    You can implement methods to tell lisp how to convert your data into mlx-array.

  2. Use lisp<- to convert mlx-array into Lisp value.

    For example:

    (lisp<- (mlx.rnd:randint 10 :shape #(5 5)))
        
  3. Use (mlx-array pathname) to load mlx-array from pathname, how to load the mlx-array depends on the extension name of the pathname.

    For example, subsystem mlx-cl/io/npy provides NPY file I/O functionalities.

    (ql:quickload :mlx-cl/io/npy)
        

    If having a NPY file:

    import numpy as np
    
    np.save("./res/tmp/example.npy", np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32))
        
    (mlx-array "./res/tmp/example.npy")
        

    You could write mlx-array as NPY using save function:

    (->* (mlx-array #2A((1 2 3) (4 5 6)) :dtype :uint8)
      (save * :output "./res/tmp/example.npy"))
        
    import numpy as np
    
    print(np.load("./res/tmp/example.npy"))
        

    See I/O for more detailed description.

Alloc mlx-array

  • Generate by range:
    • (arange [start] stop [step] &key step dtype)
      (arange 10)                             ; (arange STOP)
              
      (arange 5 10)                           ; (arange START STOP)
              
      (arange 2 10 2)                         ; (arange START STOP STEP)
              
    • (linspace start stop &optional num &key dtype)
      (linspace 0 10) ; num=50 by default
              
  • Generate array with constant values:
    • (zeros SHAPE &key DTYPE)
    • (ones SHAPE &key DTYPE)
    • (full SHAPE &optional VALUE &key DTYPE)
    (full '(5 5) 2333)
        
  • Generate coordinate grids:
    • (meshgrid arrays &key SPARES INDEXING)
      (meshgrid (arange -2 2)
                (arange -2 2))
              

Indexing mlx-array

  • at: API like Python’s arr[...]
    • (at array &rest indexs)
    • (at* array &rest indexs) (alias for (lisp<- (at ...)))

    the indexs could be

    • (~ [start=0] stop [step=1] &key step)
      • (~ * * -1) (equal to (~ :step -1))
      • (~ 0 * -1) (equal to (~ 0 -1 -1))
      • (~ 5) (equal to (~ 0 5 1))
      • (~ ) or just ~ (equal to (~ 0 -1 -1))
    • integer
    • rational for first / last (negative) parts of axis
    • keywords for shortcuts, for example:
      • :* for all
      • :first for the first element on the corresponding axis
      • :last for the last element the corresponding axis
      • use (documentation keyword 'mlx:slice) to get the documentation of slice shortcuts documentations

    Examples:

    • take the all (:*) elements in first axis, second (2) element in second axis, [0, 2) elements in third axis:
      (let ((arr (reshape (arange 0 27) '(3 3 3))))
        (at arr :* 1 (~ 0 2)))
              

      this can also bewritten as:

      (let ((arr (reshape (arange 0 27) '(3 3 3))))
        (at arr :all :second 1/2))
              

      which means take all (:all) elements in first axis, second (:second) element in second axis, first half (1/2) in third axis.

      this is equal to calling MLX Python’s API like:

      import mlx.core as mx
      print(mx.arange(0, 27).reshape((3, 3, 3))[:,1,0:2])
              
      array([[3, 4],
             [12, 13],
             [21, 22]], dtype=int32)
              
    • take all element in the first axis, the 0, 2, 3 th elements on the second axis:
      (let ((arr (reshape (arange 0 20) '(2 10))))
        (at arr :* '(0 2 3)))
              

      this is equal to calling MLX Python’s API like:

      import mlx.core as mx
      print(mx.arange(0, 20).reshape((2, 10))[:,[0, 2, 3]])
              
      array([[0, 2, 3],
             [10, 12, 13]], dtype=int32)
              

    Dev Note: You can use defmlx-slice to define alias of the slice shortcuts. For example, the shortcuts of :all can be defined as:

    (defmlx-slice :all (shape)
      "Slice for all SHAPE. "
      (~ 0 shape 1))
        

    The documentation of mlx slice can be show by:

    (documentation :* 'at)
        

Operations

MLX-CL overwrites a few of Common Lisp’s methods (cl:+, cl:-, cl:*, cl:/, …) as generic functions. So if you don’t worry about some speed lost, you can do

(+ 1 (* 2 3) (/ 10 5))

as if you are using cl:+, cl:-, cl:*, cl:/ (other functions are the same).

For those functions that are not supported in normal common lisp functions:

(+ 2 '(3 4 5))

they would be convert into mlx-array automatically. Use lisp<- to force convert mlx-array as lisp value.

A example

the following example came from my Image Processing homework:

(defun gauss-kernel (sigma &optional m
                     &aux
                       (m-min (1+ (* 2 (ceiling (* 3 sigma)))))
                       (m-val (the (or null (integer 0)) (or m m-min))))
  "Return a Gauss kernel matrix.

Definition:
   gauss(x, y) = exp(- (x^2 + y^2) / (2 * sigma^2)) / (2 * pi * sigma^2)
"
  (declare (type (real 0) sigma)
           (type (or null integer) m))
  (when (lisp<- (< m-val m-min))
    (warn "Given m=~A is lower than m_min=~A. " m-val m-min))
  (let ((half (/ (1- m-val) 2)))
    (destructuring-bind (x y)
        (meshgrid (arange (- half) (1+ half))
                  (arange (- half) (1+ half)))
      (let ((ker (exp (- (/ (+ (square x) (square y))
                            (* 2 (square sigma)))))))
        (/ ker (sum ker))))))

which would produce:

(gauss-kernel 0.3)

Random

The package mlx-cl.random is a collection of PRNG processes in MLX.

(loop :repeat 3 :collect (list (rnd:uniform) (rnd:uniform :key 0)))

the API in mlx-cl.random use :key as the random seed locally, or you could use mlx.rnd:seed to set a seed globally.

(rnd:seed 2333)
(rnd:uniform 10 :shape '(4 4))

FFT

The package mlx-cl.fft is a collection of FFT processes binding in MLX.

(fft:fft #(1 2 3 4))

and inverse FFT:

(fft:ifft (fft:fft #(1 2 3 4)))

Note: in order to acclerate, mlx.fft:1dfft, mlx.fft:2dfft would be faster.

I/O

The high-level API of reading and writing mlx-array is:

  • (mlx-array pathname &key ...)
  • (save mlx-array &key output ...)

Behind the scene, it calls:

  • (load-from source format &key)
  • (save-to object output format &key)

Use these functions if failed to guess correct format keyword from the pathname.

If you want to define your own format reading and writing functionalities, you can use the: (defmlx-extension (format . extensions) &rest options).

(write-string (documentation 'defmlx-extension 'function))

Currently implemented io submodules are:

Contributing and Developing

DEV Note:

  • You could load system mlx-cl/dev to quickly load mlx-cl and mlx-cl/test, together with a set of indent rules to use with SLY/SLIME in Emacs.
  • There’s some helpful Emacs scripts under dev which could help with developing MLX-CL

    read more in MLX-CL/DEV/ELISP

About

Common Lisp bindings for MLX

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published