Skip to main content

Simple Llama model inference on macOS with MLX

Project description

inferMLX - Easy LLM inference on macOS with MLX

(including infer.py - LLM inference in one annotated Python file!)

PyPI

An easy library for doing LLM inference of Llama-based models on macOS, thanks to Apple's MLX project. The main feature is being able to manipulate the inference process for either educational or entertainment reasons.

Basic installation and usage

pip install infermlx
import infermlx.infer as infermlx
model = infermlx.Model.load_model()
model.generate("Tell me a joke")

A man walked into a library and asked the librarian, "Do you have any books on Pavlov's dogs and Schrödinger's cat?"

Alternatively, if you don't want it to output in real-time to stdout:

output, metadata = model.generate("Tell me a joke", temp=1.8, max_tokens=50, realtime=False)
print(output)
print(metadata.generation_tps)

Look at the example-*.py scripts for more ideas. You can also pass in parameters to generate: system_prompt, max_tokens, seed, realtime and logits_processors.

(infer.py, in the infermlx folder, is a single Python program with the fewest parts you need to do inference of Llama-compatible models on macOS. On a modern Mac with Python and the dependencies installed, python infer.py --prompt 'Tell me a joke.' should result in a cringeworthy joke.)

Running infer.py directly

If you download the repo and want to play with infer.py, do pip install -r requirements.txt and run infer.py like so:

python -m infermlx.infer --prompt 'Tell me a joke.' 
# OR if you end up in the infermlx directory
python infer.py --prompt 'Tell me a joke.' 

It defaults to unsloth/Llama-3.2-1B-Instruct as it's free to use, quick, and only needs 4GB of RAM for inference. mistralai/Mistral-7B-Instruct-v0.2 is a better model by far but needs >16GB of RAM free.

infer.py can be run with several options:

  • --model
    Default: "unsloth/Llama-3.2-1B-Instruct"
    Description: The model to load for inference. Can be a HuggingFace repo or local directory.

  • --prompt, -p
    Default: "Tell me a joke."
    Description: Your prompt, unsurprisingly.

  • --system-prompt, -s
    Default: None
    Description: An optional system prompt, e.g. "Talk like a pirate."

  • --max-tokens, -m
    Type: int
    Default: 1000
    Description: The maximum number of tokens to generate.

  • --temp
    Type: float
    Default: 0.0
    Description: Temperature for sampling. As always, values yield more deterministic outputs, while higher values introduce more randomness. This is explained in more detail in the code.

  • --seed
    Type: int
    Default: 42 Description: A random seed for the PRNG. Everyone in LLM-land seems to use 42 as a default because Ilya Sutskever did it in a demo once or something.

Logits processors (the fun bit)

One way to have fun with LLMs, even small ones, is to 'force' them to say things. For example, if your model always refuses to answer a specific prompt, what happens if you force it to start its response with 'Yes, I am happy to help'? It depends. But you can do that!

import infermlx.infer as infermlx

model = infermlx.Model.load_model()

token_stream = model.tokenizer.encode("Here's one about pirates.", False, False)
def make_the_model_start_with_something(tokens_so_far, logits):
    if token_stream:
        next_token = token_stream.pop(0)
        logits[:, next_token] = 2000
    return logits

model.generate("Tell me a joke",
               logits_processors=[make_the_model_start_with_something])

You could also give the model a 'tic' of sorts:

import random
import infermlx.infer as infermlx

model = infermlx.Model.load_model("mistralai/Mistral-7B-Instruct-v0.2")

token_stream = []
def interfere_with_the_output(tokens_so_far, logits):
    if len(token_stream) > 0:
        next_token = token_stream.pop(0)
        logits[:, next_token] = 2000
    else:
      if random.random() < 0.08:
          token_stream.extend(model.tokenizer.encode("crap!", False, False))
    return logits

model.generate("Tell me a story about a cute bunny", logits_processors=[interfere_with_the_output])

Which yields something akin to this:

Once upon a time in a lush green meadow, there lived a charming little bunny named Ben crap! I mean, Bunny. Bunny was known for his crap! I mean, his beautiful, crap! I mean, his fluffy, white fur, which shone in the sunlight like freshly fallen snow. He had twinkling, blue eyes that spark crap! I mean, shone with curiosity crap! I mean, curiosity and wonder.

Let your imagination run wild! (You could also detect certain things it has said and then force its ongoing response from there.)

Motivation

When I saw Apple's work on LLM inference tooling I had fun using it, but realized if I wanted to understand inference I'd need to get my head into it and reimplement things.

infer.py (and then the overall infermlx package) is the result. I've annotated the source to provide guidance. Much of the code has been changed from Apple's original source (and the structure is totally different) but check out the mlx_lm project to see where it began and for more features.

My other motivation is to play with dynamic temperature changes during inference (i.e during 'thinking') and with logit stuffing (i.e. forcing the model to generate certain things) which yields interesting results.

For now, I will let you play. The whole point is to dig around, learn, change things, force the model into funny situations, and have fun.

TODO

  • Provide an option to not automatically print streamed output
  • Add support for LoRA adapters to add fine tuning fun to the mix

Credits

  • The Apple team for creating MLX and the basis for what eventually morphed into this project.
  • Everyone making and training models.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

infermlx-0.0.2.tar.gz (14.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

infermlx-0.0.2-py3-none-any.whl (13.0 kB view details)

Uploaded Python 3

File details

Details for the file infermlx-0.0.2.tar.gz.

File metadata

  • Download URL: infermlx-0.0.2.tar.gz
  • Upload date:
  • Size: 14.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.11

File hashes

Hashes for infermlx-0.0.2.tar.gz
Algorithm Hash digest
SHA256 7d9db32afd7960e66ca44caa3fe8faaa79d6b6442515759d2b46ff1ff58a5545
MD5 aad0fb223b94f0af4dd8cf15ef6fd827
BLAKE2b-256 f2c46b54c67e80f8b777729aee51b216077f8e12fd8dd1709c1a33bd8e2afd63

See more details on using hashes here.

File details

Details for the file infermlx-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: infermlx-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 13.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.11

File hashes

Hashes for infermlx-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 57d57b3edc0b8c6b9bdc092690e3a365071dc3cb6904402c025cd10f25d20d92
MD5 1039e4b2f35e425ff933239c3b5773d7
BLAKE2b-256 8ebb2b2fd82dd3e7d9c5d34faaa80296ae92f9cadfc3bac8cd42e8900011ca36

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page