Files
enginex-mlu370-compat/test_wrapper_e2e.py

168 lines
7.6 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_wrapper_e2e.py —— 用平台真实命令形态端到端验证 vllm_wrapper.sh
背景:平台 bi-100 × vllm × text-generation 的 build-config 实际下发的是
vllm serve /model --port 80 --served-model-name llm --max-model-len 4096 \
--gpu-memory-utilization 0.9 --enforce-eager --trust-remote-code -tp 1
因为 Dockerfile 把镜像里的 vllm 换成了本仓 wrapper,这条命令会流经 wrapper。
本测试用一个"假 vllm_real"(把收到的参数原样打印)验证:
1. wrapper 正确识别 serve 形态,不吞参数、不串位
2. preflight 产出的修补参数被**追加**在原命令之后(不覆盖、不前置)
3. 非 serve 子命令(如 --version)原样透传
4. preflight 自己挂掉时不阻断启动(仍能 exec vllm_real)
5. 模型目录里有真实缺陷时,参数确实被补上
不需要 GPU / vLLM / Docker。
"""
import io
import json
import os
import shutil
import subprocess
import sys
import tempfile
HERE = os.path.dirname(os.path.abspath(__file__))
WRAPPER = os.path.join(HERE, 'vllm_wrapper.sh')
PREFLIGHT = os.path.join(HERE, 'preflight.py')
PASS, FAIL = [], []
def check(name, cond, detail=''):
(PASS if cond else FAIL).append(name)
print((' PASS ' if cond else ' FAIL ') + name + ((' -- ' + str(detail)) if (detail and not cond) else ''))
def make_fake_preflight(dirpath, extra_args, fail=False):
"""造一个假 preflight:按给定 extra_args 输出 JSON;fail=True 时非 0 退出。"""
p = os.path.join(dirpath, 'preflight.py')
body = [
'import json, sys',
'args = sys.argv[1:]',
'out = ""',
'if "--out" in args: out = args[args.index("--out") + 1]',
'if ' + ('True' if fail else 'False') + ':',
' sys.stderr.write("boom' + chr(92) + 'n"); sys.exit(3)',
'payload = {"extra_args": ' + repr(extra_args) + ', "patches": ["stub"], "overlay": ""}',
'if out:',
' fh = open(out, "w")',
' fh.write(json.dumps(payload))',
' fh.close()',
'print(json.dumps(payload))',
]
with io.open(p, 'w', encoding='utf-8', newline=chr(10)) as f:
f.write(chr(10).join(body) + chr(10))
return p
def run_wrapper(model_dir, args, box, extra_args=None, fail_preflight=False):
"""在隔离目录跑 wrapper。
box = 隔离目录;里面放:vllm_real(假)、preflight.py(假)、wrapper_local.sh。
extra_args 由假 preflight 产出,用来验证 wrapper 的参数拼装。
全部使用相对路径(cwd=box),规避 Windows 路径被 MSYS 吃掉。
"""
real = os.path.join(box, 'vllm_real')
with io.open(real, 'w', encoding='utf-8', newline=chr(10)) as f:
f.write('#!/bin/bash' + chr(10) + 'echo "VLLM_REAL_ARGS: $@"' + chr(10))
os.chmod(real, 0o755)
make_fake_preflight(box, extra_args or [], fail=fail_preflight)
src = io.open(WRAPPER, encoding='utf-8', newline=chr(10)).read()
local = os.path.join(box, 'wrapper_local.sh')
with io.open(local, 'w', encoding='utf-8', newline=chr(10)) as f:
f.write(src
.replace('/usr/local/corex/lib64/python3/dist-packages/bin/vllm_real', './vllm_real')
.replace('/opt/preflight.py', './preflight.py')
.replace('/tmp/mhxc_preflight.json', './preflight.json')
.replace('/tmp/mhxc_preflight.out', './preflight.out')
.replace('/tmp/mhxc_preflight.err', './preflight.err'))
os.chmod(local, 0o755)
env = dict(os.environ)
env['PATH'] = box + os.pathsep + env.get('PATH', '')
cmd = ['bash', 'wrapper_local.sh'] + args
p = subprocess.run(cmd, capture_output=True, text=True, timeout=90, env=env, cwd=box)
return p.returncode, p.stdout, p.stderr
def parse_real_args(stdout):
for l in stdout.splitlines():
if l.startswith('VLLM_REAL_ARGS: '):
return l[len('VLLM_REAL_ARGS: '):]
return None
def main():
tmp = tempfile.mkdtemp(prefix='mhxc_e2e_')
try:
PLATFORM_CMD = ['serve', '/model', '--port', '80', '--served-model-name', 'llm',
'--max-model-len', '4096', '--gpu-memory-utilization', '0.9',
'--enforce-eager', '--trust-remote-code', '-tp', '1']
EXTRA = ['--tokenizer', '/tmp/ov/tokenizer',
'--chat-template', '/tmp/ov/tpl.jinja',
'--hf-overrides', '{"architectures":["Qwen3_5MoeForCausalLM"]}']
print('[1] 平台真实命令 + preflight 产出三类补丁')
rc, out, err = run_wrapper('/model', PLATFORM_CMD, tmp, extra_args=EXTRA)
final = parse_real_args(out) or ''
check('wrapper 正常退出', rc == 0, err[-300:])
check('真 vllm 被调起', final != '', out[:200])
orig = PLATFORM_CMD[2:]
check('原命令参数全部保留', all(a in final.split() for a in orig), final)
check('serve 与 /model 在最前', final.split()[:2] == ['serve', '/model'], final[:100])
check('三类补丁都带上', all(a in final.split() for a in ['--tokenizer', '--chat-template', '--hf-overrides']), final)
# 正确性要求不是"补丁必须在末尾",而是"平台原命令的参数序列不被拆乱":
# 平台参数应以原有顺序连续出现,且补丁只作为追加项存在。
toks = final.split()
plat = PLATFORM_CMD[2:]
letidx = toks.index(plat[0]) if plat[0] in toks else -1
seq_ok = letidx >= 0
if seq_ok:
for k in range(len(plat)):
if toks[letidx + k] != plat[k]:
seq_ok = False
break
check('平台原命令参数序列完整且顺序不变', seq_ok, (letidx, final))
head_ok = toks[0] == 'serve' and toks[1] == '/model'
check('serve 与模型目录仍在最前(位置参数不被挤掉)', head_ok, final[:80])
print(' 最终命令: ' + final[:260])
print('[2] 干净模型:preflight 无产出 → 命令与平台原始命令完全一致')
rc2, out2, err2 = run_wrapper('/model', PLATFORM_CMD, tmp, extra_args=[])
final2 = parse_real_args(out2) or ''
check('无补丁时命令逐字一致', final2.strip() == ' '.join(PLATFORM_CMD).strip(), final2)
print('[3] 非 serve 子命令原样透传')
rc3, out3, err3 = run_wrapper('/model', ['--version'], tmp, extra_args=EXTRA)
check('--version 原样透传', (parse_real_args(out3) or '').strip() == '--version', out3[:200])
print('[4] preflight 挂掉时不阻断启动')
rc4, out4, err4 = run_wrapper('/model', PLATFORM_CMD, tmp, fail_preflight=True)
final4 = parse_real_args(out4) or ''
check('仍能起服务且用原命令', rc4 == 0 and final4.strip() == ' '.join(PLATFORM_CMD).strip(), (rc4, final4[:150]))
print('[5] preflight 自己写 JSON 也能被正确读回(不依赖 --out 文件时的兜底)')
rc5, out5, err5 = run_wrapper('/model', PLATFORM_CMD, tmp, extra_args=['--dtype', 'half'])
check('单参数补丁也能拼上', '--dtype' in (parse_real_args(out5) or '').split(), out5[:200])
print('[6] bash 语法')
p = subprocess.run(['bash', '-n', 'vllm_wrapper.sh'], capture_output=True, text=True, cwd=HERE)
check('wrapper bash 语法正确', p.returncode == 0, p.stderr[:200])
print(chr(10) + '===== 结果: %d passed, %d failed =====' % (len(PASS), len(FAIL)))
if FAIL:
print('失败项: ' + ', '.join(FAIL))
return 1
return 0
finally:
shutil.rmtree(tmp, ignore_errors=True)
if __name__ == '__main__':
sys.exit(main())