Files
enginex-mlu370-compat/test_wrapper_e2e.py

168 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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())