Skip to main content

mpk-internals

Reference guide for the MPK compilation-to-runtime pipeline. Use when asked how MPK works internally, how compilation/code generation works, what happens at runtime, or when debugging the megakernel scheduler.

Ir a la instalación

Datos de origen

Repositorio
mirage-project/mirage
Última actividad en el origen
18 de abril de 2026 a las 01:21
Idioma detectado de SKILL.md
inglés
Estrellas
2505
Forks
256

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
mpk-internals
description
Reference guide for the MPK compilation-to-runtime pipeline. Use when asked how MPK works internally, how compilation/code generation works, what happens at runtime, or when debugging the megakernel scheduler.
# MPK Internals: Compilation-to-Runtime Pipeline This document traces the full lifecycle of an MPK megakernel from Python graph construction through CUDA compilation to persistent kernel execution. ## Pipeline Overview ``` Phase 1: Python Graph Building PersistentKernel.compile() → layer methods build KNGraph/TBGraph → kn_graph.generate_task_graph() | v Phase 2: C++ Code Generation (runtime.cc) Graph::generate_task_graph() → register_mugraph() — builds task/event lists → print_task_graph() — emits CUDA code + JSON | v Two artifacts: test.cu — _init_persistent_kernel(), _execute_task(), Python C ext task_graph.json — task descriptors, events, dependencies | v Phase 3: CUDA Compilation nvcc test.cu → test.so (Python extension module: __mirage_launcher) | v Phase 4: Runtime Initialization init_persistent_kernel() → loads JSON, allocates GPU queues, builds RuntimeConfig | v Phase 5: Runtime Execution launch_persistent_kernel() → prepare_kernel (reset queues) → worker_kernel + scheduler_kernel (persistent loop) → workers fetch tasks, wait on events, call _execute_task() → schedulers process events, enqueue tasks to workers ``` --- ## Phase 1: Python Graph Building ### Key file: `python/mirage/mpk/persistent_kernel.py` **Entry point:** `PersistentKernel.compile()` The compilation method does the following in order: 1. **Generate task graph** — calls `self.kn_graph.generate_task_graph(num_gpus, my_gpu_id)` which bridges through Cython (`python/mirage/_cython/core.pyx`, `generate_task_graph()`) into C++. Returns `{"cuda_code": str, "json_file": str}`. 2. **Write files** — writes `test.cu` (CUDA code + HARD_CODE Python extension wrapper) and `task_graph.json` to a temp directory. 3. **Compile** — builds the nvcc command via `get_compile_command()` and calls `subprocess.check_call()`. 4. **Load module** — uses `importlib.util.spec_from_file_location()` to dynamically load the compiled `.so` as Python module `__mirage_launcher`. Extracts `init_func`, `launch_func`, `init_request_func`, `finalize_func`. 5. **Initialize runtime** — calls `init_func(...)` with meta-tensor pointers, worker/scheduler counts, and serving config. ### How layers build the graph Each layer method (e.g., `rmsnorm_layer`, `linear_layer`, `moe_w13_fp8_layer`) does: 1. Create a `TBGraph` with `CyTBGraph(grid_dim, block_dim, forloop_range, reduction_dimx)` 2. Call `tb_graph.new_input(dtensor, partition, forloop_dim, store_in_dmem)` for each input and output 3. Call `self.kn_graph.customized([tensors...], tb_graph)` to register the operator 4. Call `self.kn_graph.register_task(tb_graph, "task_name")` which dispatches to C++ `Graph::register_task()` ### HARD_CODE: the Python C extension wrapper The `HARD_CODE` constant (top of `persistent_kernel.py`) is a C string appended to the generated CUDA code. It defines a Python extension module with four functions: - `init_func` — parses Python args, calls C++ `init_persistent_kernel()` - `launch_func` — takes a CUDA stream pointer, calls `launch_persistent_kernel(stream)` - `init_request_func` — calls `init_request_resources()` (for online serving) - `finalize_func` — calls `finalize_persistent_kernel()` --- ## Layer API: TBGraph Partition Scheme Each layer method (e.g., `rmsnorm_layer`, `linear_layer`, `moe_w13_linear_layer`) builds a **TBGraph** that describes how the global tensors are sliced into per-task tiles. This section explains every parameter. ### `CyTBGraph` constructor ```python tb_graph = TBGraph(CyTBGraph(grid_dim, block_dim, forloop_range, reduction_dimx)) ``` | Parameter | Meaning | |---|---| | `grid_dim` | `(x, y, z)` — number of task instances in each dimension. Total tasks = `x * y * z`. | | `block_dim` | `(threads, 1, 1)` — threads per task. Must be `(128,1,1)` Ampere, `(256,1,1)` Hopper/Blackwell. | | `forloop_range` | Number of forloop iterations (always **1** in MPK — see note below). | | `reduction_dimx` | Tile size for the reduction dimension (always **64** in MPK). | ### `tb_graph.new_input()` — registering a tensor ```python tb_graph.new_input(dtensor, input_map, forloop_dim, store_in_dmem) ``` Called for **every** tensor the task touches — both inputs and outputs. The first `num_inputs` calls register inputs; the remaining register outputs. This ordering must match `num_inputs`/`num_outputs` in `graph.cc`'s `task_config` tuple. #### `input_map`: the partition tuple A 3-element tuple `(mx, my, mz)` that maps **grid dimensions → tensor dimensions**: | `input_map.x` value | Meaning | |---|---| | `-1` | `grid_dim.x` does **not** partition this tensor. Every task sees the full extent of every dimension. | | `0` | `grid_dim.x` partitions **tensor dimension 0**. Task at grid position `gx` sees the slice `[gx * dim[0]/grid_x : (gx+1) * dim[0]/grid_x]` along dim 0. | | `1` | `grid_dim.x` partitions **tensor dimension 1**. Same slicing logic on dim 1. | | `2` | `grid_dim.x` partitions **tensor dimension 2**. | `input_map.y` and `input_map.z` work identically for `grid_dim.y` and `grid_dim.z`. **In short:** the value tells you *which tensor dimension* that grid axis splits. `-1` means "don't split by this grid axis." #### `forloop_dim` (vestigial in MPK) In the Mirage superoptimizer, `forloop_dim` and `forloop_range` together control tiled reduction loops within a TBGraph. However, **in MPK `forloop_range` is always 1**, which makes `forloop_dim` a no-op — the dimension division (`dim / 1`) and stride multiplier (`* 1`) have no effect regardless of what value you pass. MPK task kernels handle their own internal tiling and reduction directly in CUDA (e.g., looping over the K dimension in a matmul). The TBGraph forloop mechanism is not used. You'll see various `forloop_dim` values in existing layer methods (e.g., `1`, `2`, `-1`), but they're all equivalent when `forloop_range=1`. By convention, existing code sets `forloop_dim` to the "reduction dimension" of the operation, but this is cosmetic. #### `store_in_dmem` - `True` — the per-task tensor slice lives in **device (global) memory**. Should be set to **True** for all MPK tensors. ### Annotated example: `moe_w13_linear_layer` ```python def moe_w13_linear_layer(self, input, weight, moe_routing_indices, moe_mask, output, grid_dim, block_dim): # input: (batch_size, hidden_size) 2D bf16 # weight: (num_experts, 2*intermediate_size, hidden_size) 3D bf16 # moe_routing_indices:(num_experts, batch_size) 2D int32 # moe_mask: (num_experts + 1,) 1D int32 # output: (batch_size, num_experts_per_tok, 2*inter_size) 3D bf16 tb_graph = TBGraph(CyTBGraph(grid_dim, block_dim, 1, 64)) # tensor, input_map, forloop_dim*, store_in_dmem # (* forloop_dim is vestigial in MPK — has no effect when forloop_range=1) tb_graph.new_input(input, (-1, -1, -1), 1, True) # → No partition on any grid axis. Every task sees full (batch, hidden). tb_graph.new_input(weight, (-1, 1, -1), 2, True) # → grid_dim.y partitions dim 1 (the 2*intermediate_size axis). # Each task handles 2*inter_size / grid_dim.y rows of the weight matrix. tb_graph.new_input(moe_routing_indices, (-1, -1, -1), -1, True) # → No partition. Every task sees the full routing table. tb_graph.new_input(moe_mask, (-1, -1, -1), -1, True) # → No partition. Every task sees the full mask. tb_graph.new_input(output, (-1, 2, -1), -1, True) # → grid_dim.z partitions dim 2 (the 2*intermediate_size axis of the output). # Each task writes to its slice of output columns. self.kn_graph.customized([input, weight, moe_routing_indices, moe_mask, output], tb_graph) self.kn_graph.register_task(tb_graph, "moe_w13_linear_sm100") ``` ### How partitioning connects to task pointers At runtime, the partition tuple is resolved during task graph generation (`src/threadblock/graph.cc`). For each task instance (one grid coordinate), the code generator computes a **byte offset** from the tensor's base pointer: ``` per_task_ptr = base_ptr + blockIdx.x * stride_for(input_map.x) + blockIdx.y * stride_for(input_map.y) + blockIdx.z * stride_for(input_map.z) ``` These offsets are baked into the `TaskDesc` at init time (via JSON → `FullTaskDesc` → `TaskDesc`). The task kernel receives pre-offset pointers in `task_desc->input_ptrs[i]` and `task_desc->output_ptrs[i]` — this is why tasks are **blockIdx-agnostic**. --- ## Phase 2: C++ Code Generation ### Key file: `src/kernel/runtime.cc` **Entry point:** `Graph::generate_task_graph()` This function orchestrates all code generation: 1. **`register_mugraph()`** — walks the KNGraph operators and converts each into `FullTaskDesc` entries. For each `KN_CUSTOMIZED_OP`, it queries `task_config[op]` (a tuple of `num_inputs, num_outputs, TaskType, variant_id` set by `Graph::register_task()`) to determine the task type and variant. It also creates `EventDesc` entries for inter-task dependencies and populates `first_tasks` (the initial ready tasks). 2. **`print_task_graph()`** — generates two outputs: **Output 1: CUDA code** containing three generated functions: - `construct_task_graph()` — loads `task_graph.json` at runtime, parses it into `FullTaskDesc`/`EventDesc` vectors, and creates TMA descriptors for Hopper/Blackwell tasks. - `_init_persistent_kernel()` — sets up tensor pointers from `io_configs` (torch tensors, cudaMalloc buffers, shuffled tensors, NVSHMEM buffers). Called once during initialization. - `_execute_task()` — a giant if/else dispatcher that maps `(task_type, variant_id)` pairs to the actual kernel function calls. Each branch contains the code string generated by the corresponding `TaskRegister::register_*_task()` function. **Output 2: JSON task graph** — serializes all tasks, events, and dependencies (see JSON Schema section below). ### Key file: `src/kernel/graph.cc` **`Graph::register_task()`** maps task name strings to registration functions: ``` "moe_w13_fp8_sm100" → register_moe_fp8_sm100_task() → TASK_MOE_W13_FP8_SM100 ``` Each registration function (in `src/kernel/task_register.cc`) reads tensor dimensions from the TBGraph, generates a CUDA code string calling the templated kernel with those dimensions, and returns a `variant_id` via `register_task_variant()`. Same code string → same variant_id (deduplication). --- ## Phase 3: CUDA Compilation ### Key function: `get_compile_command()` in `persistent_kernel.py` Builds the nvcc command with: - **Includes**: Python headers, Mirage headers, CUTLASS, JSON library - **Architecture flags**: `-gencode=arch=compute_90a,code=sm_90a` (Hopper), `compute_100a,code=sm_100a` (Blackwell) - **Feature defines**: `-DMPK_ENABLE_TMA` (Hopper/Blackwell), `-DMIRAGE_GRACE_HOPPER` or `-DMIRAGE_GRACE_BLACKWELL` - **Runtime defines**: `-DMODE_OFFLINE`, `-DMPK_MAX_NUM_BATCHED_REQUESTS=N`, `-DMPK_MAX_NUM_BATCHED_TOKENS=N`, `-DMPK_MAX_NUM_PAGES=N`, `-DMPK_PAGE_SIZE=N`, `-DMPK_MAX_SEQ_LENGTH=N` - **Scheduler config**: `-DMAX_WORKER_PER_SCHEDULER=N` (computed from worker/scheduler ratio) - **Output**: shared library (`.so`) as a Python extension module For multi-GPU (NVSHMEM): adds `-rdc=true`, NVSHMEM/MPI includes and libraries. --- ## Phase 4: Runtime Initialization ### Key file: `include/mirage/persistent_kernel/persistent_kernel.cuh` **`init_persistent_kernel()`** sets up the full runtime state: 1. **Meta-tensor mapping** — stores 10 meta-tensor pointers in `global_runtime_config` (step, tokens, input_tokens, output_tokens, num_new_tokens, prompt_lengths, qo_indptr, paged_kv_indptr, paged_kv_indices, paged_kv_last_page_len). 2. **NVSHMEM init** (if multi-GPU) — calls `nvshmemx_init_attr()`, creates NVSHMEM teams for cross-GPU communication. 3. **Call generated `_init_persistent_kernel()`** — this loads the JSON task graph via `construct_task_graph()`, allocates GPU memory for intermediate tensors, and populates the `all_tasks`, `all_events`, `first_tasks` vectors. 4. **Allocate runtime queues on GPU**:
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub