From 4948b0d3560b0fda00ad95fab6edd4fd1cc59bd1 Mon Sep 17 00:00:00 2001 From: wanglin <2281216234@qq.com> Date: Sat, 3 Oct 2026 23:02:30 +0800 Subject: [PATCH] enginex-mlu370-compat: test_wrapper_e2e.py --- test_wrapper_e2e.py | 167 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 test_wrapper_e2e.py diff --git a/test_wrapper_e2e.py b/test_wrapper_e2e.py new file mode 100644 index 0000000..c1abde5 --- /dev/null +++ b/test_wrapper_e2e.py @@ -0,0 +1,167 @@ +#!/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())