Machine Learning

Now Python can now call Mojo | Looking at the data science

ML engineers, along with software developers, to do all other activities from our code can be considered important consideration. If you are the Python user, you will lose its limitations in this regard. Python is considered a slow language, and you may have heard that most reason is because of its worldwide locking translator (Gil).

What is it, but what can we do about it? There are many ways we can help us with this problem when installing the Spython codes, especially when using a logical python version.

  • Python's latest release is valid code option without using Gil.
  • We can use third-work libraries, such as Numpy, to make the full number of number.
  • There are many ways to compare the same with the same compatibility in the designated language.

One way we can use to beat other high languages ​​within the Python of the critical parties of our code. That's what we will cover in this article as I show you how to call Mojo Code from Python.

Have you heard about Mojo before? If not, here is a fast history lesson.

Mojo is a new language of a modular incident programs. (AI infrastructure company was established by 2022 with compiler Writing Legend Chris Lattner, of llvm and Swift Deamer Fame, and Google Tpus Lead Tim Davis) and first shown in public May 2023.

It was born a simple point of pain, which means lack of Python the work we have discussed earlier. Mojo is experiencing the Supersel of the Syntax of Pytthon / Mlir-Based Compiler Pipelines offering Zero-Active, Memory-based memory management and GPU Accceler.

The first benchmarks have been broken in its opening at Ran Kernel-Mine Works until 35,000 × faster There is vanilla Python, proving that the Mojo can match – or pass – the green and cuda green to allow developers to stay in the normal “Pythonic” area.

However, there is always a stumbling block, and that is that inertia of the inertia completely to move completely into the new language. I'm one of those people, so I was happy when I read that, as a few weeks ago, now it's more than calling Mojo Code from the Newthon.

Does this mean that we get the best in both world: Embell of Python and Mojo's operation?

To explore claims, we will write a certain code using vanillan Python. Then, for each, we will install the version of the Incess and, finally, the Python version Loading some of its combinations in the Mojo Module. Finally, we will compare different times to run.

Will we see the benefit of important work? Read to get.

Setting up the environmental environment

I will be using WSL2 personality with my advancement windows. The best practice is to set up a new environment for the development of each project you work. I usually use Conda for this, but as everyone and Gondannny of whom they appear to go to new use Uv The Package Manager, I will give that movement instead. There are a few ways you can put UV.

$ curl -LsSf  | sh

or...

$ pip install uv

Next, start the project.

$ uv init mojo-test 
$ cd mojo-test
$ uv venv
$ source .venv/bin/activate

Initialized project `mojo-test` at `/home/tom/projects/mojo-test`
(mojo-test) $ cd mojo-test
(mojo-test) $ ls -al
total 28
drwxr-xr-x  3 tom tom 4096 Jun 27 09:20 .
drwxr-xr-x 15 tom tom 4096 Jun 27 09:20 ..
drwxr-xr-x  7 tom tom 4096 Jun 27 09:20 .git
-rw-r--r--  1 tom tom  109 Jun 27 09:20 .gitignore
-rw-r--r--  1 tom tom    5 Jun 27 09:20 .python-version
-rw-r--r--  1 tom tom    0 Jun 27 09:20 README.md
-rw-r--r--  1 tom tom   87 Jun 27 09:20 main.py
-rw-r--r--  1 tom tom  155 Jun 27 09:20 pyproject.toml

Now, add any external libraries we need

(mojo-test) $ uv pip install modular numpy matplotlib

How do you call Mojo from Python at work?

Let's imagine that we have the next simple job that takes the flexible python as a dispute and adds two to their value. For example,

# mojo_func.mojo
#
fn add_two(py_obj: PythonObject) raises -> Python
    var n = Int(py_obj)
    return n + 2

When Python tried to load Add_TWO, looking at a job called Pyanit_add_TWO (). Inside Pyanit_add_TWO (), we must announce all Mojo activities and the types that want to appear in Python use PythonmoduleBuilder the library. Therefore, in fact, our MOBO code in its final form will be true.

from python import PythonObject
from python.bindings import PythonModuleBuilder
from os import abort

@export
fn PyInit_mojo_module() -> PythonObject:
    try:
        var m = PythonModuleBuilder("mojo_func")
        m.def_function[add_two]("add_two", docstring="Add 2 to n")
        return m.finalize()
    except e:
        return abort[PythonObject](String("Rrror creating Python Mojo module:", e))

fn add_two(py_obj: PythonObject) raises -> PythonObject:
    var n = Int(py_obj)
    n + 2

Python code requires additional boilplate code for efficiency, as shown here.

import max.mojo.importer
import sys

sys.path.insert(0, "")

import mojo_func

print(mojo_func.add_two(5))

# SHould print 7

Examples of the Code

In my example each, I will show three different types of code. One will be recorded with pure Pipon, one will use profits to speed things, while the other will place the calls in Mojo where appropriate.

Be fired that beating the Mojo code from Python is the first development. You can expect significant changes to APIs and Ergonomics

Example 1 – Counting the Mandelbrot set

By our original example, we will include and show a set of mandelbrot. This is more expensive, and as we will see, pureython version takes a lot of time to complete.

We will need four complete files of this example.

1 / Mandelbrot_pure_py.py

# mandelbrot_pure_py.py
def compute(width, height, max_iters):
    """Generates a Mandelbrot set image using pure Python."""
    image = [[0] * width for _ in range(height)]
    for row in range(height):
        for col in range(width):
            c = complex(-2.0 + 3.0 * col / width, -1.5 + 3.0 * row / height)
            z = 0
            n = 0
            while abs(z) <= 2 and n < max_iters:
                z = z*z + c
                n += 1
            image[row][col] = n
    return image

2 / mandelbrot_numpy.py

# mandelbrot_numpy.py

import numpy as np

def compute(width, height, max_iters):
    """Generates a Mandelbrot set using NumPy for vectorized computation."""
    x = np.linspace(-2.0, 1.0, width)
    y = np.linspace(-1.5, 1.5, height)
    c = x[:, np.newaxis] + 1j * y[np.newaxis, :]
    z = np.zeros_like(c, dtype=np.complex128)
    image = np.zeros(c.shape, dtype=int)

    for n in range(max_iters):
        not_diverged = np.abs(z) <= 2
        image[not_diverged] = n
        z[not_diverged] = z[not_diverged]**2 + c[not_diverged]
        
    image[np.abs(z) <= 2] = max_iters
    return image.T

3 / Mandulbrot_mojo.mojo

# mandelbrot_mojo.mojo 

from python import PythonObject, Python
from python.bindings import PythonModuleBuilder
from os import abort
from complex import ComplexFloat64

# This is the core logic that will run fast in Mojo
fn compute_mandel_pixel(c: ComplexFloat64, max_iters: Int) -> Int:
    var z = ComplexFloat64(0, 0)
    var n: Int = 0
    while n < max_iters:
        # abs(z) > 2 is the same as z.norm() > 4, which is faster
        if z.norm() > 4.0:
            break
        z = z * z + c
        n += 1
    return n

# This is the function that Python will call
fn mandelbrot_mojo_compute(width_obj: PythonObject, height_obj: PythonObject, max_iters_obj: PythonObject) raises -> PythonObject:
    
    var width = Int(width_obj)
    var height = Int(height_obj)
    var max_iters = Int(max_iters_obj)

    # We will build a Python list in Mojo to return the results
    var image_list = Python.list()

    for row in range(height):
        # We create a nested list to represent the 2D image
        var row_list = Python.list()
        for col in range(width):
            var c = ComplexFloat64(
                -2.0 + 3.0 * col / width,
                -1.5 + 3.0 * row / height
            )
            var n = compute_mandel_pixel(c, max_iters)
            row_list.append(n)
        
        image_list.append(row_list)
            
    return image_list

# This is the special function that "exports" our Mojo function to Python
@export
fn PyInit_mandelbrot_mojo() -> PythonObject:
    try:
       
        var m = PythonModuleBuilder("mandelbrot_mojo")
        m.def_function[mandelbrot_mojo_compute]("compute", "Generates a Mandelbrot set.")
        return m.finalize()
    except e:
        return abort[PythonObject]("error creating mandelbrot_mojo module")

4 / Main.py

This will cost more of three programs and allow us to remove the mandelbrot graph in Jobyter registry. I will only show the plan once. You will have to take my voice to get well organized in all three Code runs.

# main.py (Final version with visualization)

import time
import numpy as np
import sys

import matplotlib.pyplot as plt # Now, import pyplot

# --- Mojo Setup ---
try:
    import max.mojo.importer
except ImportError:
    print("Mojo importer not found. Please ensure the MODULAR_HOME and PATH are set correctly.")
    sys.exit(1)

sys.path.insert(0, "")

# --- Import Our Modules ---
import mandelbrot_pure_py
import mandelbrot_numpy
import mandelbrot_mojo

# --- Visualization Function ---
def visualize_mandelbrot(image_data, title="Mandelbrot Set"):
    """Displays the Mandelbrot set data as an image using Matplotlib."""
    print(f"Displaying image for: {title}")
    plt.figure(figsize=(10, 8))
    # 'hot', 'inferno', and 'plasma' are all great colormaps for this
    plt.imshow(image_data, cmap='hot', interpolation='bicubic')
    plt.colorbar(label="Iterations")
    plt.title(title)
    plt.xlabel("Width")
    plt.ylabel("Height")
    plt.show()

# --- Test Runner ---
def run_test(name, compute_func, *args):
    """A helper function to run and time a test."""
    print(f"Running {name} version...")
    start_time = time.time()
    # The compute function returns the image data
    result_data = compute_func(*args)
    duration = time.time() - start_time
    print(f"-> {name} version took: {duration:.4f} seconds")
    # Return the data so we can visualize it
    return result_data

if __name__ == "__main__":
    WIDTH, HEIGHT, MAX_ITERS = 800, 600, 5000
    
    print("Starting Mandelbrot performance comparison...")
    print("-" * 40)

    # Run Pure Python Test
    py_image = run_test("Pure Python", mandelbrot_pure_py.compute, WIDTH, HEIGHT, MAX_ITERS)
    visualize_mandelbrot(py_image, "Pure Python Mandelbrot")

    print("-" * 40)

    # Run NumPy Test
    np_image = run_test("NumPy", mandelbrot_numpy.compute, WIDTH, HEIGHT, MAX_ITERS)
    # uncomment the below line if you want to see the plot
    #visualize_mandelbrot(np_image, "NumPy Mandelbrot")

    print("-" * 40)

    # Run Mojo Test
    mojo_list_of_lists = run_test("Mojo", mandelbrot_mojo.compute, WIDTH, HEIGHT, MAX_ITERS)
    # Convert Mojo's list of lists into a NumPy array for visualization
    mojo_image = np.array(mojo_list_of_lists)
    # uncomment the below line if you want to see the plot  
    #visualize_mandelbrot(mojo_image, "Mojo Mandelbrot")

    print("-" * 40)
    print("Comparison complete.")

Finally, here is the result.

Photo by the writer

Okay, so that is a happy start of Mojo. It was about 20 times as soon as Pure Pure Pure and 5 times faster than NUNPY code.

Example 2 – Numerical Compilation

In this example, we will make the integration of the price uses the Mempson law to find the amount of sin (X) on the limit 0 to π. Remember that Simpton's law is a way to calculate the estimated amount of integration and defined as,

∫ ≈ (H / 3) * [f(x₀) + 4f(x₁) + 2f(x₂) + 4f(x₃) + … + 2f(xₙ-₂) + 4f(xₙ-₁) + f(xₙ)]

When:

  • h diameter of each step.
  • The weight is 1, 4, 2, 4, 2, 2, …, 4, 1.
  • The first and last points weighed for 1.
  • Points at exotic the indices have weights for 4.
  • Points at even the indices have weights for 2.

The true number of combinations we try to count two. Let's see how accurate our ways are (and quick).

Once again, we need four files.

1 / integration_pure_py.py

# integration_pure_py.py
import math

def compute(start, end, n):
    """Calculates the definite integral of sin(x) using Simpson's rule."""
    if n % 2 != 0:
        n += 1 # Simpson's rule requires an even number of intervals
    
    h = (end - start) / n
    integral = math.sin(start) + math.sin(end)

    for i in range(1, n, 2):
        integral += 4 * math.sin(start + i * h)
    
    for i in range(2, n, 2):
        integral += 2 * math.sin(start + i * h)
        
    integral *= h / 3
    return integral

2 / Compilation_stream

# integration_numpy.py
import numpy as np

def compute(start, end, n):
    """Calculates the definite integral of sin(x) using NumPy."""
    if n % 2 != 0:
        n += 1
    
    x = np.linspace(start, end, n + 1)
    y = np.sin(x)
    
    # Apply Simpson's rule weights: 1, 4, 2, 4, ..., 2, 4, 1
    integral = (y[0] + y[-1] + 4 * np.sum(y[1:-1:2]) + 2 * np.sum(y[2:-1:2]))
    
    h = (end - start) / n

3 / Consolidation_mojo.mojo

# integration_mojo.mojo
from python import PythonObject, Python
from python.bindings import PythonModuleBuilder
from os import abort
from math import sin

# Note: The 'fn' keyword is used here as it's compatible with all versions.
fn compute_integral_mojo(start_obj: PythonObject, end_obj: PythonObject, n_obj: PythonObject) raises -> PythonObject:
    # Bridge crossing happens ONCE at the start.
    var start = Float64(start_obj)
    var end = Float64(end_obj)
    var n = Int(n_obj)

    if n % 2 != 0:
        n += 1
    
    var h = (end - start) / n
    
    # All computation below is on NATIVE Mojo types. No Python interop.
    var integral = sin(start) + sin(end)

    # First loop for the '4 * f(x)' terms
    var i_1: Int = 1
    while i_1 < n:
        integral += 4 * sin(start + i_1 * h)
        i_1 += 2

    # Second loop for the '2 * f(x)' terms
    var i_2: Int = 2
    while i_2 < n:
        integral += 2 * sin(start + i_2 * h)
        i_2 += 2
        
    integral *= h / 3
    
    # Bridge crossing happens ONCE at the end.
    return Python.float(integral)

@export
fn PyInit_integration_mojo() -> PythonObject:
    try:
        var m = PythonModuleBuilder("integration_mojo")
        m.def_function[compute_integral_mojo]("compute", "Calculates a definite integral in Mojo.")
        return m.finalize()
    except e:
        return abort[PythonObject]("error creating integration_mojo module")

4 / Main.py

import time
import sys
import numpy as np

# --- Mojo Setup ---
try:
    import max.mojo.importer
except ImportError:
    print("Mojo importer not found. Please ensure your environment is set up correctly.")
    sys.exit(1)
sys.path.insert(0, "")

# --- Import Our Modules ---
import integration_pure_py
import integration_numpy
import integration_mojo

# --- Test Runner ---
def run_test(name, compute_func, *args):
    print(f"Running {name} version...")
    start_time = time.time()
    result = compute_func(*args)
    duration = time.time() - start_time
    print(f"-> {name} version took: {duration:.4f} seconds")
    print(f"   Result: {result}")

# --- Main Test Execution ---
if __name__ == "__main__":
    # Use a very large number of steps to highlight loop performance
    START = 0.0
    END = np.pi 
    NUM_STEPS = 100_000_000 # 100 million steps
    
    print(f"Calculating integral of sin(x) from {START} to {END:.2f} with {NUM_STEPS:,} steps...")
    print("-" * 50)

    run_test("Pure Python", integration_pure_py.compute, START, END, NUM_STEPS)
    print("-" * 50)
    run_test("NumPy", integration_numpy.compute, START, END, NUM_STEPS)
    print("-" * 50)
    run_test("Mojo", integration_mojo.compute, START, END, NUM_STEPS)
    print("-" * 50)
    print("Comparison complete.")

And the results?

Calculating integral of sin(x) from 0.0 to 3.14 with 100,000,000 steps...
--------------------------------------------------
Running Pure Python version...
-> Pure Python version took: 4.9484 seconds
   Result: 2.0000000000000346
--------------------------------------------------
Running NumPy version...
-> NumPy version took: 0.7425 seconds
   Result: 1.9999999999999998
--------------------------------------------------
Running Mojo version...
-> Mojo version took: 0.8902 seconds
   Result: 2.0000000000000346
--------------------------------------------------
Comparison complete.

It is interesting that in this case, the Nungy code was more faster than the Mojo code, and its last value was increasing. This highlights the key idea in high performance performance: Trading Vectations and Loops Consolidated Loops.

Numper's power lies in its vectorise power. It will be a great memory block and cost a very well, CO code is found in modern CPU features, such as simd, committing sin () the operation of millions of prices at one time. This “explosion is processing” is very effective.

On the other hand, the Mojo, on the other hand, takes our simple while the LOOP and JIT-combines into the most effective machine code. While this avoids the great distribution of Numperity memory, in this case, the green energy of the NUMCY's Vectoration gives a little edge.

Example 3- Sigmoid work

Storm work is an important idea in AI as a binary division room.

Known as a meal work, is defined as this.

Sigmoid work takes any X inputs to and “Squashes” by sliding on the open limit (0.1). In simple terms, no matter what is transferred to the SIGMOID work, it will always return the average number of 0 and 1.

So, for example,

S(-197865) = 0
S(-2) = 0.0009111
S(3) = 0.9525741
S(10776.87) = 1

This makes it ready to represent certain things like situations.

Because the Python code is simple, we can put it in the measurement text, so we have only two files in this regard.

Sigmoid_mojo.mojo

from python               import Python, PythonObject
from python.bindings      import PythonModuleBuilder
from os                   import abort
from math                 import exp
from time                 import perf_counter

# ----------------------------------------------------------------------
#   Fast Mojo routine (no Python calls inside)
# ----------------------------------------------------------------------
fn sigmoid_sum(n: Int) -> (Float64, Float64):
    # deterministic fill, sized once
    var data = List[Float64](length = n, fill = 0.0)
    for i in range(n):
        data[i] = (Float64(i) / Float64(n)) * 10.0 - 5.0   # [-5, +5]

    var t0: Float64 = perf_counter()
    var total: Float64 = 0.0
    for x in data:                       # single tight loop
        total += 1.0 / (1.0 + exp(-x))
    var elapsed: Float64 = perf_counter() - t0
    return (total, elapsed)

# ----------------------------------------------------------------------
#   Python-visible wrapper
# ----------------------------------------------------------------------
fn py_sigmoid_sum(n_obj: PythonObject) raises -> PythonObject:
    var n: Int = Int(n_obj)                        # validates arg
    var (tot, secs) = sigmoid_sum(n)

    # safest container: build a Python list (auto-boxes scalars)
    var out = Python.list()
    out.append(tot)
    out.append(secs)
    return out                                     # -> PythonObject

# ----------------------------------------------------------------------
#   Module initialiser  (name must match:  PyInit_sigmoid_mojo)
# ----------------------------------------------------------------------
@export
fn PyInit_sigmoid_mojo() -> PythonObject:
    try:
        var m = PythonModuleBuilder("sigmoid_mojo")
        m.def_function[py_sigmoid_sum](
            "sigmoid_sum",
            "Return [total_sigmoid, elapsed_seconds]"
        )
        return m.finalize()
    except e:
        # if anything raises, give Python a real ImportError
        return abort[PythonObject]("error creating sigmoid_mojo module")

main.

# bench_sigmoid.py
import time, math, numpy as np

N = 50_000_000  

# --------------------------- pure-Python -----------------------------------
py_data = [(i / N) * 10.0 - 5.0 for i in range(N)]
t0 = time.perf_counter()
py_total = sum(1 / (1 + math.exp(-x)) for x in py_data)
print(f"Pure-Python : {time.perf_counter()-t0:6.3f} s  - Σσ={py_total:,.1f}")

# --------------------------- NumPy -----------------------------------------
np_data = np.linspace(-5.0, 5.0, N, dtype=np.float64)
t0 = time.perf_counter()
np_total = float(np.sum(1 / (1 + np.exp(-np_data))))
print(f"NumPy       : {time.perf_counter()-t0:6.3f} s  - Σσ={np_total:,.1f}")

# --------------------------- Mojo ------------------------------------------
import max.mojo.importer          # installs .mojo import hook
import sigmoid_mojo               # compiles & loads shared object

mj_total, mj_secs = sigmoid_mojo.sigmoid_sum(N)
print(f"Mojo        : {mj_secs:6.3f} s  - Σσ={mj_total:,.1f}")

Here's the outgoing.

$ python sigmoid_bench.py
Pure-Python :  1.847 s  - Σσ=24,999,999.5
NumPy       :  0.323 s  - Σσ=25,000,000.0
Mojo        :  0.150 s  - Σσ=24,999,999.5

This page ΣS = … … Results show the amount of all sigmoid values. In the Turtor, this should be accurately equal to the classification of 2, as in the N often supports infinity.

But as we see, the use of Mojo symbolizes the best increase in the nunpy code already immediately and more than 12x faster than the implementation of the Python.

Not very shack.

Summary

This article examines the exciting new power to call the highest performance code directly from Python to speed up wide jobs. MOJO, A new language of the best programs from Modar, promises the C-level implementation of the normal Pytax Syntax, aims to resolve the speed of psychological history.

Examining this promise, we set the three-expensive three-generation marks: Mandelbrot Set, numbers, and Sigmoid calculations, using each Python, Hybrid Python-Mojo.

Results portray the effective workplace of Loop-Heavy algoriths where data can be fully processed in traditional Mojo. MOJO can extract very pure python and the codere of a very prepared letter. However, we have also seen that in well-synchronized activities with Numper's Vectomed activities, the previously integrated activities may save a small edge over MOJO.

This investigation shows that while Mollo is a new powerful Psythese maximum tool, gaining high performance requires a thought-provision of “Bridge-crossing” over two tongues of language.

As regularly, when you look at your code enhancements, you test, test, test. That is the last Arbiter that is worth what or not.

Source link

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button