pymllm Setup and Usage¶
总览¶
pymllm 是 mllm 面向 Python / CUDA 生态的推理服务运行时,主要跑在 NVIDIA Jetson
Orin 系列边缘 GPU(Orin NX / AGX Orin)上。它针对 Orin Ampere Tensor Core 的 INT8
算力做了系统级适配,支持 BF16 原生推理以及 W4A16、W8A8_INT8 两种量化方案,兼顾推理
速度与模型精度,目前已完成对 Qwen3、Qwen3-VL、Qwen3.5 的支持,并对外提供一套
OpenAI-compatible 的 HTTP API。
环境要求¶
下面是当前已经跑通的一组版本:
组件 |
版本或说明 |
|---|---|
JetPack / Jetson Linux |
JetPack |
Python |
|
PyTorch |
|
torchvision |
|
transformers |
|
safetensors |
|
flashinfer |
|
Triton Language |
|
CUDA |
|
GPU |
Jetson Orin NX,SM87 |
安装依赖¶
克隆仓库后,进入根目录安装 pymllm 和 mllm-kernel:
cd <repo-root>
SKBUILD_WHEEL_CMAKE=false python3 -m pip install -e .
python3 -m pip install -e <repo-root>/mllm-kernel --no-deps --no-build-isolation
triton 和 flashinfer 有两个来源,任选其一:
# 方式一:从 Jetson AI Lab 装 Jetson wheel。
python3 -m pip install --extra-index-url https://pypi.jetson-ai-lab.io/ triton flashinfer
# 方式二:从官方 PyPI 固定 Triton 版本,FlashInfer 仍从 Jetson AI Lab 装。
python3 -m pip install --index-url https://pypi.org/simple triton==3.6.0
python3 -m pip install --extra-index-url https://pypi.jetson-ai-lab.io/ flashinfer
在 aarch64 上,Triton wheel 能不能开箱即用,主要取决于 wheel 来源以及
ptxas / cuda.h 的查找路径。在上面这组已验证环境里,官方 PyPI 的
triton==3.6.0 manylinux aarch64 wheel 更接近开箱即用;如果用 Jetson AI Lab
的 wheel 碰到 ptxas 或 CUDA 头文件找不到的问题,显式设置 TRITON_PTXAS_PATH
和 CPATH 再重试通常能解决。装完后建议用 per_token_quant_int8 之类的最小
kernel 跑一次 smoke test,确认 Triton 真的能编译。
W8A8 首次运行的 JIT 编译¶
W8A8 的 INT8 GEMM 走 CUTLASS,依赖 CUTLASS 头文件。默认情况下不需要额外配置——
flashinfer 自带了一份 bundled CUTLASS,可以直接用;如果想换成自己的版本,设置
CUTLASS_HOME 即可。
第一次调用 W8A8 kernel 会触发一次 JIT 编译,编译产物缓存在:
~/.cache/mllm_kernel/cutlass_int8_scaled_mm/
之后复用缓存,不会再编译。想重新验证首次编译行为时,删掉这个目录再跑一次就行。
启动服务¶
服务入口是 pymllm.server.launch,启动后提供 /health、/v1/models、
/v1/completions、/v1/chat/completions、/generate 等接口。
W4A16 / W8A8 量化模型和 BF16 原生模型共用同一个入口,运行时会读 config.json
里的量化配置,自动走 W4A16 或 W8A8 路径。一条典型的量化模型启动命令:
cd <repo-root>
python3 -m pymllm.server.launch \
--server.model_path <quantized-model-path> \
--server.dtype float16 \
--quantization.method compressed-tensors \
--server.host 0.0.0.0 \
--server.port 30000 \
--server.mem_fraction_static 0.8 \
--server.max_running_requests 1 \
--server.max_total_tokens 4096 \
--server.disable_radix_cache \
--server.log_level debug
BF16 / FP16 原生模型用同一条命令,去掉 --quantization.method 即可。
常用参数¶
参数 |
说明 |
|---|---|
|
模型权重目录,通常是 HuggingFace 或 ModelScope 格式。 |
|
tokenizer 目录;不设置时默认等于 |
|
模型运行 dtype,可选 |
|
启用 |
|
|
|
同时运行的请求数。Jetson 小显存环境一般从 |
|
KV cache token pool 的容量上限,是整个 worker 全局共享的池子(不是单请求上限)。
实际容量取 |
|
关闭 Radix Cache,改用 |
OpenAI-compatible 请求¶
健康检查:
curl -s --noproxy '*' http://127.0.0.1:30000/v1/models ; echo
文本请求:
curl -s --noproxy '*' http://127.0.0.1:30000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "default",
"messages": [{"role": "user", "content": "你好,只回复:ok"}],
"max_tokens": 8,
"temperature": 0.0,
"stream": false
}' ; echo
图文请求里的图片路径要用服务进程可访问的绝对路径,不要带 file:// 前缀:
cat > /tmp/mm_req_path.json <<'JSON'
{
"model": "default",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "请描述这张图片。"},
{"type": "image_url", "image_url": {"url": "/workspace/test.png"}}
]
}
],
"max_tokens": 128,
"temperature": 0.0,
"stream": false
}
JSON
curl -s --noproxy '*' http://127.0.0.1:30000/v1/chat/completions \
-H "Content-Type: application/json" \
--data @/tmp/mm_req_path.json ; echo
Benchmark¶
bench_one_batch 是一个低层离线 benchmark。它直接初始化
pymllm.executor.model_runner.ModelRunner,绕过 HTTP server、tokenizer、scheduler、
detokenizer 这些进程,只测模型本身一次静态 prefill 加逐 token decode 的开销,因此适合
分析模型 forward、KV cache、attention、CUDA Graph 和量化 kernel 的模型级表现,也方便
验证 fused projection、residual-carry 这类模型图优化。它测不到在线服务的 TTFT / ITL /
E2E,这两个口径不要混用。
目前 bench_one_batch 支持三种测速口径:
纯文本:用 synthetic token ids 测纯文本的 prefill / decode;
视觉编码(vit_prefill):同步墙钟只包住视觉 encoder(
self.visual(...)), 反映纯视觉编码速度;多模态 prefill(multimodal_prefill):覆盖“视觉编码 + 图像/文本 token 的 LLM prefill”,反映完整多模态 prefill 速度。
纯文本用法:
PYTHONPATH="$PWD:$PWD/mllm-kernel" python3 -m pymllm.bench_one_batch \
--server.model_path <model-or-quantized-model-path> \
--server.dtype float16 \
--quantization.method compressed-tensors \
--server.mem_fraction_static 0.8 \
--server.max_running_requests 1 \
--server.max_total_tokens 2048 \
--server.log_level info \
--run-name qwen3vl_w8a8_bench_one_batch \
--batch-size 1 \
--input-len 256 512 1024 \
--output-len 128 \
--result-filename <result-jsonl-path>
--batch-size、--input-len、--output-len 都支持多个值,脚本会遍历所有组合
并把结果追加到 JSONL 文件。output_len 用的是总输出 token 语义:prefill 之后已经
拿到第一个 next token,后续 decode loop 再跑 output_len - 1 步。
多模态 prefill 用法。给 --image 传一张真实图片,再显式传 --input-len 时,长度
口径是 image placeholder tokens + text prompt tokens 的目标总长——脚本只在文本
token 上做补齐或截断,绝不动 image token,因此可以用同一张图 sweep
314/512/1024/2048 等不同总长,测包含视觉编码的完整多模态 prefill 速度:
PYTHONPATH="$PWD:$PWD/mllm-kernel" python3 -m pymllm.bench_one_batch \
--server.model_path <qwen3-vl-quantized-model-path> \
--server.trust_remote_code true \
--server.dtype float16 \
--quantization.method compressed-tensors \
--server.mem_fraction_static 0.8 \
--server.max_running_requests 1 \
--server.disable_cuda_graph \
--batch-size 1 \
--input-len 314 512 1024 2048 \
--output-len 1 \
--image <image-path> \
--prompt "Describe this image." \
--run-name qwen3vl_w8a8_multimodal_prefill \
--result-filename <result-jsonl-path>
JSONL 里 vit_prefill_ms 只包住 self.visual(...),multimodal_prefill_*
则是完整 VIT + LLM prefill 的别名字段,两者口径不同。在 AGX Orin 32GB 上的实测中,
W8A8 在长 prefill 上明显领先 FP16 / W4A16。
脚本的整体执行流程大致是:
pymllm.bench_one_batch CLI
|
|-- 解析 GlobalConfig 参数和 BenchArgs
|-- 加载 HuggingFace AutoConfig 到 cfg.model.hf_config
|
|-- ModelRunner.initialize()
| |-- 加载模型和量化配置
| |-- 初始化 KV pool 和 attention backend
| |-- 按需 capture decode CUDA Graph
|
|-- warmup 一次
|
|-- 遍历每个 (batch_size, input_len, output_len):
| |-- 清空 req_to_token_pool 和 token_to_kv_pool_allocator
| |-- 构造 synthetic input_ids
| |-- prefill:分配 request/KV slot,写 KV 映射,跑 forward + sampling
| |-- decode loop:逐步分配 KV slot,跑 forward + sampling,更新 seq_lens
|
|-- 追加 JSONL 结果行
Profile¶
bench_one_batch 内置了 profile 入口,方便在本地看 kernel timeline。目前有两条路径:
torch.profiler(已支持):
--profile-activities CPU GPU``(默认),输出 ``.trace.json.gztimeline,可以直接在 Perfetto / chrome://tracing 里看。输出目录 由PYMLLM_TORCH_PROFILER_DIR指定,默认/tmp。Nsight Systems / nsys(实验性):
--profile-activities CUDA_PROFILER通过cudaProfilerStart/Stop驱动 nsys,需要外层用nsys --capture-range=cudaProfilerApi包住命令。这条路径还在打磨中,部分场景下 可能不够顺手,仅作为可选的深入分析手段。