一、什么是pymtml?
pymtml(Python Moore Threads Management Library)是摩尔线程官方提供的 GPU 设备管理 Python 库,由发行包名 mthreads-ml-py 提供。它让开发者可以用几行 Python 代码完成 GPU 的枚举、状态查询、温度监控、显存使用率统计、拓扑查询等工作,是构建 监控面板、调度脚本、推理服务健康检查 的基础工具。
如果你熟悉 NVIDIA 生态中的 pynvml,那么 pymtml 的 API 风格会让你感到非常亲切 —— 摩尔线程在设计时有意对齐了命名习惯与调用流程,让 CUDA 生态的运维脚本可以低成本迁移到 MUSA 环境。
2. 作业调度:根据空闲显存和温度挑选目标 GPU,自动设置
MUSA_VISIBLE_DEVICES。3. 健康检查:为 vLLM-MUSA / SGLang-MUSA 等推理服务提供 readiness probe。
4. 从 CUDA 迁移:把原本依赖 pynvml 的运维脚本一一对应改为 pymtml。
二、安装
pymtml 以 mthreads-ml-py 的名字发布到摩尔线程的 Python 索引中,安装完成后在代码里 import pymtml 即可。要正确工作,你的系统需要已经安装好 MUSA 驱动,推荐配合 MUSA SDK >= 4.3.2 使用。
# 安装
pip install mthreads-ml-py
# 如果你的环境使用摩尔线程私有索引
pip install mthreads-ml-py --index-url https://developer.mthreads.com/simple
# 验证:应看到类似 pymtml x.y.z 的版本号
python -c "import pymtml; print(pymtml.__version__)"
mtmlInit() 报错,大概率是驱动缺失或版本过低,先用 mthreads-gmi 命令确认驱动就绪。三、第一次设备查询
最常见的开场白:列出本机所有 MTT GPU,逐一打印名称、显存、温度。
import pymtml
# 初始化 MTML 库(每个进程调用一次)
pymtml.mtmlInit()
try:
count = pymtml.mtmlDeviceGetCount()
print(f"检测到 {count} 张摩尔线程 GPU\n")
for i in range(count):
handle = pymtml.mtmlDeviceGetHandleByIndex(i)
name = pymtml.mtmlDeviceGetName(handle)
uuid = pymtml.mtmlDeviceGetUUID(handle)
# 显存信息(单位:字节)
mem = pymtml.mtmlDeviceGetMemoryInfo(handle)
used_gb = mem.used / 1024**3
total_gb = mem.total / 1024**3
# 温度(sensor 0 通常是 die 温度,单位 °C)
temp = pymtml.mtmlDeviceGetTemperature(handle, 0)
# 功耗(单位 mW)
power_mw = pymtml.mtmlDeviceGetPowerUsage(handle)
# 利用率(0~100)
util = pymtml.mtmlDeviceGetUtilizationRates(handle)
print(f"GPU {i}: {name}")
print(f" UUID: {uuid}")
print(f" 显存: {used_gb:.2f} / {total_gb:.2f} GiB")
print(f" 温度: {temp} °C")
print(f" 功耗: {power_mw/1000:.1f} W")
print(f" 使用率: GPU {util.gpu}% 显存 {util.memory}%\n")
finally:
pymtml.mtmlShutdown()
关键点:
mtmlInit()/mtmlShutdown()必须成对调用,建议用try/finally包裹。mtmlDeviceGetHandleByIndex(i)返回一个句柄,后续所有查询都基于它。mem.used、mem.total、mem.free单位都是 字节。
四、查询 GPU 拓扑与 PCIe 信息
多卡场景下,显存不够时你通常希望把通信密集的任务放在 拓扑距离近 的 GPU 上(例如共享同一个 PCIe Switch)。pymtml 提供 PCIe 信息查询接口:
pymtml.mtmlInit()
count = pymtml.mtmlDeviceGetCount()
for i in range(count):
h = pymtml.mtmlDeviceGetHandleByIndex(i)
pci = pymtml.mtmlDeviceGetPciInfo(h)
print(f"GPU {i}: PCI bus {pci.bus:02x}:{pci.device:02x}.{pci.function} "
f"(domain {pci.domain:04x}) Device ID: {pci.pciDeviceId:#x}")
pymtml.mtmlShutdown()
如果你的 GPU 支持 PCIe Gen5 或者 MT-Link 互联,后续可以通过 mtmlDeviceGetTopologyCommonAncestor 类接口判断两张卡的共同祖先节点 —— 这是实现 多卡亲和性调度 的基础。
五、构建一个简单的监控脚本
下面的脚本每 2 秒采样一次,把指标打印成表格,可以直接 Ctrl+C 退出。稍加改造即可接入 Prometheus textfile collector 或自建的时序数据库。
import time
import pymtml
from datetime import datetime
def snapshot():
rows = []
count = pymtml.mtmlDeviceGetCount()
for i in range(count):
h = pymtml.mtmlDeviceGetHandleByIndex(i)
name = pymtml.mtmlDeviceGetName(h)
mem = pymtml.mtmlDeviceGetMemoryInfo(h)
util = pymtml.mtmlDeviceGetUtilizationRates(h)
temp = pymtml.mtmlDeviceGetTemperature(h, 0)
power = pymtml.mtmlDeviceGetPowerUsage(h) / 1000.0
rows.append({
"idx": i,
"name": name,
"mem_used_gb": round(mem.used / 1024**3, 2),
"mem_total_gb": round(mem.total / 1024**3, 2),
"gpu_util": util.gpu,
"mem_util": util.memory,
"temp_c": temp,
"power_w": round(power, 1),
})
return rows
if __name__ == "__main__":
pymtml.mtmlInit()
try:
while True:
ts = datetime.now().strftime("%H:%M:%S")
print(f"\n=== {ts} ===")
print(f"{'idx':<4}{'name':<20}{'mem(GiB)':<18}{'GPU%':<6}{'MEM%':<6}{'T°C':<6}{'W':<6}")
for r in snapshot():
print(f"{r['idx']:<4}{r['name']:<20}"
f"{r['mem_used_gb']}/{r['mem_total_gb']:<12}"
f"{r['gpu_util']:<6}{r['mem_util']:<6}{r['temp_c']:<6}{r['power_w']:<6}")
time.sleep(2)
except KeyboardInterrupt:
pass
finally:
pymtml.mtmlShutdown()
六、与 vLLM-MUSA / SGLang-MUSA 配合
在部署 LLM 推理服务时,pymtml 扮演两个关键角色:
MUSA_VISIBLE_DEVICES。import os, pymtml
def pick_best_gpu(min_free_gb=40, max_temp=85):
pymtml.mtmlInit()
try:
best, best_free = -1, 0
for i in range(pymtml.mtmlDeviceGetCount()):
h = pymtml.mtmlDeviceGetHandleByIndex(i)
mem = pymtml.mtmlDeviceGetMemoryInfo(h)
temp = pymtml.mtmlDeviceGetTemperature(h, 0)
free_gb = mem.free / 1024**3
if free_gb >= min_free_gb and temp <= max_temp and free_gb > best_free:
best, best_free = i, free_gb
return best
finally:
pymtml.mtmlShutdown()
idx = pick_best_gpu()
if idx < 0:
raise RuntimeError("没有满足条件的 GPU")
os.environ["MUSA_VISIBLE_DEVICES"] = str(idx)
print(f"已选中 GPU {idx} 用于 vLLM-MUSA 服务")
# 接下来启动 vllm serve …
七、从 pynvml 迁移到 pymtml
对于已有的 NVIDIA 运维脚本,迁移成本非常低。下表给出最常用 API 的一一对应关系:
| pynvml (CUDA) | pymtml (MUSA) | 说明 |
|---|---|---|
nvmlInit | mtmlInit | 初始化 |
nvmlShutdown | mtmlShutdown | 释放 |
nvmlDeviceGetCount | mtmlDeviceGetCount | 设备数 |
nvmlDeviceGetHandleByIndex | mtmlDeviceGetHandleByIndex | 句柄 |
nvmlDeviceGetMemoryInfo | mtmlDeviceGetMemoryInfo | 显存 total/free/used |
nvmlDeviceGetUtilizationRates | mtmlDeviceGetUtilizationRates | GPU/显存利用率 |
nvmlDeviceGetTemperature | mtmlDeviceGetTemperature | 温度 |
nvmlDeviceGetPowerUsage | mtmlDeviceGetPowerUsage | 功耗 mW |
CUDA_VISIBLE_DEVICES | MUSA_VISIBLE_DEVICES | 环境变量 |
mthreads-ml-py。通过与 pynvml 对齐的 API 设计,它让 GPU 的枚举、显存、温度、功耗、拓扑查询都变得像调用 torch.cuda.* 一样自然。在构建推理服务的健康检查、训练作业的调度脚本、机房监控面板时,pymtml 都是首选工具。参考:摩尔线程开发者站。附录:2026-08-10 更新章节 —— MTML 2.4.1 支持与 GPU 监控实战
1. MTML 2.4.1 的加载顺序变化
PR #13 的核心是一处看似很小、实则关系到升级平滑性的改动:优先加载 libmtml.so.2,把 libmtml.so 作为回退。
为什么要这样做?共享库的版本化命名(.so.2)表示 ABI 主版本,而无版本号的 libmtml.so 通常是指向当前版本的开发用符号链接。老实现直接加载 libmtml.so,意味着一旦系统上装了多个版本、或者符号链接指向了不期望的版本,Python 侧就会加载到 ABI 不匹配的库——表现为诡异的结构体字段错位,而不是干脆的加载失败。新实现优先绑定明确的 ABI 主版本,同时保留回退路径,兼顾了新旧环境。
与此配套,PR #13 让 ctypes 结构对齐 MTML 2.x 的 C ABI,并保留了兼容别名——这意味着你按本课正文写的旧脚本不需要改就能继续工作。官方在 MTT S80 上对 MTML 2.4.1 与 2.2.0 都做过冒烟验证,说明这次升级是双向兼容的。
| 项目 | 变化前 | 变化后(2.4.1) |
|---|---|---|
| 库加载顺序 | 直接加载 libmtml.so | 优先 libmtml.so.2,回退 libmtml.so |
| ctypes 结构 | 对齐旧 ABI | 对齐 MTML 2.x C ABI,保留兼容别名 |
| Python 包版本 | 2.2.x | 2.4.1 |
| 验证范围 | — | S80 上对 2.4.1 与 2.2.0 双版本冒烟 |
mthreads-ml-py 已经成为推理栈的硬依赖:vllm-musa PR #149 明确要求 mthreads-ml-py>=2.4.1。也就是说,如果你的推理环境里 pymtml 版本过低,受影响的可能不只是监控面板,还包括 vLLM-MUSA 自身的启动与设备发现。升级 vLLM-MUSA 前请先核对这个依赖。同一 PR 还把 MUSA wheel 名称去掉了 s5000 后缀,详见 m5-l3 的 2026-08-10 更新章节。2. Step-by-Step:升级与版本核对
# Step 1 — 查看当前版本与实际加载的动态库
pip show mthreads-ml-py | grep -E "Version|Location"
python - <<'PY'
import ctypes.util, subprocess, sys
for name in ("libmtml.so.2", "libmtml.so"):
try:
ctypes.CDLL(name)
print("loadable:", name)
except OSError as e:
print("not loadable:", name, "-", e)
PY
# Step 2 — 升级到 2.4.1(vllm-musa 的最低要求)
pip install "mthreads-ml-py>=2.4.1"
# Step 3 — 冒烟验证
python - <<'PY'
import pymtml
pymtml.mtmlLibraryInit()
n = pymtml.mtmlLibraryCountDevice()
print("device count:", n)
pymtml.mtmlLibraryShutDown()
PY
# Step 4 — 交叉核对 vllm-musa 依赖是否满足
pip check 2>&1 | grep -i mthreads || echo "dependency ok"
3. 完整监控示例:一个可直接投产的采集器
下面这个脚本把本课正文讲过的各项查询组织成一个可循环采集、可输出 CSV、可作为告警数据源的完整工具。它覆盖设备枚举、显存、利用率、温度、功耗四类核心指标,并做了健壮的异常处理——监控脚本挂掉比没有监控更糟,因为它会给你"一切正常"的错觉。
#!/usr/bin/env python3
"""MTT GPU 监控采集器 — 基于 pymtml (mthreads-ml-py >= 2.4.1)
用法:python gpu_monitor.py --interval 5 --csv /var/log/mtgpu.csv
"""
import argparse, csv, os, signal, sys, time
from datetime import datetime
import pymtml
RUNNING = True
def _stop(signum, frame):
global RUNNING
RUNNING = False
def safe(fn, default=None):
"""任何单项指标失败都不应该让整个采集器退出。"""
try:
return fn()
except Exception:
return default
def collect():
rows = []
count = pymtml.mtmlLibraryCountDevice()
for i in range(count):
dev = pymtml.mtmlLibraryInitDeviceByIndex(i)
gpu = safe(lambda: pymtml.mtmlDeviceInitGpu(dev))
rows.append({
"ts": datetime.now().isoformat(timespec="seconds"),
"index": i,
"name": safe(lambda: pymtml.mtmlDeviceGetName(dev), "unknown"),
"uuid": safe(lambda: pymtml.mtmlDeviceGetUUID(dev), "unknown"),
"mem_total": safe(lambda: pymtml.mtmlMemoryGetTotal(
pymtml.mtmlDeviceInitMemory(dev)), 0),
"mem_used": safe(lambda: pymtml.mtmlMemoryGetUsed(
pymtml.mtmlDeviceInitMemory(dev)), 0),
"util": safe(lambda: pymtml.mtmlGpuGetUtilization(gpu), -1),
"temp": safe(lambda: pymtml.mtmlGpuGetTemperature(gpu), -1),
"power": safe(lambda: pymtml.mtmlDeviceGetPowerUsage(dev), -1),
})
return rows
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--interval", type=float, default=5.0)
ap.add_argument("--csv", default="")
ap.add_argument("--temp-warn", type=float, default=85.0)
args = ap.parse_args()
signal.signal(signal.SIGINT, _stop)
signal.signal(signal.SIGTERM, _stop)
pymtml.mtmlLibraryInit()
writer, fh = None, None
if args.csv:
new = not os.path.exists(args.csv)
fh = open(args.csv, "a", newline="")
writer = csv.DictWriter(fh, fieldnames=list(collect()[0].keys()))
if new:
writer.writeheader()
try:
while RUNNING:
for r in collect():
used_gb = r["mem_used"] / 1024 if r["mem_used"] else 0
total_gb = r["mem_total"] / 1024 if r["mem_total"] else 0
print("[%s] GPU%d %-16s util=%3s%% mem=%.1f/%.1fGB temp=%s C power=%sW"
% (r["ts"], r["index"], r["name"], r["util"],
used_gb, total_gb, r["temp"], r["power"]))
if isinstance(r["temp"], (int, float)) and r["temp"] >= args.temp_warn:
print(" !! WARN: GPU%d temperature %s C >= %s C"
% (r["index"], r["temp"], args.temp_warn), file=sys.stderr)
if writer:
writer.writerow(r)
if fh:
fh.flush()
time.sleep(args.interval)
finally:
if fh:
fh.close()
pymtml.mtmlLibraryShutDown()
print("monitor stopped cleanly")
if __name__ == "__main__":
main()
safe() 包装让某个指标查询失败时返回默认值而非抛出,避免一块卡异常拖垮整个采集。② 信号处理与资源释放:捕获 SIGINT/SIGTERM 并在 finally 里调用 mtmlLibraryShutDown(),容器优雅停机时不会泄漏句柄。③ 落盘与 flush:CSV 每轮 flush,进程被强杀时也不会丢掉最后一批数据。这三点看着琐碎,但正是"能跑"与"能长期跑"之间的差别。4. 监控指标该看什么:从数据到决策
采集到数据只是第一步,真正难的是知道哪个指标异常时应该做什么。下面把采集器的五类指标逐一对应到实际的运维动作。
显存使用率是推理服务最关键的容量指标。对 vLLM/SGLang 这类预分配显存的引擎,启动后显存占用会迅速撑到设定值并保持平稳,因此显存曲线突然波动反而是异常信号,可能意味着服务重启过。反之,对训练作业,显存周期性波动是正常的,但峰值逐步抬升则提示可能有泄露。
利用率需要配合业务负载一起看。一个常见的误判是把低利用率直接归因为"GPU 没跑满,可以多接量"。实际上 Decode 阶段天然就是访存受限的,利用率偷低很正常;真正要关心的是利用率与吞吐是否同时低——那才说明服务卡在了别处(比如 CPU 预处理、网络、或者上一课讲的同步点)。
温度与功耗是硬件健康信号,也是性能稳定性的隐形变量。当一个节点上某张卡的温度明显高于其他卡时,不要只看绝对值是否超阈,卡间差异本身就是信号——它可能意味着风道阻塞、散热库老化,或者多卡任务负载不均。多卡训练场景下,一张卡因过热降频会拖慢整个集合通信,表现为全局吞吐下降而非单卡变慢,极难定位。这就是为什么采集器要按卡逐个记录而不是只存节点均值。
设备枚举与 UUID看似只是元数据,实际上是排障时最有用的一栏。容器化环境下,GPU 的逻辑编号会因为透传顺序不同而变化,只记录 index 会导致日志与物理卡对不上号。把 UUID 一并落盘,是跨容器、跨重启追踪同一张物理卡的唯一可靠方式。
5. 部署为常驻服务与排障
# 以 systemd 常驻(示例单元文件)
cat > /etc/systemd/system/mtgpu-monitor.service <<'EOF'
[Unit]
Description=MTT GPU Monitor
After=network.target
[Service]
ExecStart=/usr/bin/python3 /opt/monitor/gpu_monitor.py --interval 5 --csv /var/log/mtgpu.csv
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now mtgpu-monitor
systemctl status mtgpu-monitor --no-pager
# 快速查看最近记录
tail -5 /var/log/mtgpu.csv
| 现象 | 根因 | 处理 |
|---|---|---|
OSError: libmtml.so not found | 驱动未安装或库路径缺失 | 安装 MT 驱动,检查 LD_LIBRARY_PATH |
| 结构体字段值异常离谱 | ABI 不匹配(加载到旧版库) | 升级到 mthreads-ml-py>=2.4.1,确认加载 libmtml.so.2 |
| 容器内查不到设备 | 未透传 GPU 设备节点 | 启动时增加 --device=/dev/mtgpu0 等 |
| vLLM-MUSA 启动报 pymtml 相关错误 | 版本低于 2.4.1 | 按 PR #149 要求升级 |
回滚:pip install "mthreads-ml-py==2.2.0" --force-reinstall 可退回旧版本,由于 PR #13 保留了兼容别名,回滚不会破坏按旧 API 编写的脚本;但请注意,若同机运行 vLLM-MUSA 新版本,回滚会违反其 >=2.4.1 的依赖要求。
本章节小结:MTML 2.4.1 的核心变化是库加载顺序改为优先 libmtml.so.2、回退 libmtml.so,ctypes 结构对齐 MTML 2.x C ABI 并保留兼容别名,Python 包版本升至 2.4.1,官方在 S80 上对 2.4.1 与 2.2.0 做过双版本冒烟(PR #13)。它同时是 vLLM-MUSA 的硬依赖下限(PR #149),因此升级推理栈前必须先核对。官方教程仓库的 GPU monitor 更新(tutorial_on_musa PR #65)因公开描述不足以支撑独立课程,本站以本章节的完整采集器实现替代,覆盖枚举、显存、利用率、温度、功耗五类指标与 systemd 常驻部署。