Programming Objects

A PXL host application can use the typed Launcher interface or manage the runtime objects explicitly. Launcher is the shorter path for single-source __pxl_kernel__ programs. The explicit object path gives the application direct control over Context, Job, Module, Function, and Map lifetimes.

Typed Launcher path

__pxl_kernel__ function  →  Launcher::execute() / stage()
                                      └─ tasks() → run() / runAsync()

Explicit object path

pxl::createContext()  →  Context
        └─ createJob()           →  Job
                  └─ load(module) / buildMap(...)

pxl::createModule(path)  →  Module
        └─ createFunction(name)  →  Function

Both paths use the same task-distribution and argument model. See Kernel Execution for the Launcher and Map execution flows. Stream is covered in Streams.


Launcher

pxl::Launcher is a high-level, typed interface for kernels compiled with pxcc. The host names a kernel by its C++ symbol and passes the original device pointer or pxl::NDArray; Launcher selects the device from those arguments and configures the underlying execution objects.

__pxl_kernel__ void add_one(int* data)
{
    data[mu::getTaskIdx()] += 1;
}

auto result = pxl::Launcher()
                  .execute<add_one>(data)
                  .tasks(taskCount)
                  .run();

execute() starts a single-kernel request. stage<kernel>() adds a sequential kernel; stage() without arguments opens a parallel group whose add<kernel>() calls add kernels to that group. See the sequential and parallel examples. With the request-scoped form shown above, each kernel entry must receive its task count through .tasks(taskCount) before it can run or continue to the next kernel. Launcher and its builders are ordinary C++ values and require no explicit destruction.

Use the explicit object API instead when the application needs to load a standalone .mubin, retain and reuse a Job or Map, or directly control Map lifecycle operations. See Kernel Execution for task configuration, directional pointer arguments, NDArray slicing, and multi-kernel execution.


Context

A Context is an execution session bound to a device. It owns device memory and is the factory for Jobs.

Responsibilities:

  • Create and destroy Jobs.
  • Allocate and free device memory.
  • Query memory information of a pointer.
auto ctx = pxl::createContext();              // first available device
auto ctx0 = pxl::createContext(deviceId);     // specific device

// Device memory
auto ptr = ctx->memAlloc(size);
ctx->memFree(ptr);

// Job creation
auto job = ctx->createJob(numSub);

pxl::destroyContext(ctx);

Reuse a single Context for the lifetime of your application. Allocating device memory once at startup and reusing it across kernel launches avoids re-mapping device addresses on every launch, which would otherwise add latency to each call.


Job

A Job is a reservation of one or more Sub units. It loads a kernel binary and creates Map objects that run on those Subs.

Responsibilities:

  • Hold a set of Subs (allocate more or release some at runtime).
  • Load a Module so the kernel binary lives on the Job’s Subs.
  • Build Map objects that target functions in the loaded module.
  • Own a private default Stream used by every Map that does not bind its own.
auto job = ctx->createJob(4);          // 4 Subs
job->load("mu_kernel.mubin");          // load by path
// or: job->load(module);              // load a pre-created Module

job->subAlloc(2);                      // request 2 more Subs
auto subIds = job->subIdList();        // inspect what was assigned

auto map = job->buildMap("my_kernel", taskCount);

ctx->destroyJob(job);

Destroying a Job synchronously drains every Map built from it, so it is safe to call even when asynchronous executions are still in flight.


Module and Function

A Module is a compiled MU kernel binary. A Function is a single host-callable entry point inside that module.

Module and Function

auto module = pxl::createModule("mu_kernel.mubin");

auto sortFunc   = module->createFunction("bubbleSort");
auto searchFunc = module->createFunction("binarySearch");

// Inspect the module
auto names = module->getMuFunctionList();
auto count = module->getNumMuFunctions();

pxl::destroyModule(module);

A Module can be loaded into a Job with job->load(module). The Job’s Subs then hold the binary, so buildMap(funcName, ...) can resolve function names against it.


Lifecycle Summary

Launcher and its builders have automatic C++ lifetimes. The table below covers the explicit runtime objects that an application may retain and reuse.

Object Created by Destroyed by Owns
Context pxl::createContext() pxl::destroyContext() device memory, Jobs
Job Context::createJob() Context::destroyJob() Subs, default Stream, Maps
Module pxl::createModule() pxl::destroyModule() binary image, Functions
Function Module::createFunction() Module::destroyFunction() (handle only)
Map Job::buildMap() Job::destroyMap() argument bindings, stream binding

Destroying a parent object releases its children, so you typically only need to destroy the top-level objects (Context, Module). Explicit child destruction is supported for cases where you want to free resources earlier.


→ Related: Kernel Execution, Streams