Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
9ca1889
Add `ExternalKernel` support for JIT
mawad-amd Aug 5, 2025
058ad42
Add tests
mawad-amd Aug 5, 2025
f5c7fe4
Add tesnor `fill_` test
mawad-amd Aug 5, 2025
6d36175
Update test name
mawad-amd Aug 5, 2025
136a5a7
Rename jit test files
mawad-amd Aug 5, 2025
e29af78
Fix compiler flags for NPU2
mawad-amd Aug 5, 2025
def58f0
Use define flags instead of debug
mawad-amd Aug 5, 2025
de7e523
Remove stray compiler flags
mawad-amd Aug 5, 2025
1c63fe5
Remove aie opt path addition
mawad-amd Aug 5, 2025
3f20500
Merge branch 'main' into muhaawad/extern-func
mawad-amd Aug 5, 2025
4feb272
Use existing `compile_cxx_core_function`
mawad-amd Aug 5, 2025
8926f75
Update python/iron/jit.py
mawad-amd Aug 5, 2025
37f61d3
Move import to the top
mawad-amd Aug 5, 2025
433aa64
Try debugging the workflow. Can't repro locally.
mawad-amd Aug 6, 2025
7877e21
Always trigger debug
mawad-amd Aug 6, 2025
f32061f
always trigger debug
mawad-amd Aug 6, 2025
e31859c
Run only broken test
mawad-amd Aug 6, 2025
3db23a9
Remove if
mawad-amd Aug 6, 2025
00193fd
Remove debug
mawad-amd Aug 6, 2025
4b75714
Manual debug
mawad-amd Aug 6, 2025
a1be75f
Debug
mawad-amd Aug 6, 2025
40ba192
Pass device
mawad-amd Aug 6, 2025
1611eff
Debug
mawad-amd Aug 6, 2025
ada8709
Fix install command
mawad-amd Aug 6, 2025
ebd4eed
Fix cmake command
mawad-amd Aug 6, 2025
7eb8f3b
Debug
mawad-amd Aug 6, 2025
7e861d5
Debug
mawad-amd Aug 6, 2025
44155c6
Debug
mawad-amd Aug 6, 2025
ec2a777
debug
mawad-amd Aug 6, 2025
c5c9692
Debug
mawad-amd Aug 6, 2025
5306493
Debug
mawad-amd Aug 6, 2025
ae45e4f
Add debugging statments
mawad-amd Aug 6, 2025
6689356
Include the device in the hash
mawad-amd Aug 6, 2025
0898a48
Merge branch 'main' into muhaawad/extern-func
mawad-amd Aug 6, 2025
561dbe3
Revert debug code
mawad-amd Aug 6, 2025
85b864e
Remove debug code and handke NPU2Col1 and NPU1Col1
mawad-amd Aug 6, 2025
4a22567
handle other device names
mawad-amd Aug 6, 2025
750d705
Replace `ExternalKernel` with `ExternalFunction`
mawad-amd Aug 6, 2025
2b4db11
Improve comments
mawad-amd Aug 6, 2025
3e7bd80
Merge branch 'main' into muhaawad/extern-func
mawad-amd Aug 7, 2025
10b49ea
Merge branch 'main' into muhaawad/extern-func
mawad-amd Aug 7, 2025
3cb0b9b
Merge branch 'main' into muhaawad/extern-func
mawad-amd Aug 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/buildAndTestRyzenAI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,4 @@ jobs:
ninja check-reference-designs
ninja check-programming-guide

popd
popd
2 changes: 1 addition & 1 deletion python/iron/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .globalbuffer import GlobalBuffer
from .kernel import Kernel
from .kernel import ExternalFunction, Kernel
from .localbuffer import LocalBuffer
from .program import Program
from .worker import Worker, WorkerRuntimeBarrier
Expand Down
14 changes: 12 additions & 2 deletions python/iron/compile/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def compile_cxx_core_function(
source_path: str,
target_arch: str,
output_path: str,
include_dirs=None,
compile_args=None,
cwd=None,
verbose=False,
Expand All @@ -28,8 +29,9 @@ def compile_cxx_core_function(
source_path (str): Path to C++ source.
target_arch (str): Target architecture, e.g., aie2.
output_path (str): Output object file path.
compile_args (list[str]): Compile arguments to peano.
cwd (str): Overrides the current working directory.
include_dirs (list[str], optional): List of include directories to add with -I.
compile_args (list[str], optional): Additional compile arguments to peano.
cwd (str, optional): Overrides the current working directory.
verbose (bool): Enable verbose output.
"""
cmd = [
Expand All @@ -48,8 +50,16 @@ def compile_cxx_core_function(
"-DNDEBUG",
f"--target={target_arch}-none-unknown-elf",
]

# Add include directories
if include_dirs:
for include_dir in include_dirs:
cmd.extend(["-I", include_dir])

# Add additional compile arguments
if compile_args:
cmd = cmd + compile_args

if verbose:
print("Executing:", " ".join(cmd))
ret = subprocess.run(
Expand Down
196 changes: 172 additions & 24 deletions python/iron/jit.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@
import hashlib
import numpy as np
import pyxrt as xrt
import shutil
import sys
import traceback

from aie.extras.context import mlir_mod_ctx
from ..utils.compile import compile_mlir_module_to_binary
from ..utils.xrt import read_insts_binary
from .device import NPU1, NPU2, NPU1Col1, NPU2Col1
from .config import get_current_device
from aie.dialects.aie import AIEDevice


# The `iron.jit` decorator below caches compiled kenrels inside the `IRON_CACHE_DIR` directory.
Expand Down Expand Up @@ -93,11 +99,15 @@ def __call__(self, *args):
kernel_args = []

for tensor in args:
# Skip callable arguments since these are inlined in the kernel
if callable(tensor):
continue
if not hasattr(tensor, "buffer_object"):
raise TypeError(
f"Expected Tensor with .buffer_object(), got {type(tensor)}"
)
kernel_args.append(tensor.buffer_object())

h = self.__kernel(opcode, self.__insts_buffer_bo, self.__n_insts, *kernel_args)
r = h.wait()
if r != xrt.ert_cmd_state.ERT_CMD_STATE_COMPLETED:
Expand Down Expand Up @@ -133,18 +143,61 @@ def jit(function=None, is_placed=True, use_cache=True):

@functools.wraps(function)
def decorator(*args, **kwargs):
if is_placed:
with mlir_mod_ctx() as ctx:
function(*args, **kwargs)
assert (
ctx.module.operation.verify()
), f"Verification failed for '{function.__name__}'"
mlir_module = ctx.module
else:
mlir_module = function(*args, **kwargs)
from .kernel import ExternalFunction

# Clear any instances from previous runs to make sure if the user provided any broken code we don't try to recompile it
ExternalFunction._instances.clear()

# Find ExternalFunction instances in arguments and kwargs
external_kernels = []
for arg in args:
if isinstance(arg, ExternalFunction):
external_kernels.append(arg)
for value in kwargs.values():
if isinstance(value, ExternalFunction):
external_kernels.append(value)

# Hash of the IR string
module_hash = hash_module(mlir_module)
# Execute the function to generate MLIR
try:
if is_placed:
with mlir_mod_ctx() as ctx:
function(*args, **kwargs)
assert (
ctx.module.operation.verify()
), f"Verification failed for '{function.__name__}'"
mlir_module = ctx.module
else:
mlir_module = function(*args, **kwargs)
except Exception as e:
raise

# Compile all ExternalFunction instances that were created during this JIT compilation
for func in ExternalFunction._instances:
if (
not hasattr(func, "_compiled") or not func._compiled
): # Don't compile if already compiled
external_kernels.append(func)

# Determine target architecture based on device type
try:
current_device = get_current_device()

# Determine target architecture based on device type
if isinstance(current_device, (NPU2, NPU2Col1)):
Comment thread
mawad-amd marked this conversation as resolved.
target_arch = "aie2p"
elif isinstance(current_device, (NPU1, NPU1Col1)):
target_arch = "aie2"
elif current_device in (AIEDevice.npu2, AIEDevice.npu2_1col):
target_arch = "aie2p"
elif current_device in (AIEDevice.npu1, AIEDevice.npu1_1col):
target_arch = "aie2"
else:
raise RuntimeError(f"Unsupported device type: {type(current_device)}")
except Exception as e:
raise

# Hash of the IR string, ExternalFunction compiler options, and target architecture
module_hash = hash_module(mlir_module, external_kernels, target_arch)
kernel_dir = os.path.join(IRON_CACHE_DIR, f"{module_hash}")
mlir_path = os.path.join(kernel_dir, "aie.mlir")

Expand All @@ -161,25 +214,120 @@ def decorator(*args, **kwargs):
inst_exists = os.path.exists(inst_path)

if not use_cache or not xclbin_exists or not inst_exists:
with open(mlir_path, "w", encoding="utf-8") as f:
print(mlir_module, file=f)
compile_mlir_module_to_binary(
mlir_module=mlir_module,
inst_path=inst_path,
xclbin_path=xclbin_path,
)
try:
with open(mlir_path, "w", encoding="utf-8") as f:
print(mlir_module, file=f)

# Compile ExternalFunctions from inside the JIT compilation directory
for func in external_kernels:
compile_external_kernel(func, kernel_dir, target_arch)

# Compile the MLIR module
compile_mlir_module_to_binary(
mlir_module=mlir_module,
inst_path=inst_path,
xclbin_path=xclbin_path,
work_dir=kernel_dir,
)
except Exception as e:
# Clean up cache directory on any compilation failure to avoid any corrupted objects in the cache
if os.path.exists(kernel_dir):
shutil.rmtree(kernel_dir)
Comment thread
mawad-amd marked this conversation as resolved.
raise e

kernel_name = "MLIR_AIE"
return NPUKernel(xclbin_path, inst_path, kernel_name=kernel_name)(
*args, **kwargs
)
try:
kernel = NPUKernel(xclbin_path, inst_path, kernel_name=kernel_name)
result = kernel(*args, **kwargs)
return result
except Exception as e:
raise

return decorator


def hash_module(module):
def compile_external_kernel(func, kernel_dir, target_arch):
"""
Hash the MLIR module to create a unique identifier.
Compile an ExternalFunction to an object file in the kernel directory.

Args:
func: ExternalFunction instance to compile
kernel_dir: Directory to place the compiled object file
target_arch: Target architecture (e.g., "aie2" or "aie2p")
"""
# Skip if already compiled
if hasattr(func, "_compiled") and func._compiled:
return

# Check if object file already exists in kernel directory
output_file = os.path.join(kernel_dir, func._object_file_name)
if os.path.exists(output_file):
return

# Create source file in kernel directory
source_file = os.path.join(kernel_dir, f"{func._name}.cc")

# Handle both source_string and source_file cases
if func._source_string is not None:
# Use source_string (write to file)
try:
with open(source_file, "w") as f:
Comment thread
mawad-amd marked this conversation as resolved.
f.write(func._source_string)
except Exception as e:
raise
elif func._source_file is not None:
# Use source_file (copy existing file)
# Check if source file exists before copying
if os.path.exists(func._source_file):
try:
shutil.copy2(func._source_file, source_file)
except Exception as e:
raise
else:
return
else:
raise ValueError("Neither source_string nor source_file is provided")

from .compile.compile import compile_cxx_core_function

try:
compile_cxx_core_function(
source_path=source_file,
target_arch=target_arch,
output_path=output_file,
include_dirs=func._include_dirs,
compile_args=func._compile_flags,
cwd=kernel_dir,
verbose=False,
)
except Exception as e:
raise

# Mark the function as compiled
func._compiled = True


def hash_module(module, external_kernels=None, target_arch=None):
"""
Hash the MLIR module and ExternalFunction compiler options to create a unique identifier.
"""
mlir_str = str(module)
return hashlib.sha256(mlir_str.encode("utf-8")).hexdigest()[:16]

# Include ExternalFunction compiler options in the hash
if external_kernels:
compiler_options = []
for func in external_kernels:
compiler_options.extend(func._include_dirs)
compiler_options.extend(func._compile_flags)

# Create a combined string for hashing
combined_str = mlir_str + "|" + "|".join(compiler_options)
else:
combined_str = mlir_str

# Include target architecture in the hash
if target_arch:
combined_str += f"|target_arch={target_arch}"

hash_result = hashlib.sha256(combined_str.encode("utf-8")).hexdigest()[:16]
return hash_result
Loading
Loading