CPython 扩展模块入门:用 C API 从零写出第一个 Python 扩展模块(spam 案例)
本文基于 CPython 官方文档 Your first C API extension module 展开,带你直接用底层 Python C API 写出一个名为 spam 的扩展模块,它把 C 标准库的 system() 函数封装成一个 Python 函数。读完本篇,你将掌握扩展模块的完整落地流程:如何组织 pyproject.toml / meson.build 并用 pip 构建、如何用 PyMODEXPORT_FUNC 定义模块导出钩子、如何用 PySlot 槽位表声明模块名与文档串、如何用 PyMethodDef 暴露函数,以及如何用 PyLong_FromLong 与 PyUnicode_AsUTF8AndSize 完成 Python 对象与 C 值之间的转换。同时会结合仓库源码解释这些新 C API 宏在解释器导入机制中的真实行为,帮助你建立"既有实操、又有源码级原理"的完整认知。
说明:本文使用的部分 C API(
PyModExport_*、PyABIInfo_VAR、槽位表模块导出)是在 CPython 3.15 中引入的,并使用了 C11 / C++20 语法。若你需要编写兼容更早 CPython 版本的扩展模块,应参考更早版本的官方文档(旧版采用PyInit_*初始化函数与PyModuleDef)。本篇的前提是使用 Unix-like 系统(含 macOS、Linux)或 Windows,并安装了合适的 C 编译器与 Python 开发头文件(Linux 上常为python3-dev或python3-devel包)。
我们要做什么
先建立一个直觉:我们要写一个扩展模块叫 spam,它把 C 标准库函数 system() 暴露给 Python。该函数声明在 stdlib.h 中:
#include <stdlib.h>
int system(const char *command);
它接收一个 C 字符串作为命令,把该命令交给系统 shell 执行,并把结果以整型(状态码)返回。我们的目标是让它可以从 Python 这样调用:
>>> import spam
>>> status = spam.system("whoami")
User Name
>>> status
0
两点提醒:
- 和许多 C 标准库函数一样,
system在 Python 中已有对应物。生产环境请使用os.system或subprocess.run,本例只是教学用途。 whoami在 Unix 与 Windows 上命令名相同,所以适合做教学示例;它在标准输出打印你的用户名。
从头部文件开始
先为教程建一个目录并进入它,然后在该目录创建名为 spammodule.c 的源文件。教程采用传统的 *module.c 后缀命名(文件名其实可以自定,只是部分工具对 .c 扩展名比较挑剔)。
在这个文件里,我们引入两个头文件:Python.h 拉入整个 Python C API 的声明,stdlib.h 提供 system() 函数:
#include <Python.h>
#include <stdlib.h> // for system()
务必把 stdlib.h 以及其它标准库头文件放在 Python.h 之后。 因为在某些系统上,Python 会在预处理阶段定义一些会影响标准头文件行为的宏。这一点在 Python.h 的注释里也有体现:Python.h 为了自身使用或向后兼容,已经包含了 stdlib.h 等若干标准头文件,因此显式再包含 stdlib.h 严格说并非必需,但这是良好实践——"显式包含你真正用到的东西"。
运行构建工具
此时只有 #include,扩展模块还什么也做不了。但这是一个先编译、先试导入的好时机:一旦构建工具被验证可用,后续每一步增量修改都能快速编译与测试。
CPython 本身不附带构建扩展模块的工具,官方推荐使用第三方项目。本教程选用 meson-python(从源码结构看,它对一个使用 PyModExport 的简单扩展而言开销最小;如果你倾向其它工具,见后文"附录:其它构建工具")。
meson-python 要求你定义一个"项目",需要额外两个文件。
第一,添加 pyproject.toml:
[build-system]
build-backend = 'mesonpy'
requires = ['meson-python']
[project]
# Placeholder project information
# (change this before distributing the module)
name = 'sampleproject'
version = '0'
第二,创建 meson.build:
project('sampleproject', 'c')
py = import('python').find_installation(pure: false)
py.extension_module(
'spam', # name of the importable Python module
'spammodule.c', # the C source file
install: true,
)
详细的
meson-python配置可查阅其官方文档。
现在,通过 pip 构建并安装当前目录的项目(.):
python -m pip -v install .
-v(--verbose)选项让 pip 显示编译器输出,在开发过程中非常有用。
如果你没装
pip,可在(最好是虚拟环境里的)运行python -m ensurepip。或者使用任何能构建并安装基于pyproject.toml项目的工具(如uv pip install)。
注意:每次改动扩展模块后都要重新运行这条命令。与 Python 不同,C 有显式的编译步骤。
当扩展模块被编译并安装后,启动 Python 并尝试导入它。此时应该得到如下异常:
>>> import spam
Traceback (most recent call last):
...
ImportError: dynamic module does not define module export function (PyModExport_spam or PyInit_spam)
模块导出钩子(module export hook)
导入时的异常已经告诉你,Python 在找"模块导出函数"(module export function),也即模块导出钩子(module export hook)。来定义一个。
先在 #include 行下方添加一个函数原型:
PyMODEXPORT_FUNC PyModExport_spam(void);
原型并非严格必要,但部分现代编译器在没有它时会发出警告。通常添加原型比关掉警告更好。
宏 PyMODEXPORT_FUNC 声明了该函数的返回类型,并加上让 CPython 在加载时能看到并使用它所需的一切特殊链接声明。从 exports.h 可以看到它的真实定义:
#ifndef PyMODEXPORT_FUNC
#define PyMODEXPORT_FUNC _PyINIT_FUNC_DECLSPEC PySlot*
#endif
也就是说,它展开为一个返回 PySlot*、带导出链接属性的函数声明。这与旧式 PyMODINIT_FUNC(返回 PyObject*)相对:PyMODINIT_FUNC 面向传统初始化函数,而 PyMODEXPORT_FUNC 面向新的"槽位数组"式导出。_PyINIT_FUNC_DECLSPEC 会在 Windows / Cygwin 上展开为 __declspec(dllexport),在 GCC/Clang 下展开为 __attribute__((visibility("default"))),从而保证符号跨平台可被导入器发现(参见 exports.h 中针对不同平台的分支)。
解释器如何找到这个函数?在 importdl.c 中定义了导入器查找的符号前缀:
static const struct hook_prefixes ascii_only_prefixes = {
"PyInit", "PyModExport"};
static const struct hook_prefixes nonascii_prefixes = {
"PyInitU", "PyModExportU"};
对 ASCII 命名的模块,导入器会依次尝试 PyInit_spam 与 PyModExport_spam 两个符号;对含非 ASCII 字符的模块名(PEP 489 编码),则使用 PyInitU_ / PyModExportU_ 前缀。这也解释了报错信息里为何同时出现 PyModExport_spam or PyInit_spam。
接着在原型之后添加函数本体。先让它返回 NULL:
PyMODEXPORT_FUNC
PyModExport_spam(void)
{
return NULL;
}
重新编译并加载。这次会得到不同的错误:
>>> import spam
Traceback (most recent call last):
...
SystemError: module export hook for module 'spam' failed without setting an exception
仅仅返回 NULL 对导出钩子而言并非正确行为,CPython 会为此抱怨。这其实是好事——它证明 CPython 已经找到了你的函数!
从 import.c 可以看到解释器对返回值的具体判定逻辑:
PySlot *slots = ex0();
if (!slots) {
if (!PyErr_Occurred()) {
PyErr_Format(
PyExc_SystemError,
"module export hook for module %R failed without setting an exception",
info->name);
}
return NULL;
}
即:钩子返回 NULL 且未设置任何异常时,解释器抛出 SystemError;若返回 NULL 的同时已设置了异常,则会走另一分支("raised unreported exception")。只有返回一个非空的 PySlot* 数组时,才会进入 PyModule_FromSlotsAndSpec(slots, spec) 真正创建模块。
让我们现在让它做点有用的事。
槽位表(slot table)
除了 NULL,导出钩子应当返回"创建一个模块所需的信息"。先从最基本的开始:名字与文档串。
这些信息应当定义在一个 PySlot 条目数组中——PySlot 本质上是"键值对"。在导出钩子之前定义这个数组:
PyABIInfo_VAR(abi_info);
static PySlot spam_slots[] = {
PySlot_STATIC_DATA(Py_mod_abi, &abi_info),
PySlot_STATIC_DATA(Py_mod_name, "spam"),
PySlot_STATIC_DATA(Py_mod_doc, "A wonderful module with an example function"),
PySlot_END
};
宏 PySlot_STATIC_DATA 用于槽位值是指向常量、静态分配数据的指针情形(这里分别是 &abi_info、"spam" 与文档串)。在 slots.h 中可以看到 PySlot 结构与这些宏的定义:
struct PySlot {
uint16_t sl_id;
uint16_t sl_flags;
_Py_ANONYMOUS union { uint32_t sl_reserved; }; // must be 0
_Py_ANONYMOUS union {
void *sl_ptr;
_Py_funcptr_t sl_func;
Py_ssize_t sl_size;
int64_t sl_int64;
uint64_t sl_uint64;
};
};
#define PySlot_STATIC_DATA(NAME, VALUE) \
{.sl_id=(NAME), .sl_flags=PySlot_STATIC, .sl_ptr=(VALUE)}
#define PySlot_END {0}
可见一个槽位由"槽位 ID(sl_id)+ 标志位(sl_flags)+ 一个联合体值"组成;PySlot_STATIC_DATA 会打上 PySlot_STATIC 标志并填充 sl_ptr,而 PySlot_END {0} 就是全零的哨兵。
关于 PyABIInfo_VAR(abi_info); 与 Py_mod_abi 槽位,它们是一小段样板代码,作用是防止为不同 Python 版本编译的扩展在加载时把解释器搞崩。在 modsupport.h 中可以看到其定义(3.15 新增):
typedef struct PyABIInfo {
uint8_t abiinfo_major_version;
uint8_t abiinfo_minor_version;
uint16_t flags;
uint32_t build_version;
uint32_t abi_version;
} PyABIInfo;
#define PyABIInfo_STABLE 0x0001
#define PyABIInfo_GIL 0x0002
#define PyABIInfo_FREETHREADED 0x0004
#define PyABIInfo_INTERNAL 0x0008
...
#define PyABIInfo_VAR(NAME) \
static PyABIInfo NAME = _PyABIInfo_DEFAULT;
PyABIInfo_VAR 会在编译期填充一份"编译本扩展时的版本/ABI 信息"(含构建版本 PY_VERSION_HEX、ABI 版本、以及 GIL / free-threaded / 稳定 ABI 等标志),加载时由 PyABIInfo_Check() 与目标解释器比对,从而在 ABI 不匹配时尽早失败而不是崩溃。对 CPython 核心内部模块,还存在一个便捷宏 _Py_ABI_SLOT(见 cpython/modsupport.h)直接生成该槽位。
对于 Py_mod_name 与 Py_mod_doc,它们的值都是 C 字符串——即 NUL 结尾、UTF-8 编码的字节数组。这些 Py_mod_* 槽位的数值由 slots_generated.h 统一生成,例如 Py_mod_name 100、Py_mod_doc 101、Py_mod_methods 103、Py_mod_abi 109 等;导入器最终会用这些 ID 把槽位解析成对应的模块属性(参见 pycore_slots_generated.h 中 _PySlot_resolve_mod_slot 的 switch 分支)。
注意末尾的 PySlot_END 哨兵条目,它标记数组结束。若忘记写它,会触发未定义行为。
该数组被声明为 static——即在这个 .c 文件之外不可见。这将成为一个反复出现的主题:CPython 只需要访问导出钩子,所有全局变量和其它函数一般都应声明为 static,以免与其它扩展模块发生符号冲突。
让你的导出钩子返回这个数组,而非 NULL:
PyMODEXPORT_FUNC
PyModExport_spam(void)
{
return spam_slots;
}
现在重新编译并试一下:
>>> import spam
>>> print(spam)
<module 'spam' from '/home/encukou/dev/cpython/spam.so'>
你拥有了一个扩展模块!试着 help(spam) 看看文档串。下一步是添加一个函数。
暴露一个函数
要直接把 C 的 system 函数暴露给 Python,需要写一层"胶水代码",把参数从 Python 对象转换成 C 值、把 C 的返回值转换回 Python。
写胶水代码最简单的方式之一是 METH_O 函数——它接收两个 Python 对象并返回一个。所有 Python 对象——无论其 Python 类型——在 C 中都表示为指向 PyObject 结构的指针。
在槽位数组上方添加这样一个函数:
static PyObject *
spam_system(PyObject *self, PyObject *arg)
{
Py_RETURN_NONE;
}
先忽略参数,改用宏 Py_RETURN_NONE。它展开为一个正确返回 Python None 对象的 return 语句。在 object.h 中可见其定义:
#if Py_GIL_DISABLED
# define Py_RETURN_NONE return Py_NewRef(Py_None)
#else
# define Py_RETURN_NONE return Py_None
#endif
在 free-threaded 构建下它返回 Py_None 的一个新引用,普通构建下则直接返回 Py_None。重新编译扩展以确保没有语法错误。由于我们还没把 spam_system 加进模块,你可能会收到一个"spam_system 未使用"的警告,这属正常。
方法定义(PyMethodDef)
要把 C 函数暴露给 Python,需要在名为 PyMethodDef 的结构体里提供若干信息:
ml_name:Python 函数名;ml_doc:文档串;ml_meth:被调用的 C 函数;ml_flags:一组标志,描述诸如"Python 参数如何传给 C 函数"等细节。这里用METH_O——与spam_system函数签名相匹配的标志。
METH_O 在 methodobject.h 中定义为 0x0008,表示"接收恰好一个参数、且该参数是任意单个 Python 对象"(与 METH_NOARGS 的 0x0004、METH_VARARGS 的 0x0001、METH_KEYWORDS 的 0x0002 相互独立,且 METH_NOARGS/METH_O 不得与 METH_VARARGS/METH_KEYWORDS 组合)。
由于模块通常会创建多个函数,这些定义需要收集进一个数组,末尾带一个零填充的哨兵。在 spam_system 函数下方添加这个数组:
static PyMethodDef spam_methods[] = {
{
.ml_name="system",
.ml_meth=spam_system,
.ml_flags=METH_O,
.ml_doc="Execute a shell command.",
},
{NULL, NULL, 0, NULL} /* Sentinel */
};
与模块槽位一样,零填充哨兵标记数组结束。
PyMethodDef结构体也用于创建类的方法,因此并不存在单独的"PyFunctionDef"。
接下来,把方法加入模块。向你的 PyMethodDef 数组对应的模块中加一个 Py_mod_methods 槽位:
PyABIInfo_VAR(abi_info);
static PySlot spam_slots[] = {
PySlot_STATIC_DATA(Py_mod_abi, &abi_info),
PySlot_STATIC_DATA(Py_mod_name, "spam"),
PySlot_STATIC_DATA(Py_mod_doc, "A wonderful module with an example function"),
PySlot_STATIC_DATA(Py_mod_methods, spam_methods), /* 新增这一行 */
PySlot_END
};
再次重新编译并测试。记得重启 Python 解释器,这样 import spam 才能加载模块的新版本。
现在你应该能调用该函数了:
>>> import spam
>>> print(spam.system)
<built-in function system>
>>> print(spam.system('whoami'))
None
注意此时 spam.system 还不会真正运行 whoami 命令,它只是返回 None。
验证该函数确实只接受一个参数(由 METH_O 标志所规定):
>>> print(spam.system('too', 'many', 'arguments'))
Traceback (most recent call last):
...
TypeError: spam.system() takes exactly one argument (3 given)
返回一个整数
现在看看返回值。除了 None,我们希望 spam.system 返回一个数字——即一个 Python int 对象。最终它将是系统命令的退出码,但先从一个固定值(比如 3)开始。
Python C API 提供了从 C int 值创建 Python int 对象的函数:PyLong_FromLong。
调用它,用下面 3 行替换 Py_RETURN_NONE:
static PyObject *
spam_system(PyObject *self, PyObject *arg)
{
int status = 3;
PyObject *result = PyLong_FromLong(status);
return result;
}
名字
PyLong_FromLong可能不太直观:PyLong指的是 Python 的int(它最初叫long),FromLong指的是 C 的long(或long int)类型。
重新编译,再次重启 Python 解释器,确认函数现在返回 3:
>>> import spam
>>> spam.system('whoami')
3
接受一个字符串
最后处理函数参数。
我们的 C 函数 spam_system 接收两个参数。第一个 PyObject *self 会被设为 spam 模块对象。本例用不到它,因此忽略。另一个 PyObject *arg 会被设为用户从 Python 传入的对象。我们期望它是一个 Python 字符串。为了使用其中的信息,需要把它转成 C 值——在本例中是一个 C 字符串(const char *)。
这里有个轻微的"类型不匹配":Python 的 str 对象存的是 Unicode 文本,而 C 字符串是字节数组。因此需要把数据编码,这里用 UTF-8。(对系统命令而言 UTF-8 未必总是正确,但它是 str.encode 的默认行为,且 C API 对它有专门支持。)
把 Python 字符串编码为 UTF-8 缓冲区的函数叫 PyUnicode_AsUTF8AndSize:
static PyObject *
spam_system(PyObject *self, PyObject *arg)
{
const char *command = PyUnicode_AsUTF8AndSize(arg, NULL);
int status = 3;
PyObject *result = PyLong_FromLong(status);
return result;
}
名字里
PyUnicode指的是 Pythonstr类的原始名字unicode;AndSize部分指该函数还能通过一个输出参数取回缓冲区长度。我们这里用不到,所以第二个参数传NULL。
若 PyUnicode_AsUTF8AndSize 成功,command 将指向结果 C 字符串——一个以零结尾的字节数组。该缓冲区由 arg 对象管理,意味着我们无需释放它,但要遵守一些规则:
- 我们只应在
spam_system函数内部使用该缓冲区。spam_system返回后,arg及其管理的缓冲区可能被垃圾回收。 - 我们不得修改它。这就是为什么用
const。
这里忽略了"Python 字符串也可能包含 NUL 字节"这一事实(NUL 会终止一个 C 字符串)。换言之,我们的函数会把
spam.system("foo\0bar")当成spam.system("foo")。这可能导致安全问题,因此真实的os.system会做长度检查并对这种情况抛错。
若 PyUnicode_AsUTF8AndSize 未成功,它返回 NULL 指针。调用任何 Python C API 时,我们都需要处理这类错误情形。通用做法留到后续章节(参见 错误处理相关文档)。此刻先放心:我们已经正确处理了来自 PyLong_FromLong 的错误。
对 PyUnicode_AsUTF8AndSize 调用,正确的错误处理方式是从 spam_system 返回 NULL。为它加一个 if 块:
static PyObject *
spam_system(PyObject *self, PyObject *arg)
{
const char *command = PyUnicode_AsUTF8AndSize(arg, NULL);
if (command == NULL) {
return NULL;
}
int status = 3;
PyObject *result = PyLong_FromLong(status);
return result;
}
(注:PyUnicode_AsUTF8AndSize 的第二个参数是缓冲区大小的输出参数,用不到时传 NULL。)
为测试错误处理是否生效,再次编译,重启 Python 让 import spam 加载新版本,并试着传一个非字符串值:
>>> import spam
>>> spam.system(3)
Traceback (most recent call last):
...
TypeError: bad argument type for built-in operation
现在只剩最后一步:用 char * 缓冲区调用 C 库函数 system,并改用它的结果而非 3:
static PyObject *
spam_system(PyObject *self, PyObject *arg)
{
const char *command = PyUnicode_AsUTF8AndSize(arg, NULL);
if (command == NULL) {
return NULL;
}
int status = system(command);
PyObject *result = PyLong_FromLong(status);
return result;
}
编译模块,重启 Python,测试。这次你应该看到你的用户名——whoami 系统命令的输出:
>>> import spam
>>> result = spam.system('whoami')
User Name
>>> result
0
你也可以用其它命令测试,如 ls、dir,或一个不存在的命令:
>>> import spam
>>> result = spam.system('nonexistent-command')
sh: line 1: nonexistent-command: command not found
>>> result
32512
最终完整源码
恭喜你!你已写好了一个完整的 Python C API 扩展模块,走完了整个教程。下面是整个源文件(与仓库中 spammodule-01.c 完全一致):
/* This file needs to be kept in sync with the tutorial
* at Doc/extending/first-extension-module.rst
*/
/// Includes
#include <Python.h>
#include <stdlib.h> // for system()
/// Implementation of spam.system
static PyObject *
spam_system(PyObject *self, PyObject *arg)
{
const char *command = PyUnicode_AsUTF8AndSize(arg, NULL);
if (command == NULL) {
return NULL;
}
int status = system(command);
PyObject *result = PyLong_FromLong(status);
return result;
}
/// Module method table
static PyMethodDef spam_methods[] = {
{
.ml_name="system",
.ml_meth=spam_system,
.ml_flags=METH_O,
.ml_doc="Execute a shell command.",
},
{NULL, NULL, 0, NULL} /* Sentinel */
};
/// Module slot table
PyABIInfo_VAR(abi_info);
static PySlot spam_slots[] = {
PySlot_STATIC_DATA(Py_mod_abi, &abi_info),
PySlot_STATIC_DATA(Py_mod_name, "spam"),
PySlot_STATIC_DATA(Py_mod_doc, "A wonderful module with an example function"),
PySlot_STATIC_DATA(Py_mod_methods, spam_methods),
PySlot_END
};
/// Export hook prototype
PyMODEXPORT_FUNC PyModExport_spam(void);
/// Module export hook
PyMODEXPORT_FUNC
PyModExport_spam(void)
{
return spam_slots;
}
整个文件自上而下遵循一条清晰脉络:头文件 → 胶水函数(METH_O 风格)→ 方法表 PyMethodDef → 槽位表 PySlot(含 ABI 信息与 Py_mod_methods)→ 导出钩子原型 → 导出钩子本体。所有非导出符号都保持 static,保证多扩展共存时不冲突。
附录:其它构建工具
除了"运行构建工具"那一节本身,你应该能用 meson-python 以外的构建工具走完本教程。Python 打包用户指南提供了推荐工具列表;请务必为 C 语言选一个。
缺少 PyInit 函数的临时解决方案(workaround)
如果你的构建工具输出抱怨缺少 PyInit_spam,先把下面这个函数加进模块:
// A workaround
void *PyInit_spam(void) { return NULL; }
这是对旧式初始化函数的一种"垫片(shim)"——该初始化函数在 CPython 3.14 及更早版本中是扩展模块所必需的。当前 CPython 不再需要它,但某些构建工具可能仍假设所有扩展模块都必须定义它。
若使用这个 workaround,你会得到异常 SystemError: initialization of spam failed without raising an exception,而不是 ImportError: dynamic module does not define module export function。
直接编译
强烈建议使用第三方构建工具,因为它会替你处理平台与 Python 安装的诸多细节、给最终扩展命名,以及(将来)帮你分发成果。
如果你是为特定系统、或仅为自己构建扩展,也可以直接运行编译器。具体方式因系统而异,请做好需要自行解决问题的准备。
Linux:Python 开发包可能附带 python3-config 命令,用于打印所需的编译器标志。若使用它,请确认它对应你将用来加载模块的那个 CPython 解释器。然后从下面这条命令开始:
gcc --shared $(python3-config --cflags --ldflags) spammodule.c -o spam.so
这应生成一个 spam.so 文件,你需要把它放到 sys.path 中的某个目录里。
更多平台相关的构建细节可参阅 building 文档 与 Windows 构建文档。
小结
本教程用一个最小的 spam 模块串起了 CPython 3.15 新式 C API 扩展的完整闭环:
- 用
pyproject.toml+meson.build定义项目,python -m pip -v install .增量构建; - 用
PyMODEXPORT_FUNC PyModExport_spam(void)定义模块导出钩子,解释器通过PyInit/PyModExport前缀(importdl.c)定位它; - 用
PySlot槽位表声明Py_mod_abi/Py_mod_name/Py_mod_doc,并以PySlot_END收尾;PyABIInfo_VAR负责跨版本 ABI 校验; - 用
PyMethodDef(METH_O)暴露函数,经Py_mod_methods槽位挂到模块; - 用
PyLong_FromLong造整型返回值、用PyUnicode_AsUTF8AndSize把字符串参数安全地转为 UTF-8 C 串并处理其错误路径。
理解了这条链路,你就掌握了把任意 C 库能力封装成 Python 可调用接口的基本功底;后续的错误处理、引用计数、自定义类型等进阶主题,可在 extending 目录 中继续深入。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00