[diffusion][feature] Add LingBot-Video T2I and TI2V generation modes (#5311)

Signed-off-by: wtz2333 <2955110911@qq.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
wtz2333andClaude authored and GitHub committed 2026-08-10 10:02:31 +08:00
1 parent 11f633aa83
commit 36135ef09a
28 files changed
+2410 -98

No files matched your search

@@ -33,6 +33,25 @@ This folder provides a unified CLI script for image-to-video generation using vL
Default model: `Wan-AI/Wan2.2-I2V-A14B-Diffusers`.
### LingBot-Video TI2V
LingBot-Video accepts exactly one first-frame image and uses the same dense or
MoE checkpoint as its T2I and T2V modes:
```bash
python examples/offline_inference/image_to_video/image_to_video.py \
--model robbyant/lingbot-video-dense-1.3b \
--model-class-name LingBotVideoPipeline \
--image /path/to/input.png \
--prompt "the fox looks toward the camera" \
--height 192 --width 320 --num-frames 9 --num-inference-steps 2 \
--guidance-scale 3.0 --flow-shift 3.0 --fps 24 \
--output lingbot_ti2v.mp4
```
The input image keeps its original geometry until the LingBot pipeline applies
its model-specific resize and center crop.
## Prerequisites
Download the example image used in the snippets below:
@@ -43,6 +43,23 @@ This folder provides several entrypoints for experimenting with text-to-image di
Default model: `Qwen/Qwen-Image`
### LingBot-Video
LingBot-Video uses the same checkpoint for image and video generation. Run the
shared T2I entry point with:
```bash
python examples/offline_inference/text_to_image/text_to_image.py \
--model robbyant/lingbot-video-dense-1.3b \
--prompt "a red fox standing in fresh snow" \
--height 192 --width 320 --num-inference-steps 2 \
--guidance-scale 3.0 --extra-body '{"flow_shift":3.0}' \
--output lingbot_t2i.png
```
The LingBot prompt builder selects the `image` output modality and requests one
frame. Passing an input image is not supported in LingBot T2I mode.
## Quick Start
### Python API
@@ -55,6 +55,21 @@ python text_to_video.py \
--output vace_t2v_output.mp4
```
### LingBot-Video
The dedicated LingBot example explicitly selects the video output modality:
```bash
python examples/offline_inference/text_to_video/text_to_video_lingbot.py \
--model robbyant/lingbot-video-dense-1.3b \
--height 192 --width 320 --num-frames 9 --num-inference-steps 2 \
--output lingbot_t2v.mp4
```
Use the same command with `robbyant/lingbot-video-moe-30b-a3b` for the MoE
checkpoint. Requested frame counts are rounded upward to the causal
VAE's `4n+1` grid.
### LTX-2
```bash
@@ -20,9 +20,9 @@ from vllm_omni.platforms import current_omni_platform
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Generate text-to-video with a LingBot-Video checkpoint.")
parser.add_argument("--model", default="/home/models/lingbot-video-dense-1.3b")
parser.add_argument("--model", default="robbyant/lingbot-video-dense-1.3b")
parser.add_argument("--prompt", default="a robotic arm picks up a red block")
parser.add_argument("--negative-prompt", default="")
parser.add_argument("--negative-prompt", default=None)
parser.add_argument("--output", default="lingbot_video_output.mp4")
parser.add_argument("--height", type=int, default=192)
parser.add_argument("--width", type=int, default=320)
@@ -58,9 +58,12 @@ def _to_frame_array(video: Any) -> np.ndarray:
if isinstance(video, torch.Tensor):
tensor = video.detach().cpu()
if tensor.dim() == 5:
tensor = tensor[0] if tensor.shape[1] not in (3, 4) else tensor[0].permute(1, 2, 3, 0)
elif tensor.dim() == 4 and tensor.shape[0] in (3, 4):
tensor = tensor.permute(1, 2, 3, 0)
tensor = tensor[0]
if tensor.dim() == 4 and tensor.shape[-1] not in (3, 4):
if tensor.shape[0] in (3, 4):
tensor = tensor.permute(1, 2, 3, 0)
elif tensor.shape[1] in (3, 4):
tensor = tensor.permute(0, 2, 3, 1)
if tensor.is_floating_point():
if float(tensor.min()) < 0.0:
tensor = tensor.clamp(-1, 1) * 0.5 + 0.5
@@ -87,8 +90,12 @@ def main() -> None:
parallel_config=DiffusionParallelConfig(),
)
prompt: dict[str, str] = {"prompt": args.prompt}
if args.negative_prompt:
prompt: dict[str, Any] = {
"prompt": args.prompt,
"modalities": ["video"],
"num_frames": args.num_frames,
}
if args.negative_prompt is not None:
prompt["negative_prompt"] = args.negative_prompt
sampling_params = OmniDiffusionSamplingParams(
@@ -0,0 +1,74 @@
# LingBot-Video
LingBot-Video uses one `LingBotVideoPipeline` for text-to-image (T2I),
text-to-video (T2V), and text-image-to-video (TI2V) generation. Both the dense
and MoE checkpoints use the same request format.
## Start the server
```bash
MODEL=robbyant/lingbot-video-dense-1.3b bash run_server.sh
```
The MoE checkpoint uses the same server and request scripts, but requires
substantially more GPU memory:
```bash
MODEL=robbyant/lingbot-video-moe-30b-a3b bash run_server.sh
```
## Text to image
The image endpoint selects T2I mode and always generates one frame:
```bash
bash run_curl_text_to_image.sh
```
The script sends a `320x192`, two-step smoke request and writes
`lingbot_t2i.png`.
## Text or text-image to video
Run the video script without an image to select T2V mode:
```bash
bash run_curl_text_image_to_video.sh
```
Pass a first-frame image to the same script to select TI2V mode:
```bash
INPUT_IMAGE=/path/to/input.png bash run_curl_text_image_to_video.sh
```
The client scripts omit the optional `model` request field, so they target
whichever dense or MoE checkpoint the server loaded. The video example uses the
lightweight `320x192`, 9-frame, two-step configuration.
Until the shared `/v1/videos` reference-image resizing is removed, TI2V target
dimensions must be sent through `extra_params`, for example
`{"size":"320x192"}`. Do not use the top-level `size`, `width`, or `height`
fields for TI2V because the serving layer currently applies those dimensions
to the reference image before the model receives it. T2V requests can continue
to use the top-level dimension fields.
LingBot video frame counts use the causal VAE `4n+1` grid. The pipeline rounds
any requested frame count upward to the next valid value. An explicit
`num_frames` takes precedence over `seconds`; otherwise, the server first
resolves `seconds * fps` and the pipeline applies the same alignment.
Official `resolution`/`ratio` presets can be sent through `extra_params`, for
example `{"resolution":"720p","ratio":"16:9"}`. The `2k` and `4k` entries
only define output dimensions; whether they run successfully depends on the
checkpoint, GPU memory, and memory optimizations available in the deployment.
For `/v1/images/generations`, the server resolves these aliases to their final
pixel dimensions before applying `--max-generated-image-size`. Requests above
the configured limit return HTTP 400 before engine dispatch. LingBot produces
one output per prompt; image requests with `n>1` are also rejected with HTTP
400.
LingBot TI2V accepts exactly one image reference. Image editing, video
references, audio references, batching, and Refiner execution are not supported
by this pipeline mode.
@@ -0,0 +1,67 @@
#!/bin/bash
set -euo pipefail
if [ -n "${INPUT_IMAGE:-}" ] && [ ! -f "${INPUT_IMAGE}" ]; then
echo "INPUT_IMAGE does not exist: ${INPUT_IMAGE}"
exit 1
fi
BASE_URL="${BASE_URL:-http://localhost:8099}"
POLL_INTERVAL="${POLL_INTERVAL:-2}"
if [ -n "${INPUT_IMAGE:-}" ]; then
default_output="lingbot_ti2v.mp4"
else
default_output="lingbot_t2v.mp4"
fi
OUTPUT_PATH="${OUTPUT_PATH:-${default_output}}"
curl_args=(
-sS -X POST "${BASE_URL}/v1/videos"
-H "Accept: application/json"
-F "prompt=the subject turns toward the camera with smooth natural motion"
-F "num_frames=9"
-F "fps=24"
-F "num_inference_steps=2"
-F "guidance_scale=3.0"
-F "flow_shift=3.0"
-F "seed=42"
)
if [ -n "${INPUT_IMAGE:-}" ]; then
curl_args+=(
-F "input_reference=@${INPUT_IMAGE}"
-F 'extra_params={"size":"320x192"}'
)
else
curl_args+=(-F "width=320" -F "height=192")
fi
create_response="$(curl "${curl_args[@]}")"
video_id="$(echo "${create_response}" | jq -er '.id')"
echo "Created video job ${video_id}"
while true; do
status_response="$(curl -sS "${BASE_URL}/v1/videos/${video_id}")"
status="$(echo "${status_response}" | jq -er '.status')"
case "${status}" in
queued|in_progress)
sleep "${POLL_INTERVAL}"
;;
completed)
break
;;
failed)
echo "${status_response}" | jq .
exit 1
;;
*)
echo "Unexpected video job status: ${status}"
exit 1
;;
esac
done
curl -sS -L "${BASE_URL}/v1/videos/${video_id}/content" -o "${OUTPUT_PATH}"
echo "Saved video to ${OUTPUT_PATH}"
@@ -0,0 +1,20 @@
#!/bin/bash
set -euo pipefail
BASE_URL="${BASE_URL:-http://localhost:8099}"
OUTPUT_PATH="${OUTPUT_PATH:-lingbot_t2i.png}"
curl -sS -X POST "${BASE_URL}/v1/images/generations" \
-H "Content-Type: application/json" \
-d "{
\"prompt\": \"a red fox standing in fresh snow\",
\"size\": \"320x192\",
\"num_inference_steps\": 2,
\"guidance_scale\": 3.0,
\"flow_shift\": 3.0,
\"seed\": 42,
\"response_format\": \"b64_json\"
}" | jq -er '.data[0].b64_json' | base64 -d > "${OUTPUT_PATH}"
echo "Saved image to ${OUTPUT_PATH}"
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
set -euo pipefail
MODEL="${MODEL:-robbyant/lingbot-video-dense-1.3b}"
PORT="${PORT:-8099}"
vllm serve "${MODEL}" --omni \
--model-class-name LingBotVideoPipeline \
--port "${PORT}"