KaiSpace
tech

多种代码范式详解

C++侧给python写接口

mirage/python/mirage/kernel.py

#include <Python.h>        // CPython C API,用来写 Python 扩展模块
#include <cuda_runtime.h>  // CUDA runtime API,主要用 cudaStream_t
#include <vector>          // std::vector,原代码最好补上这个 include

// Python 层调用的入口函数:__mirage_launcher.launch(...)
static PyObject *launch(PyObject *self, PyObject *args) {
  // Python 传进来的 5 个对象
  PyObject *input_list, *output_list, *py_buffer, *py_stream, *py_profiler_buffer;

  // C++/CUDA 侧真正需要的裸指针
  void *buffer;
  std::vector<void const *> input_tensors; // 输入 tensor 指针,只读
  std::vector<void*> output_tensors;       // 输出 tensor 指针,可写
  void *profiler_buffer;

  // 解析 Python 参数:期望传入 5 个 Python object
  // Python 调用大概是:
  // launch(input_ptrs, output_ptrs, workspace_ptr, stream_ptr, profiler_ptr)
  if (!PyArg_ParseTuple(args, "OOOOO",
                        &input_list,
                        &output_list,
                        &py_buffer,
                        &py_stream,
                        &py_profiler_buffer)) {
    PyErr_SetString(PyExc_TypeError, "Invalid parameters");
    return NULL;
  }

  // 检查 input/output 是否是 Python list
  // 注意这里传的不是 tensor object,而是 tensor.data_ptr() 组成的 list
  if(!PyList_Check(input_list) || !PyList_Check(output_list)) {
    PyErr_SetString(PyExc_TypeError, "Both arg1 and arg2 must be lists.");
    return NULL;
  }

  Py_ssize_t input_size = PyList_Size(input_list);
  Py_ssize_t output_size = PyList_Size(output_list);

  // 遍历 input_list,把 Python int 地址转成 C++ void const*
  for(Py_ssize_t i = 0; i < input_size; i++) {
    PyObject *item = PyList_GetItem(input_list, i); // borrowed reference
    void* tensor = PyLong_AsVoidPtr(item);          // Python int -> void*

    if(!tensor) {
      PyErr_Format(PyExc_TypeError,
                   "Failed to convert item %d (input) to void pointer", i);
      return NULL;
    }

    input_tensors.push_back(tensor);
  }

  // 遍历 output_list,把 Python int 地址转成 C++ void*
  for(Py_ssize_t i = 0; i < output_size; i++) {
    PyObject *item = PyList_GetItem(output_list, i);
    void* tensor = PyLong_AsVoidPtr(item);

    if(!tensor) {
      PyErr_Format(PyExc_TypeError,
                   "Failed to convert item %d (output) to void pointer", i);
      return NULL;
    }

    output_tensors.push_back(tensor);
  }

  // workspace buffer 指针
  buffer = PyLong_AsVoidPtr(py_buffer);

  // profiler buffer 指针,用来写 profiling 信息
  profiler_buffer = PyLong_AsVoidPtr(py_profiler_buffer);

  // CUDA stream 指针,决定 kernel 在哪个 stream 上 launch
  cudaStream_t stream = (cudaStream_t)PyLong_AsVoidPtr(py_stream);

  // 真正执行 Mirage 生成的 graph/kernel
  // 前面的代码只是 Python -> C++/CUDA 的 thin wrapper
  execute_mugraph(input_tensors,
                  output_tensors,
                  buffer,
                  stream,
                  profiler_buffer);

  Py_RETURN_NONE;
}

// Python 模块中的函数表
// 这里把 C++ 函数 launch 注册成 Python 函数 "launch"
static PyMethodDef ModuleMethods[] = {
  {
    "launch",        // Python 侧函数名:__mirage_launcher.launch
    launch,          // 对应的 C/C++ 函数
    METH_VARARGS,    // 使用普通 tuple 参数传递
    "Entry point for all kernels with this signature"
  },
  {NULL, NULL, 0, NULL} // sentinel,表示函数表结束
};

// Python 扩展模块定义
// 描述模块名、文档、函数表等信息
static struct PyModuleDef ModuleDef = {
  PyModuleDef_HEAD_INIT,
  "__mirage_launcher", // Python 模块名:import __mirage_launcher
  NULL,                // 模块文档
  -1,                  // 模块状态大小;-1 表示全局状态
  ModuleMethods,       // 模块函数表
  nullptr,             // m_slots,多阶段初始化用,这里不用
  nullptr,             // m_traverse,GC 相关,这里不用
  nullptr,             // m_clear,GC 相关,这里不用
  nullptr,             // m_free,模块释放 hook,这里不用
};

// Python import 这个 .so 时会调用这个初始化函数
// 模块名是 __mirage_launcher,所以函数名必须是 PyInit___mirage_launcher
PyMODINIT_FUNC PyInit___mirage_launcher(void) {
  // 根据 ModuleDef 创建 Python module object
  PyObject *m = PyModule_Create(&ModuleDef);
  if(m == NULL) {
    return NULL;
  }

  // 这里理论上有点冗余:
  // 因为 PyModule_Create(&ModuleDef) 已经会根据 ModuleMethods 注册函数
  PyModule_AddFunctions(m, ModuleMethods);

  return m;
}

.pyx

.pyx 的开发范式可以概括为:Python 负责易用接口,Cython 负责跨语言 binding,C++ 负责真正逻辑。在这类代码里,Python 用户看到的是 DTensorCyKNGraph 这种高级对象;但这些对象内部实际保存的是 CppDTensor*CppKNGraph* 之类的 C++ 指针。每个 .pyx 方法通常都遵循同一个模式:先检查 Python 参数类型,把 list/tuple/str/dtype 等 Python 对象转换成 C++ 能接受的 vector/char*/enum/raw pointer,然后调用 C++ core 的方法,最后把 C++ 返回的指针重新包装成 Python 对象返回。因此 .pyx 层不是主要算法实现层,而是 marshalling + thin wrapper + API facade:它让 Python 能自然地调用底层 C++ 编译器/IR/search/transpiler,同时避免把核心系统逻辑写在 Python 里。

def fuse_tensors(self, list[DTensor] inputs, int fused_dim, int num_groups, str name):
    # Python list[DTensor] -> C++ vector<const CppDTensor*>
    # 注意:这里传的是 graph tensor 的 C++ 指针,不是 tensor 数据本身。
    cdef vector[const CppDTensor*] cinputs
    cinputs.resize(len(inputs))

    cdef DTensor t
    for i in range(len(inputs)):
        assert(type(inputs[i]) == DTensor)
        t = inputs[i]
        cinputs[i] = t.c_ptr   # 取出 Python wrapper 内部保存的 CppDTensor*

    # Python str -> C char*
    # 这个 char* 只在本次 C++ 调用期间安全;如果 C++ 长期保存 name,C++ 侧应自行拷贝。
    cdef char* cname = NULL
    if name is not None:
        py_byte_string = name.encode('UTF-8')
        cname = py_byte_string

    # 调用真正的 C++ graph API,在 C++ IR 中创建 fused tensor。
    cdef CppDTensor* ptr = self.p_kgraph.fuse_tensors(
        cinputs, fused_dim, num_groups, cname
    )

    # C++ 返回 CppDTensor*,这里重新包装成 Python 侧 DTensor 句柄。
    output = ctypes.cast(<unsigned long long>ptr, ctypes.c_void_p)
    return DTensor(output)

C和python转换

kernel.py 里的 Python/C API 只解决 运行时 launch 的裸指针传递:
torch.Tensor.data_ptr()
-> Python int
-> PyLong_AsVoidPtr()
-> void*
-> execute_mugraph(...)

core.pyx 解决的是 编译器控制面 的复杂类型转换:
Python tuple/list/str/dtype/object
-> C++ vector/string/enum/pointer
-> Mirage C++ IR / search / transpiler

Comments

No comments yet.