diff --git a/app/api/submit/index.dspy b/app/api/submit/index.dspy index 756f3c8..dee03fa 100644 --- a/app/api/submit/index.dspy +++ b/app/api/submit/index.dspy @@ -20,7 +20,7 @@ if method == 'POST': sample_guide_scale = params_kw.get('sample_guide_scale', None) base_seed = params_kw.get('base_seed', None) - valid_sizes = ['704*1280', '1280*704'] + valid_sizes = ['480*832', '832*480', '704*1024', '1024*704', '704*1280', '1280*704'] if size not in valid_sizes: return json.dumps({'error': f'invalid size, must be one of: {valid_sizes}'}, ensure_ascii=False) @@ -67,5 +67,5 @@ else: 'base_seed': 'int (optional)', 'task_id': 'string (optional, auto-generated)', }, - 'valid_sizes': ['704*1280', '1280*704'] + 'valid_sizes': valid_sizes }, ensure_ascii=False) diff --git a/nohup.out b/nohup.out new file mode 100644 index 0000000..0774de5 --- /dev/null +++ b/nohup.out @@ -0,0 +1,22 @@ +2026-06-13 19:47:13.896[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/ahserver/configuredServer.py:40]client_max_size=1024000000 +reuse_port= True +> is a coroutine +2026-06-13 19:47:13.898[webapp][debug][/data/ymq/wan22-service/ah.py:32]longtasks worker started, GPU: 2 +======== Running on http://0.0.0.0:8079 ======== +(Press CTRL+C to quit) +2026-06-13 19:47:13.998[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:39]cleanup_expired_tasks() called ... +2026-06-13 19:47:14.001[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:131][worker 0] start +2026-06-13 19:47:26.171[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:144]get task_id=Z_nN_4cWgxJQCP2QhgnAs +2026-06-13 19:47:26.172[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:148]task={'payload': {'task_type': 'generate_video', 'task_id': '439fcdcf2e08', 'prompt': 'A cat walking on a rainbow bridge in a sunny park, cartoon style, cinematic', 'image': None, 'size': '1280*704', 'frame_num': 33, 'sample_steps': None, 'sample_guide_scale': None, 'base_seed': None}, 'status': 'PENDING', 'created_at': 1781351246.1700892, 'task_id': 'Z_nN_4cWgxJQCP2QhgnAs'} +2026-06-13 19:47:26.172[webapp][debug][/data/ymq/wan22-service/ah.py:20]Wan22Tasks processing: type=generate_video +2026-06-13 19:47:26.175[webapp][debug][/data/ymq/wan22-service/workers/generate.py:29]Loading Wan22 engine (first call, may take 30-60s)... + Loading checkpoint shards: 0%| | 0/3 [00:00> is a coroutine +2026-06-13 19:58:05.642[webapp][debug][/data/ymq/wan22-service/ah.py:32]longtasks worker started, GPU: 1 +======== Running on http://0.0.0.0:8079 ======== +(Press CTRL+C to quit) +2026-06-13 19:58:05.742[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:39]cleanup_expired_tasks() called ... +2026-06-13 19:58:05.745[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:131][worker 0] start +2026-06-13 20:02:40.958[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:144]get task_id=WWI20LOCUyF0ZtFQb97Fr +2026-06-13 20:02:40.959[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:148]task={'payload': {'task_type': 'generate_video', 'task_id': 'c0be4524e760', 'prompt': 'Concurrent test task 2 - mountain landscape with flowing river', 'image': None, 'size': '832*480', 'frame_num': 33, 'sample_steps': None, 'sample_guide_scale': None, 'base_seed': None}, 'status': 'PENDING', 'created_at': 1781352160.9567037, 'task_id': 'WWI20LOCUyF0ZtFQb97Fr'} +2026-06-13 20:02:40.959[webapp][debug][/data/ymq/wan22-service/ah.py:20]Wan22Tasks processing: type=generate_video +2026-06-13 20:02:40.963[webapp][debug][/data/ymq/wan22-service/workers/generate.py:29]Loading Wan22 engine (first call, may take 30-60s)... + Loading checkpoint shards: 0%| | 0/3 [00:00> is a coroutine +2026-06-13 19:58:06.654[webapp][debug][/data/ymq/wan22-service/ah.py:32]longtasks worker started, GPU: 2 +======== Running on http://0.0.0.0:8079 ======== +(Press CTRL+C to quit) +2026-06-13 19:58:06.755[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:39]cleanup_expired_tasks() called ... +2026-06-13 19:58:06.757[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:131][worker 0] start +2026-06-13 19:58:31.880[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:144]get task_id=OwOonR1G4U7fEsy1mBodZ +2026-06-13 19:58:31.881[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:148]task={'payload': {'task_type': 'generate_video', 'task_id': '681b096112c5', 'prompt': 'A beautiful sunset over the ocean, waves gently lapping on the shore', 'image': None, 'size': '1280*704', 'frame_num': 33, 'sample_steps': None, 'sample_guide_scale': None, 'base_seed': None}, 'status': 'PENDING', 'created_at': 1781351911.878588, 'task_id': 'OwOonR1G4U7fEsy1mBodZ'} +2026-06-13 19:58:31.881[webapp][debug][/data/ymq/wan22-service/ah.py:20]Wan22Tasks processing: type=generate_video +2026-06-13 19:58:31.886[webapp][debug][/data/ymq/wan22-service/workers/generate.py:29]Loading Wan22 engine (first call, may take 30-60s)... + Loading checkpoint shards: 0%| | 0/3 [00:00> is a coroutine +2026-06-13 19:58:07.680[webapp][debug][/data/ymq/wan22-service/ah.py:32]longtasks worker started, GPU: 3 +======== Running on http://0.0.0.0:8079 ======== +(Press CTRL+C to quit) +2026-06-13 19:58:07.781[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:39]cleanup_expired_tasks() called ... +2026-06-13 19:58:07.782[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:131][worker 0] start +2026-06-13 20:02:40.997[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:144]get task_id=0g-viJnViW4v5AVaqQFCZ +2026-06-13 20:02:40.998[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:148]task={'payload': {'task_type': 'generate_video', 'task_id': '949ddbbf0fa5', 'prompt': 'Concurrent test task 3 - mountain landscape with flowing river', 'image': None, 'size': '832*480', 'frame_num': 33, 'sample_steps': None, 'sample_guide_scale': None, 'base_seed': None}, 'status': 'PENDING', 'created_at': 1781352160.9958723, 'task_id': '0g-viJnViW4v5AVaqQFCZ'} +2026-06-13 20:02:40.998[webapp][debug][/data/ymq/wan22-service/ah.py:20]Wan22Tasks processing: type=generate_video +2026-06-13 20:02:41.002[webapp][debug][/data/ymq/wan22-service/workers/generate.py:29]Loading Wan22 engine (first call, may take 30-60s)... + Loading checkpoint shards: 0%| | 0/3 [00:00> is a coroutine +2026-06-13 19:58:08.659[webapp][debug][/data/ymq/wan22-service/ah.py:32]longtasks worker started, GPU: 4 +======== Running on http://0.0.0.0:8079 ======== +(Press CTRL+C to quit) +2026-06-13 19:58:08.759[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:39]cleanup_expired_tasks() called ... +2026-06-13 19:58:08.761[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:131][worker 0] start +2026-06-13 20:02:41.031[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:144]get task_id=i7UsLyuV3Iu7C-QC96jr0 +2026-06-13 20:02:41.031[webapp][debug][/data/ymq/wan22-service/py3/lib/python3.10/site-packages/longtasks/longtasks.py:148]task={'payload': {'task_type': 'generate_video', 'task_id': '2458c41944db', 'prompt': 'Concurrent test task 4 - mountain landscape with flowing river', 'image': None, 'size': '832*480', 'frame_num': 33, 'sample_steps': None, 'sample_guide_scale': None, 'base_seed': None}, 'status': 'PENDING', 'created_at': 1781352161.0304644, 'task_id': 'i7UsLyuV3Iu7C-QC96jr0'} +2026-06-13 20:02:41.032[webapp][debug][/data/ymq/wan22-service/ah.py:20]Wan22Tasks processing: type=generate_video +2026-06-13 20:02:41.033[webapp][debug][/data/ymq/wan22-service/workers/generate.py:29]Loading Wan22 engine (first call, may take 30-60s)... + Loading checkpoint shards: 0%| | 0/3 [00:00 wan22-service.log 2>&1 & +WAN22_GPU_ID=2 +export WAN22_GPU_ID +export CUDA_VISIBLE_DEVICES=2 +export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True +export PYTHONPATH=/data/ymq/wan22-service/repo:~/Win2.2 +nohup py3/bin/python ah.py > nohup.out 2>&1 & echo "wan22-service started, PID: $!, GPU: $WAN22_GPU_ID" diff --git a/start_multi.sh b/start_multi.sh new file mode 100755 index 0000000..04135d7 --- /dev/null +++ b/start_multi.sh @@ -0,0 +1,20 @@ +#!/bin/bash +cd ~/wan22-service + +# 先停止所有现有实例 +pkill -f 'python ah.py' 2>/dev/null +sleep 2 + +# 启动 4 个实例,分别用 GPU 1,2,3,4 +for GPU_ID in 1 2 3 4; do + export WAN22_GPU_ID=$GPU_ID + export CUDA_VISIBLE_DEVICES=$GPU_ID + export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True + export PYTHONPATH=/data/ymq/wan22-service/repo:~/Win2.2 + + nohup py3/bin/python ah.py > nohup_gpu${GPU_ID}.out 2>&1 & + echo "Started wan22-service instance on GPU $GPU_ID, PID: $!" + sleep 1 +done + +echo "All instances started. Check status with: ps aux | grep 'python ah.py'" diff --git a/workers/wan22_wrapper.py b/workers/wan22_wrapper.py index 903d839..f6c15e5 100644 --- a/workers/wan22_wrapper.py +++ b/workers/wan22_wrapper.py @@ -77,7 +77,28 @@ class Wan22: self.pipeline = self._build_pipeline() + # ========================= + # pipeline init + # 注意检查顺序:ti2v 必须在 t2v/i2v 之前检查 + # 因为 "ti2v" 同时包含 "t2v" 和 "i2v" 子串 + # ========================= def _build_pipeline(self): + if "s2v" in self.task: + return wan.WanS2V( + config=self.cfg, + checkpoint_dir=self.ckpt_dir, + device_id=self.device_id, + rank=0, + ) + + if "ti2v" in self.task: + return wan.WanTI2V( + config=self.cfg, + checkpoint_dir=self.ckpt_dir, + device_id=self.device_id, + rank=0, + ) + if "t2v" in self.task: return wan.WanT2V( config=self.cfg, @@ -94,24 +115,11 @@ class Wan22: rank=0, ) - if "ti2v" in self.task: - return wan.WanTI2V( - config=self.cfg, - checkpoint_dir=self.ckpt_dir, - device_id=self.device_id, - rank=0, - ) - - if "s2v" in self.task: - return wan.WanS2V( - config=self.cfg, - checkpoint_dir=self.ckpt_dir, - device_id=self.device_id, - rank=0, - ) - raise ValueError(self.task) + # ========================= + # prompt expand + # ========================= def _expand(self, prompt, image=None): if not self.use_prompt_extend: return prompt @@ -119,6 +127,9 @@ class Wan22: out = self.prompt_expander(prompt, image=image) return out.prompt if out.status else prompt + # ========================= + # OpenAI response packer + # ========================= def _pack(self, prompt, video_path, seed): return { "id": f"wan_{uuid.uuid4().hex}", @@ -129,10 +140,13 @@ class Wan22: "seed": seed, } + # ========================= + # 主入口(统一 + 串行锁) + # 注意 generate() 内的检查顺序必须与 _build_pipeline 一致 + # ========================= def generate(self, **kwargs): """ - OpenAI-style unified entry. - 全局串行锁保证GPU安全。 + OpenAI-style unified entry """ with _GLOBAL_INFER_LOCK: @@ -149,52 +163,7 @@ class Wan22: seed = self.seed - if "t2v" in self.task: - video = self.pipeline.generate( - prompt, - size=size_cfg, - frame_num=kwargs.get("frame_num"), - shift=kwargs.get("shift"), - sample_solver=kwargs.get("solver", "unipc"), - sampling_steps=kwargs.get("steps"), - guide_scale=kwargs.get("guide_scale"), - seed=seed, - offload_model=self.offload_model, - ) - - elif "i2v" in self.task: - img = Image.open(image_path).convert("RGB") - video = self.pipeline.generate( - prompt, - img, - size=size_cfg, - max_area=MAX_AREA_CONFIGS[size], - frame_num=kwargs.get("frame_num"), - shift=kwargs.get("shift"), - sample_solver=kwargs.get("solver", "unipc"), - sampling_steps=kwargs.get("steps"), - guide_scale=kwargs.get("guide_scale"), - seed=seed, - offload_model=self.offload_model, - ) - - elif "ti2v" in self.task: - img = Image.open(image_path).convert("RGB") - video = self.pipeline.generate( - prompt, - img=img, - size=size_cfg, - max_area=MAX_AREA_CONFIGS[size], - frame_num=kwargs.get("frame_num"), - shift=kwargs.get("shift"), - sample_solver=kwargs.get("solver", "unipc"), - sampling_steps=kwargs.get("steps"), - guide_scale=kwargs.get("guide_scale"), - seed=seed, - offload_model=self.offload_model, - ) - - elif "s2v" in self.task: + if "s2v" in self.task: video = self.pipeline.generate( input_prompt=prompt, ref_image_path=image_path, @@ -207,15 +176,60 @@ class Wan22: pose_video=kwargs.get("pose_video"), max_area=MAX_AREA_CONFIGS[size], infer_frames=kwargs.get("infer_frames", 80), - shift=kwargs.get("shift"), + shift=kwargs.get("shift") or 5.0, sample_solver=kwargs.get("solver", "unipc"), - sampling_steps=kwargs.get("steps"), - guide_scale=kwargs.get("guide_scale"), + sampling_steps=kwargs.get("steps") or 30, + guide_scale=kwargs.get("guide_scale") or 5.0, seed=seed, offload_model=self.offload_model, init_first_frame=kwargs.get("start_from_ref", False), ) + elif "ti2v" in self.task: + img = Image.open(image_path).convert("RGB") if image_path else None + video = self.pipeline.generate( + prompt, + img=img, + size=size_cfg, + max_area=MAX_AREA_CONFIGS[size], + frame_num=kwargs.get("frame_num") or 81, + shift=kwargs.get("shift") or 5.0, + sample_solver=kwargs.get("solver", "unipc"), + sampling_steps=kwargs.get("steps") or 30, + guide_scale=kwargs.get("guide_scale") or 5.0, + seed=seed, + offload_model=self.offload_model, + ) + + elif "t2v" in self.task: + video = self.pipeline.generate( + prompt, + size=size_cfg, + frame_num=kwargs.get("frame_num") or 81, + shift=kwargs.get("shift") or 5.0, + sample_solver=kwargs.get("solver", "unipc"), + sampling_steps=kwargs.get("steps") or 30, + guide_scale=kwargs.get("guide_scale") or 5.0, + seed=seed, + offload_model=self.offload_model, + ) + + elif "i2v" in self.task: + img = Image.open(image_path).convert("RGB") + video = self.pipeline.generate( + prompt, + img, + size=size_cfg, + max_area=MAX_AREA_CONFIGS[size], + frame_num=kwargs.get("frame_num") or 81, + shift=kwargs.get("shift") or 5.0, + sample_solver=kwargs.get("solver", "unipc"), + sampling_steps=kwargs.get("steps") or 30, + guide_scale=kwargs.get("guide_scale") or 5.0, + seed=seed, + offload_model=self.offload_model, + ) + else: raise ValueError(self.task)