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.
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.
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)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-cloverwritescl’s functions, which makes(:use :cl :mlx-cl)difficultmlx-cl.fftsee FFT for examplesmlx-cl.linalgmlx-cl.randomsee Random for examplesmlx-userthe following documentation are assumed to be written undermlx-userpackage.The
mlx-userpackage 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 underimagedir
See tests in test/core/api.lisp for more examples.
- Convert from Lisp to
mlx-arrayTo convert betweenmlx-arrayand Lisp array (data containers), use generic functionmlx-array:- scalar to
mlx-array(mlx-array 2) - array to
mlx-array(mlx-array #3A(((1 2) (3 4)) ((5 6) (7 8))))
note: use array, it’s fast.
(mlx-array '((1 2) (3 4)))
You can implement methods to tell lisp how to convert your data into
mlx-array. - scalar to
- Use
lisp<-to convertmlx-arrayinto Lisp value.For example:
(lisp<- (mlx.rnd:randint 10 :shape #(5 5)))
- Use
(mlx-array pathname)to loadmlx-arrayfrompathname, how to load themlx-arraydepends on the extension name of the pathname.For example, subsystem
mlx-cl/io/npyprovides 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-arrayas NPY usingsavefunction:(->* (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.
- 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))
at: API like Python’sarr[...](at array &rest indexs)(at* array &rest indexs)(alias for(lisp<- (at ...)))
the
indexscould 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:firstfor the first element on the corresponding axis:lastfor 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, 3th 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-sliceto define alias of the slice shortcuts. For example, the shortcuts of:allcan 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)
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)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))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.
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:
- =mlx-cl/io/npy=: read and write NPY file via marcoheisig/numpy-file-format
- =mlx-cl/io/png=: read and write PNG file via pngload and zpng
- =mlx-cl/io/jpeg=: read and write JPEG file via sharplispers/cl-jpeg
- =mlx-cl/io/tiff=: read and write TIFF file via slyrus/retrospectiff
DEV Note:
- You could load system
mlx-cl/devto quickly loadmlx-clandmlx-cl/test, together with a set of indent rules to use with SLY/SLIME in Emacs. - There’s some helpful Emacs scripts under
devwhich could help with developing MLX-CLread more in MLX-CL/DEV/ELISP