在 Kaggle 上运行 vLLM API Server 并部署开源模型

介绍

众所周知,Kaggle 是一个非常适合进行数据科学和机器学习实验的平台,提供了慷慨的计算资源,包括每周 30 小时的GPU时间。而 vLLM 是一个高性能的 LLM 推理引擎,本文将介绍如何在 Kaggle 上运行 vLLM API Server,并部署一个开源模型,使其能够通过在线接口供其他应用调用。

步骤

1. 创建 Kaggle Notebook

首先,在 Kaggle 上创建一个新的 Notebook,选择 GPU 作为计算资源。以 Qwen/Qwen3-VL-32B-Instruct-FP8 模型为例,我们将使用 vLLM 来部署这个模型,选择具有 80 GB 显存的 H100 实例。在右侧启用 Internet 访问以便安装依赖。

2. 安装依赖环境

kaggle 默认环境包含许多现有的依赖,为了不破坏系统环境,我们使用 uv 来创建一个虚拟环境,并在其中安装 vLLM 及其依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import subprocess, pathlib

VENV = pathlib.Path("/tmp/.venv")

if not VENV.exists():
subprocess.check_call([
"uv", "venv",
str(VENV), # 👈 指定目录
"--python", "3.12",
"--seed"
])

VENV_PY = str(VENV / "bin" / "python")
print("VENV_PY =", VENV_PY)

# 官方推荐 torch backend auto
subprocess.check_call([
"uv", "pip", "install",
"vllm",
"--torch-backend=auto",
"--python", VENV_PY
])

# 额外依赖
subprocess.check_call([
"uv", "pip", "install",
"openai>=1.0.0",
"pyngrok>=7.0.0",
"requests",
"Pillow",
"--python", VENV_PY
])

print("✅ done")

3. 配置环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import os

MODEL = os.environ.get("MODEL", "Qwen/Qwen3-VL-32B-Instruct-FP8")
TP = int(os.environ.get("TP", "1"))
PORT = int(os.environ.get("PORT", "38000"))
DTYPE = os.environ.get("DTYPE", "auto")

# 如果需要访问 HF 私有/限流资源:
os.environ["HUGGINGFACE_HUB_TOKEN"] = "hf_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

# ngrok token(必需设置)
os.environ["NGROK_AUTHTOKEN"] = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

print("MODEL:", MODEL)
print("TP:", TP)
print("PORT:", PORT)
print("DTYPE:", DTYPE)
print("✅ done")

4. 启动 vLLM API Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import os, subprocess

env = os.environ.copy()
env["TRANSFORMERS_NO_TF"] = "1"
env["TRANSFORMERS_NO_FLAX"] = "1"
env["TF_CPP_MIN_LOG_LEVEL"] = "3"

cmd = [
VENV_PY, "-m", "vllm.entrypoints.openai.api_server",
"--model", MODEL,
"--port", str(PORT),
"--tensor-parallel-size", str(TP),
"--dtype", DTYPE,
"--host", "127.0.0.1",
"--download-dir", "/tmp/working/vllm_models",
"--max-model-len", "32768", # 根据模型最大上下文长度和显存空间调整
#"--gpu-memory-utilization", "0.92",
]

print("Launching:", " ".join(cmd))

log_path = "/tmp/vllm.log"
log_f = open(log_path, "w", buffering=1)

vllm_proc = subprocess.Popen(
cmd,
env=env,
stdout=log_f,
stderr=subprocess.STDOUT,
text=True,
start_new_session=True,
)

print("vLLM pid:", vllm_proc.pid)
print("log:", log_path)

5. 查看日志

1
!tail -n 200 /tmp/vllm.log

如果看到类似以下内容,说明 vLLM API Server 已经成功启动:

1
2
3
(APIServer pid=1202) INFO:     Started server process [1202]
(APIServer pid=1202) INFO: Waiting for application startup.
(APIServer pid=1202) INFO: Application startup complete.

如果报一些关于显存不足的错误,可以尝试调整 --max-model-len 参数,或者增加 --gpu-memory-utilization 的值来让 vLLM 更激进地使用显存。抑或重启和换一个显存更大的实例试试。

6. 使用 ngrok 暴露接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import os, subprocess, textwrap, time

ngrok_script = textwrap.dedent("""
import os, time
from pyngrok import ngrok

port = int(os.environ.get('PORT', '8000'))
token = os.environ.get('NGROK_AUTHTOKEN')
if token:
ngrok.set_auth_token(token)

tunnel = ngrok.connect(port, 'tcp')
print('PUBLIC_URL=' + tunnel.public_url, flush=True)

# 保持进程常驻,避免 atexit 清理导致隧道被关
while True:
time.sleep(3600)
""")

env = os.environ.copy()
env["PORT"] = str(PORT)

ngrok_proc = subprocess.Popen(
[VENV_PY, "-c", ngrok_script],
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
)

public_url = None
deadline = time.time() + 30
while time.time() < deadline and public_url is None:
line = ngrok_proc.stdout.readline().strip()
if not line:
continue
print(line)
if line.startswith("PUBLIC_URL="):
public_url = line.split("=", 1)[1]
break

if not public_url:
raise RuntimeError("ngrok did not start in time; check NGROK_AUTHTOKEN / network")

NGROK_TCP_URL = public_url
NGROK_HTTP_BASE = "http://" + public_url.replace("tcp://", "") + "/v1"

print("\n🌍 Ngrok TCP:", NGROK_TCP_URL)
print("OpenAI base_url (via ngrok):", NGROK_HTTP_BASE)
print("✅ 该 cell 已结束,但 ngrok 在后台运行(不阻塞)。")

应当会看到类似以下输出:

1
2
3
4
5
6
7
8
Downloading ngrok: 99%
Downloading ngrok: 100%
Installing ngrok ...
PUBLIC_URL=tcp://4.tcp.ngrok.io:12227

🌍 Ngrok TCP: tcp://4.tcp.ngrok.io:12227
OpenAI base_url (via ngrok): http://4.tcp.ngrok.io:12227/v1
✅ 该 cell 已结束,但 ngrok 在后台运行(不阻塞)。

7. 测试接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from openai import OpenAI

# 走本地:
#client = OpenAI(api_key="EMPTY", base_url=f"http://127.0.0.1:{PORT}/v1")

# 如果要走 ngrok:
client = OpenAI(api_key="EMPTY", base_url=NGROK_HTTP_BASE)

resp = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": "你好!请用一句话自我介绍。"}],
temperature=0.2,
)
print(resp.choices[0].message.content)

如果一切顺利,你应该会看到模型的回复,例如:

1
你好!我是通义千问,阿里巴巴研发的超大规模语言模型,擅长回答问题、创作文字、编程等多任务处理。

公网对话地址即为 http://4.tcp.ngrok.io:12227/v1/chat/completions ,其他应用可以通过这个地址调用接口