惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 司徒正美
Google DeepMind News
Google DeepMind News
P
Palo Alto Networks Blog
SecWiki News
SecWiki News
S
Secure Thoughts
P
Privacy International News Feed
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
T
Tenable Blog
W
WeLiveSecurity
Application and Cybersecurity Blog
Application and Cybersecurity Blog
A
Arctic Wolf
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Spread Privacy
Spread Privacy
V2EX - 技术
V2EX - 技术
Project Zero
Project Zero
C
CERT Recently Published Vulnerability Notes
Security Archives - TechRepublic
Security Archives - TechRepublic
Hacker News: Ask HN
Hacker News: Ask HN
Cyberwarzone
Cyberwarzone
Hacker News - Newest:
Hacker News - Newest: "LLM"
S
Schneier on Security
L
Lohrmann on Cybersecurity
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Scott Helme
Scott Helme
H
Hacker News: Front Page
博客园 - Franky
月光博客
月光博客
D
DataBreaches.Net
Know Your Adversary
Know Your Adversary
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
O
OpenAI News
N
Netflix TechBlog - Medium
G
GRAHAM CLULEY
Engineering at Meta
Engineering at Meta
博客园 - 叶小钗
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
B
Blog
人人都是产品经理
人人都是产品经理
I
Intezer
酷 壳 – CoolShell
酷 壳 – CoolShell
云风的 BLOG
云风的 BLOG
IT之家
IT之家
V
Vulnerabilities – Threatpost
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
L
LangChain Blog
Google Online Security Blog
Google Online Security Blog
博客园 - 聂微东
Hugging Face - Blog
Hugging Face - Blog
雷峰网
雷峰网

Modular Blog

Qualcomm to Acquire Modular Modular 26.4: SOTA MoE Serving, Model Bringup via Agent Skills, Mojo 1.0 Beta 2 and More ModCon 2026: Modular’s Developer Conference Day Zero: MiniMax M3 Open Weights on Modular Cloud Modverse #55: Mojo 1.0 Beta, Community Mojo Libraries, and Real-Time Patient Conversations Powered by MAX What about OpenCL and CUDA C++ alternatives? (Democratizing AI Compute, Part 5) Why LLM Inference Needs a New Kind of Router - Part 3 Three trends from MLSys 2026 Why LLM Inference Needs a New Kind of Router - Part 2 How I built a pure Mojo app (and 10 libraries) with AI agents Hippocratic AI partners with Modular to power flexible, high-quality inference for real-time patient conversations Translating to Mojo via AI Agents Inkwell: Why Your Inference Platform Matters As Much As Your Model Why LLM Inference Needs a New Kind of Router - Part 1 Modular 26.3: Mojo 1.0 Beta, MAX Video Gen, and more Modverse #54: AMD AI DevDay, New Modular Offices, and a Community That Keeps Shipping How Frontier Coding Agents Built a Video Diffusion Pipeline on MAX TileTensor Part 1 - Safer, More Efficient GPU Kernels Modular Opens Edinburgh & San Francisco Offices Structured Mojo Kernels Part 4 - Portability and the Road Ahead Day Zero Launch: Fastest Performance for Gemma 4 on NVIDIA and AMD Modverse #54: From GTC to Edinburgh, a Community Building Momentum Software Pipelining for GPU Kernels: Part 1 - The Pipeline Problem Structured Mojo Kernels Part 3 - Composition in Practice Modular 26.2: State-of-the-Art Image Generation and Upgraded AI Coding with Mojo Modular at NVIDIA GTC 2026: MAX on Blackwell, Mojo Kernel Porting, and DeepSeek V3 on B200 Structured Mojo Kernels Part 2 - The Three Pillars Modverse #53: Community Builds, Research Milestones, and a Growing Ecosystem Structured Mojo Kernels Part 1 - Peak Performance, Half the Code The Claude C Compiler: What It Reveals About the Future of Software BentoML Joins Modular The Five Eras of KVCache Modular 26.1: A Big Step Towards More Programmable and Portable AI Infrastructure How to Beat Unsloth's CUDA Kernel Using Mojo—With Zero GPU Experience 🔥 Modular 2025 Year in Review The path to Mojo 1.0 Modverse #52: Advancing AI Together — Community Projects & Platform Milestones Modular 25.7: Faster Inference, Safer GPU Programming, and a More Unified Developer Experience "TTS 1 Max" (powered by Modular Platform) Ranked #1 Speech Model on Artificial Analysis PyTorch and LLVM in 2025 — Keeping up With AI Innovation Achieving State-of-the-Art Performance on AMD MI355 — in Just 14 Days Modular Raises $250M to scale AI's Unified Compute Layer Modular 25.6: Unifying the latest GPUs from NVIDIA, AMD, and Apple Matrix Multiplication on Blackwell: Part 4 - Breaking SOTA Modverse #51: Modular x Inworld x Oracle, Modular Meetup Recap and Community Projects Matrix Multiplication on Blackwell: Part 3 - The Optimizations Behind 85% of SOTA Performance Matrix Multiplication on Blackwell: Part 2 - Using Hardware Features to Optimize Matmul Matrix Multiplication on Blackwell: Part 1 - Introduction Modverse #50: Modular Platform 25.5, Community Meetups, and Mojo's Debut in the Stack Overflow Developer Survey Modular Platform 25.5: Introducing Large Scale Batch Inference SF Compute and Modular Partner to Revolutionize AI Inference Economics AI Agents for AWS Marketplace Modverse #49: Modular Platform 25.4, Modular 🤝 AMD, and Modular Hack Weekend Inside Modular Hack Weekend: Top Projects and Community Highlights How is Modular Democratizing AI Compute? (Democratizing AI Compute, Part 11) Modular 25.4: One Container, AMD and NVIDIA GPUs, No Lock-In Introducing Mammoth: Enterprise-Scale GenAI Deployments Made Simple Modular + AMD: Unleashing AI performance on AMD GPUs Modverse #48: Modular Platform 25.3, MAX AI Kernels, and the Modular GPU Kernel Hackathon Modular GPU Kernel Hackathon Highlights: Innovation, Community, & Mojo🔥 Modular’s bet to break out of the Matrix (Democratizing AI Compute, Part 10) Modular Platform 25.3: 450K+ Lines of Open Source Code and pip Packaging A New, Simpler License for MAX and Mojo Why do HW companies struggle to build AI software? (Democratizing AI Compute, Part 9) Modverse #47: MAX 25.2 and an evening of GPU programming at Modular HQ What about the MLIR compiler infrastructure? (Democratizing AI Compute, Part 8) What about Triton and Python eDSLs? (Democratizing AI Compute, Part 7) MAX 25.2: Unleash the power of your H200's–without CUDA! What about TVM, XLA, and AI compilers? (Democratizing AI Compute, Part 6) Modverse #46: MAX 25.1, MAX Builds, and Democratizing AI Compute CUDA is the incumbent, but is it any good? (Democratizing AI Compute, Part 4) MAX 25.1 - Introducing MAX Builds How did CUDA succeed? (Democratizing AI Compute, Part 3) Paged Attention & Prefix Caching Now Available in MAX Serve What exactly is “CUDA”? (Democratizing AI Compute, Part 2) Modular DeepSeek's Impact on AI (Democratizing AI Compute, Part 1) Modular Hands-on with Mojo 24.6 Evaluating Llama Guard with MAX 24.6 and Hugging Face Modular Introducing MAX 24.6: A GPU Native Generative AI Platform MAX GPU: State of the Art Throughput on a New GenAI platform Understanding SIMD: Infinite Complexity of Trivial Problems Community Spotlight: Writing Mojo with Cursor Hands-on with Mojo 24.5 MAX 24.5 - With SOTA CPU Performance for Llama 3.1 Announcing stack-pr: an open source tool for managing stacked PRs on GitHub Debugging in Mojo🔥 Write hardware-agnostic custom ops for PyTorch | Modular Take control of your AI Develop locally, deploy globally A brief guide to the Mojo n-body example What's new in MAX 24.4? MAX on macOS, fast local Llama3, native quantization and GGUF support What’s new in Mojo 24.4? Improved collections, new traits, os module features and core language enhancements MAX 24.4 - Introducing quantization APIs and MAX on macOS Deep dive into ownership in Mojo What ownership is really about: a mental model approach Fast⚡k-means clustering in Mojo🔥: a guide to porting Python to Mojo🔥 for accelerated k-means clustering
Exploring Metaprogramming in Mojo
No items found. · 2025-05-27 · via Modular Blog

May 27, 2025

Brian Grenier

This guest post is by Brian Grenier, a C++ developer working in cardiac image processing and an active contributor to the Mojo standard library. He maintains two open-source Mojo libraries: EmberJSON, a pure Mojo JSON parser, and Kelvin, a type-safe dimensional analysis toolkit. You can often find him sharing insights and answering questions in the Modular forum and Discord server (@eggsquad).

Mojo's parameter system is one of its most powerful and expressive features. In this post, I will take a closer look at a focused subset of Mojo's metaprogramming capabilities. For a breadth-first introduction to these features, please refer to the official Modular documentation.

Many of this article's examples include snippets of LLVM-IR to help illustrate what the Mojo compiler is doing under the hood. If you are not familiar with LLVM-IR, you can find a helpful language reference here.

Parameters for compile-time calculations

Metaprogramming is a technique widely used in many programming languages where we write code that modifies itself or other code. Python does this dynamically at runtime, whereas compiled languages like Rust, C++, and Mojo provide utilities for directing the compiler to generate and modify code at compile time. If you are already familiar with Rust generics or C++ templates, the Mojo parameter system will likely feel familiar.

A common use case for metaprogramming is creating generic functions and types; Python can do this implicitly via duck typing.  The Python interpreter is generally unconcerned with the type of a variable until it comes time to perform some operation on it, so as long as the type you provide to a function satisfies those definitions, everything will move along just fine.

Mojo

def doubler(a): return a + a b = doubler(2) c = doubler(1.245)

Without using generics, statically typed languages like Mojo need to rely on creating multiple definitions of that function in order to handle different input types:

Mojo

fn doubler(i: Int) -> Int: return i + i fn doubler(s: Float64) -> Float64: return s + s var a = doubler(2) var b = doubler(20.0)

We can solve the problem of duplicating functions like this by using generics, which in Mojo are implemented as part of the parameter system. In Mojo, the set of expected parameters for a struct or function is defined using a square bracket-enclosed list. All parameters must be types or expressions that are known at compile time. Mojo uses traits to inform the compiler about the kinds of types that a type parameter can bind to. Using traits, we can create an Addable trait like so:

Mojo

trait Addable: fn __add__(self, other: Self) -> Self: ...

With this in place, we can write a new doubler function that operates on both of these types by telling the compiler, "This function operates on all types that have the Addable trait and as a result, implement the __add__ method."

Mojo

@no_inline fn doubler[T: Addable](a: T) -> T: return a + a

By diving into the LLVM-IR output from the Mojo compiler, we can see that the compiler has done the work for us of generating a different function definition for each type:

LLVM

; Function Attrs: noinline define internal i64 "playground::doubler[playground::Addable]..."(i64 noundef %0) #1 { %2 = add i64 %0, %0 ret i64 %2 } ; Function Attrs: noinline define internal double @"playground::doubler[playground::Addable]..."(double noundef %0) #1 { %2 = fadd contract double %0, %0 ret double %2 }

Function parameters vs. arguments

Before we dive deeper, how is a “parameter” different from an “argument”? Mojo allows functions to define two different kinds of inputs: one category that must be known at compile time when a function is instantiated, and one that is allowed to be unknown until the function is called at runtime. We need words to reason about this so we say that “parameters” are compile time inputs to a function - you might say that functions can be “parameterized” - and that arguments are the runtime inputs to the function.

For example, this function takes width as a parameter which must be known at compile time.  It is used in the type of arg1 and arg2 (which are known at runtime), and supports math like multiply:

Mojo

fn take_floats[width: Int](arg1: SIMD[DType.float16, width], arg2: SIMD[DType.float16, width*2]): constrained[width > 4]() ... def main(): take_floats(SIMD[DType.float16, 1](), SIMD[DType.float16, 2]())) # compile error, width is not > 4

Parameter values are always immutable, and can be further used in other compile-time expressions. Note the use of width in a comparison expression against 4 in the constrained function call. constrained allows us to make compile-time assertions (like static_assert in other languages). In this case, we arbitrarily assert that the value of width is always greater than four.

Parameter values can also "hop the fence" into the runtime domain–observe how the constant 30 has been dropped directly into the multiplication expression in the IR output:

Mojo

@no_inline fn multiplier[m: Int](a: Int) -> Int: return m * a # "m" is a compile time value used at runtime. def main(): print(multiplier[30](2))

LLVM

define internal i64 @"playground::multiplier[::Int](::Int),m=30"(i64 noundef %0) #1 { %2 = mul i64 %0, 30 ret i64 %2 }

The alias keyword

Mojo provides a very powerful alias keyword that allows us to create new names for types, as well as define compile-time constant values. In the latter case, alias functions as the compile-time equivalent of var for runtime variables. Since values defined using alias are known at compile time, they can also be used in a parameter list:

Mojo

alias IntList = List[Int] alias MyConstant = 5 multiplier[MyConstant](10)

Looking at the IR output, we can see the value of MyConstants has been dropped directly into the expression:

LLVM

define internal i64 @"playground::multiplier[::Int](::Int),m=5"(i64 noundef %0) #1 { %2 = mul i64 %0, 5 ; <- The input value being multiplied by our constant 5 ret i64 %2 }

Thanks to Mojo's compile-time interpreter, you can use alias to precompute non-trivial expressions as well, which enables offloading runtime compute to the compiler:

Mojo

fn fib(n: Int) -> Int: if n <= 1: return n return fib(n - 1) + fib(n - 2) fn main(): alias f = fib(30) print(f)

And just like that, this function call has been folded into a store instruction:

LLVM

define dso_local i32 @main(i32 noundef %0, ptr noundef %1) #0 { %3 = alloca i64, i64 1, align 8 ... store i64 832040, ptr %3, align 8 ; <- the output of fib(30) ... }

When using alias , Mojo guarantees the expression will be completely compile-time folded. Any instructions that are not compile-time computable (such as special hardware intrinsics) will result in compilation failure.

You can even create an alias to a parameterized function, referring back to our multiplier function–we can create a function that always multiplies the input by two:

Mojo

alias doubler = multiplier[2] def main(): print(doubler(2))

The compiler is also sometimes capable of entirely eliding the runtime materialization of a compile-time variable. For instance, if you create an alias to an InlineArray and index it using a compile-time-known value, the compiler will simply replace that array access with the appropriate value:

Mojo

alias l = InlineArray[Int, 3](45, 34, 32) @no_inline fn get_val() -> Int: return l[1] fn main(): print(get_val())

LLVM

define internal i64 @"playground::get_val()"() #1 { ret i64 34 }

Compile-time branching

Through the power of parameter values, we can even make branching decisions at compile time by using @parameter if statements:

Mojo

@no_inline fn foo[n: Int]() -> String: @parameter if n < 0: return "Negative number" else: return "Positive number" fn main(): print(foo[1]()) print(foo[-1]())

We can see in the IR that the if statement has been folded into a constant value branch. Mojo guarantees unreachable code is not compiled into the final binary. So this is effectively equivalent to an #ifdef expression in C/C++, but much more powerful, including the added benefit of both branches being type checked. Meaning that developers writing code that targets multiple devices, such as GPUs and CPUs, have type safety through the entire codebase. Regardless of which platform they are currently compiling for:

LLVM

define internal { ptr, i64, i64 } @"playground::foo[::Int](),n=-1"() #1 { ... br i1 true, label %6, label %9 ...

More recently, the is_compile_time function was added to the Mojo standard library, which allows invoking different behavior at compile time and runtime:

Mojo

@no_inline fn foo() -> String: if is_compile_time(): return "CTime" else: return "Runtime" fn main(): alias s = foo() print(s) print(foo())

This is generally useful to provide fallback implementations of functions that would otherwise invoke platform-specific instructions which cannot be handled by the MLIR interpreter, or other expressions that don't have meaning at compile time, such as this example from the Mojo standard library:

LLVM

@always_inline("nodebug") fn assume(val: Bool): """Signals to the optimizer that the condition is always true. This allows the optimizer to optimize the code. Args: val: The input value which is assumed to be `True`. """ if is_compile_time(): return llvm_intrinsic["llvm.assume", NoneType](val)

In this case, the interpreter would throw an error upon trying to evaluate llvm_intrinsic["llvm.assume", NoneType](val), so we ignore it completely in that case.

Loop unrolling

Loop unrolling is an optimization technique used by compilers to save the overhead of incrementing loop variables and handling branch instructions. In performance-critical applications, this is even done manually by the programmer. In Mojo, we can ensure that the compiler always unrolls a particular loop using the @parameter decorator:

Mojo

var a = 0 var l = List(1, 2, 3) for i in range(3): a += l[i] # manually unrolled version var a = 0 var l = List(1, 2, 3) a += l[0] a += l[1] a += l[2] # @parameter for var a = 0 var l = List(1, 2, 3) @parameter for i in range(3): a += l[i]

In the IR, we can see the loop has been inlined:

LLVM

%35 = load i64, ptr %19, align 8 %36 = load i64, ptr %6, align 8 %37 = add i64 %36, %35 ; <- a += l[0] store i64 %37, ptr %6, align 8 %38 = getelementptr inbounds i64, ptr %19, i32 1 %39 = load i64, ptr %38, align 8 %40 = load i64, ptr %6, align 8 %41 = add i64 %40, %39 ; <- a += l[1] store i64 %41, ptr %6, align 8 %42 = getelementptr inbounds i64, ptr %19, i32 2 %43 = load i64, ptr %42, align 8 %44 = load i64, ptr %6, align 8 %45 = add i64 %44, %43 ; <- a += l[2] store i64 %45, ptr %6, align 8

Dependent types

As the name suggests, "dependent types" are types whose definition depends on a value. For a concrete example, let’s take a look at a struct backed by Int:

Mojo

@value @register_passable("trivial") struct Adder[I: Int]: @always_inline('builtin') fn __init__(out self): pass @always_inline('builtin') fn __add__[OI: Int, //](self, other: Adder[OI]) -> Adder[I + OI]: return {}

Adder takes a value parameter I ; therefore, the particular value of I is now a part of the type definition of Adder . Note in the definition of Adder.__add___() that we use this property to enforce the compile-time evaluation of the addition by encoding it into a type system transformation. The @always_inline('builtin') decorator is used to ensure that the method body can be fully compile-time folded so expressions like Adder[1]() + Adder[3]() are in this case exactly equal to Adder[4](). Refer to the feature proposal for a more in depth explanation.

Encoding information in this way is also useful for enforcing that a function argument is parameterized with a particular value:

Mojo

fn must_add_two[AI: Int, //](a: Adder[AI], b: Adder[2]) -> Adder[AI + 2]: return a + b fn main(): print(must_add_two(Adder[10](), Adder[2]()).I) # print(must_add_two(Adder[10](), Adder[3]()).I) # Compilation error

Since the addition operation has been moved into the type system, the resulting value I is simply materialized into a runtime store instruction of the value 12:

LLVM

store i64 12, ptr %3, align 8

Representing complex information in the type system

To show a more complex example of what we can represent in a parameter, we're going to dive into a library I wrote called Kelvin. It is a type-safe dimensional analysis library in which all the safety checks are done at compile time to ensure minimal performance overhead over raw numbers. Let's explore how we can represent "10 meters per second" in code.

First, we need a definition for a physical "dimension" – here we define a struct Dimension that has 3 parameters:

  1. Z: The value of the dimension, eg. the value of Z for the length dimension of area would be 2
  2. R: The scale of the dimension. This is used to differentiate between different scales of the same unit, eg. Meters vs Kilometers
  3. suffix: The string suffix for the value, e.g. km, K, mol, etc

We also have a distinct Angle struct since angle isn't a true dimension and behaves differently:

Mojo

struct Dimension[Z: IntLiteral, R: Ratio, suffix: String](...): ... struct Angle[R: Ratio, suffix: String](./..): ...

The Mojo parameter system allows us to write functions where the type of the return value is dependent on the function inputs. This allows us to build the following example where we define methods for Dimension that subtract the Z value of an input dimension, and return a new dimension type parameterized:

Mojo

@always_inline("builtin") fn __sub__( self, other: Dimension ) -> Dimension[Z - other.Z, R | other.R, suffix or other.suffix]: return {}

This behavior empowers us to do some cool stuff later on.

As another example, there are 7 dimensions in the SI unit system, so we can compose 7 dimension units plus an angle unit to create the Dimensions of a value:

Mojo

struct Dimensions[ L: Dimension, # Length dimension. M: Dimension, # Mass dimension. T: Dimension, # Time dimension. EC: Dimension, # Electric current dimension. TH: Dimension, # Temperature dimension. A: Dimension, # Substance amount dimension. CD: Dimension, # Luminosity dimension. Ang: Angle, # The angle component of the quantity. ](...): ...

Now we can define the result of dividing dimensions using the __sub__ method we just created. Units that live in the denominator are defined as negative Z values, e.g. m / s is represented by m^1 s^-1:

Mojo

@always_inline("builtin") fn __truediv__( self, other: Dimensions ) -> Dimensions[ L - other.L, M - other.M, T - other.T, EC - other.EC, TH - other.TH, A - other.A, CD - other.CD, Ang.pick_non_null(other.Ang), ]: return {}

With that plumbing in place, we can define a Quantity struct that represents our actual value:

Mojo

struct Quantity[D: Dimensions, DT: DType = DType.float64, Width: UInt = 1](...) alias ValueType = SIMD[DT, Width] var _value: Self.ValueType ...

Now we can define particular units easily using alias:

Mojo

alias Meter = Quantity[ Dimensions[ Dimension[1, Ratio.Unitary, "m"](), Dimension.Invalid, Dimension.Invalid, Dimension.Invalid, Dimension.Invalid, Dimension.Invalid, Dimension.Invalid, Angle.Invalid, ](), _, _, ] alias Second = Quantity[ Dimensions[ Dimension.Invalid, Dimension.Invalid, Dimension[1, Ratio.Unitary, "s"](), Dimension.Invalid, Dimension.Invalid, Dimension.Invalid, Dimension.Invalid, Angle.Invalid, ](), _, _, ]

Since we've defined a Dimensions / Dimensions operation, we can now define derivative units such as velocity in a single line of code:

Mojo

alias MetersPerSecond = Quantity[Meter.D / Second.D, _, _]

Using the power of compile-time programming, we can now define operations on Quantity that respect the rules of the SI unit system, which are all verified at compile time. Starting with a simple example (since addition/subtraction only makes sense between the quantity of matching dimensions), we can use the special Self type to ensure that self and other are the exact same type:

Mojo

@always_inline fn __add__(self, other: Self) -> Self: return Self(self._value + other._value) var s1 = Second(10) var s2 = Second(30) var s3 = s1 + s2 # All good var bad = s1 + Meter(20) # compiler error

Things get a bit more complex for multiplication and division since those operations are allowed on non-matching units, and the return type is dependent on those input types - we'll take division as our example here:

Mojo

@always_inline fn __truediv__[ OD: Dimensions ](self, other: Quantity[OD, DT, Width]) -> Quantity[D / OD, DT, Width]: _dimension_scale_check[D, OD]() return {self._value / other._value}

Here at compile-time, we have a static assertion that the dimensions of the two types have matching scale, e.g. meters per second, and miles per hour have matching dimensions, but are incompatible due to differences in unit scale. In the output argument, we compute the derivative dimensions using D / OD; therefore, the output of the method is the exact type we would expect from this operation:

Mojo

var s = Second(10) var m = Meter(20) var v: MetersPerSecond = m / s

Inspecting the IR, we can see all this parameter complexity has been resolved, and all that remains is a simple division operation:

LLVM

%3 = fdiv contract double %1, %0

Using this power responsibly

Understanding exactly how these abstractions work is critical to using them effectively; for instance, using a @parameter for loop will always result in the entire loop being unrolled, potentially resulting in binary bloat and negative performance implications:

LLVM

@parameter for i in range(1000000): print(i)

Wrapping up

Mojo offers powerful tools for executing code seamlessly at compile time, allowing programmers to build powerful abstractions into the type system. This completely removes the associated complexity from runtime, and progress is continuously made (such as parameteric aliases).

I hope you found this article helpful in understanding how to use these features, and feel free to reach out on Discord (@eggsquad) or the Modular forum (@bgreni) if you have any questions! Head over to my GitHub (@bgreni) to follow my progress on Kelvin and other Mojo libraries I work on.

  • Person with blonde hair using a laptop with an Apple logo.

    Sign up today

    Signup to our Cloud Platform today to get started easily.

    Sign Up

  • Magnifying glass emoji with black handle and round clear lens.

    Browse open models

    Browse our model catalog, or deploy your own custom model

    Browse models

Sign up for our newsletter

Get all our latest news, announcements and updates delivered directly to your inbox. Unsubscribe at anytime.

Thanks for signing up to our newsletter! 🚀

Thank you,

Modular Sales Team

Oops! Something went wrong while submitting the form.