Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b90064d15 | ||
|
|
718ed1537a | ||
|
|
842d26657b | ||
|
|
da53f27258 |
@@ -8,4 +8,3 @@
|
||||
{"name": "decompose-pipeline-pr", "description": "Decompose an oversized FastVideo pipeline PR into a stack of independently-reviewable PRs. Tiers the diff by blast radius (invisible / dead code / cross-cutting infra / activation), produces a branch graph and worktree bootstrap, drafts the AGENTS.md manifest, flags missing tests on cross-cutting infra changes, and extracts lessons from the PR body. Worked example: PR #1280 daVinci-MagiHuman (9.8k LOC) decomposed into 10 stacked PRs.", "path": "decompose-pipeline-pr/SKILL.md", "status": "tested", "trust": "medium"}
|
||||
{"name": "reseed-performance-baseline", "description": "Re-seed the HF performance-tracking baseline for an intentional runtime, dependency, or environment-caused benchmark shift. Use when performance CI fails because metrics such as latency, throughput, component time, or peak memory changed for an accepted reason and the rolling median baseline must be advanced by replicating one reviewed shifted source result into three success=true records, or five records when explicitly requested", "path": "reseed-performance-baseline/SKILL.md", "status": "draft", "trust": "low"}
|
||||
{"name": "add-model", "description": "Add a new model (or variant) to FastVideo: DiT + configs + pipeline + presets + registry + tests. Walks through FastVideo's single stage-based pipeline architecture with exact file paths and registration hooks.", "path": "add-model/SKILL.md", "status": "draft", "trust": "low"}
|
||||
{"name": "release", "description": "Cut a new FastVideo release. Bumps the version across the three authoritative files (fastvideo/version.py, pyproject.toml, pyproject_other.toml), opens a [chore]: release PR, and documents the post-merge tag + GitHub release ritual. Triggers on requests like \"release X.Y.Z\", \"cut a release\", \"bump version to X.Y.Z\", \"publish to PyPI\".", "path": "release/SKILL.md", "status": "draft", "trust": "low"}
|
||||
|
||||
@@ -1,248 +0,0 @@
|
||||
---
|
||||
name: release
|
||||
description: Cut a new FastVideo release. Bumps the version across the three authoritative files (fastvideo/version.py, pyproject.toml, pyproject_other.toml), opens a [chore]: release PR, and documents the post-merge tag + GitHub release ritual. Triggers on requests like "release X.Y.Z", "cut a release", "bump version to X.Y.Z", "publish to PyPI".
|
||||
---
|
||||
|
||||
# FastVideo release skill
|
||||
|
||||
End-to-end recipe for cutting a FastVideo release. The PyPI publish is automatic — pushing a `pyproject.toml` version change to `main` triggers `.github/workflows/publish-fastvideo.yml`. Your job is to land the version bump cleanly and follow up with a git tag + GitHub Release for the changelog.
|
||||
|
||||
## Inputs
|
||||
|
||||
- `${NEW}` — the new version (e.g. `0.2.0`). Required.
|
||||
- `${OLD}` — the current version. Auto-detect with: `grep -oP '__version__ = "\K[^"]+' fastvideo/version.py` from the repo root.
|
||||
|
||||
## When to use
|
||||
|
||||
Trigger phrases: "release X.Y.Z", "cut a release", "bump version to X.Y.Z", "publish to PyPI", "tag a release".
|
||||
|
||||
## Files to update (3 — the authoritative list)
|
||||
|
||||
These are the ONLY files that carry the version as a Python/package declaration:
|
||||
|
||||
| File | Line | Change |
|
||||
|---|---|---|
|
||||
| `fastvideo/version.py` | 1 | `__version__ = "${OLD}"` → `__version__ = "${NEW}"` |
|
||||
| `pyproject.toml` | 7 | `version = "${OLD}"` → `version = "${NEW}"` |
|
||||
| `pyproject_other.toml` | 7 | `version = "${OLD}"` → `version = "${NEW}"` |
|
||||
|
||||
`fastvideo/__init__.py` re-exports `__version__` from `fastvideo.version`, so no edit needed there.
|
||||
|
||||
## Files NOT to touch
|
||||
|
||||
- `apps/dreamverse/pyproject.toml` — declares `"fastvideo>=X.Y.Z"` as a floor. A new release usually still satisfies the floor; bumping it is a separate policy call (does dreamverse strictly require the new version?). Leave alone unless explicitly asked.
|
||||
- `.agents/memory/**/*.md` — historical notes; the version strings in there are snapshots, not declarations.
|
||||
- `examples/`, `docs/` — version mentions are illustrative; not authoritative.
|
||||
- `uv.lock` — main does NOT track a `uv.lock`. Do NOT run `uv lock` as part of a release.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Verify clean state
|
||||
|
||||
```bash
|
||||
# From the primary FastVideo jj workspace
|
||||
jj git fetch
|
||||
OLD=$(grep -oP '__version__ = "\K[^"]+' fastvideo/version.py)
|
||||
echo "current: $OLD → target: $NEW"
|
||||
```
|
||||
|
||||
Confirm `$NEW > $OLD` follows semver. Check prior tags for the pattern:
|
||||
```bash
|
||||
gh release list --repo hao-ai-lab/FastVideo --limit 5
|
||||
```
|
||||
|
||||
### 2. Create a dedicated jj workspace + bookmark
|
||||
|
||||
```bash
|
||||
WS=/home/william5lin/FastVideo_release_${NEW//./_}
|
||||
jj workspace add --name release-${NEW//./-} "$WS"
|
||||
cd "$WS"
|
||||
jj new main@origin -m "[chore]: release v${NEW}"
|
||||
jj bookmark create chore/release-${NEW} -r @
|
||||
```
|
||||
|
||||
### 3. Apply the 3-file bump
|
||||
|
||||
Use the `edit` tool or `sed -i` with exact context. Example with sed:
|
||||
```bash
|
||||
sed -i "s/__version__ = \"${OLD}\"/__version__ = \"${NEW}\"/" fastvideo/version.py
|
||||
sed -i "0,/version = \"${OLD}\"/s//version = \"${NEW}\"/" pyproject.toml
|
||||
sed -i "0,/version = \"${OLD}\"/s//version = \"${NEW}\"/" pyproject_other.toml
|
||||
```
|
||||
(The `0,/.../s//.../` form replaces only the FIRST match in each `pyproject*.toml`, since `${OLD}` might appear elsewhere as a constraint.)
|
||||
|
||||
### 4. Verify
|
||||
|
||||
```bash
|
||||
jj diff --name-only -r @ # MUST be exactly 3 files
|
||||
jj diff --stat -r @ # MUST be +3 / -3
|
||||
grep -nE "${OLD//./\\.}" fastvideo/version.py pyproject.toml pyproject_other.toml
|
||||
# expect NO matches in the three files
|
||||
```
|
||||
|
||||
### 5. Lint
|
||||
|
||||
```bash
|
||||
pre-commit run --files fastvideo/version.py pyproject.toml pyproject_other.toml
|
||||
```
|
||||
Must pass. Never `--no-verify`.
|
||||
|
||||
### 6. Describe + push
|
||||
|
||||
```bash
|
||||
jj describe -m "[chore]: release v${NEW}
|
||||
|
||||
Bumps FastVideo from ${OLD} to ${NEW}.
|
||||
|
||||
Files updated:
|
||||
fastvideo/version.py
|
||||
pyproject.toml
|
||||
pyproject_other.toml
|
||||
|
||||
Note: pushing this to main triggers .github/workflows/publish-fastvideo.yml,
|
||||
which detects the pyproject.toml version change and publishes to PyPI.
|
||||
Tag v${NEW} + GitHub release notes follow merge."
|
||||
|
||||
jj git push --bookmark chore/release-${NEW}
|
||||
```
|
||||
|
||||
### 7. Open PR
|
||||
|
||||
```bash
|
||||
gh pr create \
|
||||
--repo hao-ai-lab/FastVideo \
|
||||
--base main \
|
||||
--head chore/release-${NEW} \
|
||||
--title "[chore]: release v${NEW}" \
|
||||
--body-file - <<EOF
|
||||
## Summary
|
||||
|
||||
Bumps FastVideo from \`${OLD}\` to \`${NEW}\`.
|
||||
|
||||
## Files updated (3)
|
||||
|
||||
- \`fastvideo/version.py\`
|
||||
- \`pyproject.toml\`
|
||||
- \`pyproject_other.toml\`
|
||||
|
||||
## Out of scope
|
||||
|
||||
\`apps/dreamverse/pyproject.toml\` floor (\`fastvideo>=${OLD}\`) — \`${NEW}\` satisfies it; bumping is a separate policy call.
|
||||
|
||||
## After merge
|
||||
|
||||
\`.github/workflows/publish-fastvideo.yml\` auto-publishes to PyPI on push-to-main when \`pyproject.toml\` changes.
|
||||
|
||||
Manual follow-up:
|
||||
- Tag the merge commit: \`git tag v${NEW} <merge-sha> && git push origin v${NEW}\`
|
||||
- Create GitHub Release \`v${NEW}\` matching the prior \`Release X.Y.Z\` pattern.
|
||||
EOF
|
||||
```
|
||||
|
||||
### 8. Post-merge ritual (do AFTER the PR merges)
|
||||
|
||||
1. **Tag the merge commit**:
|
||||
```bash
|
||||
git fetch origin
|
||||
MERGE_SHA=$(gh pr view <PR-NUMBER> --repo hao-ai-lab/FastVideo --json mergeCommit --jq .mergeCommit.oid)
|
||||
git tag v${NEW} ${MERGE_SHA}
|
||||
git push origin v${NEW}
|
||||
```
|
||||
2. **Confirm PyPI publish workflow ran**:
|
||||
```bash
|
||||
gh run list --repo hao-ai-lab/FastVideo --workflow publish-fastvideo.yml --limit 3
|
||||
```
|
||||
3. **Create the GitHub Release**:
|
||||
```bash
|
||||
gh release create v${NEW} \
|
||||
--repo hao-ai-lab/FastVideo \
|
||||
--title "Release ${NEW}" \
|
||||
--notes "<changelog highlights — what shipped since v${OLD}>" \
|
||||
--target main
|
||||
```
|
||||
Use `gh release view v${OLD}` to mirror tone/structure from the prior release.
|
||||
4. **Cleanup**: after merge + tag + release land, tear down the workspace:
|
||||
```bash
|
||||
jj workspace forget release-${NEW//./-}
|
||||
rm -rf "$WS"
|
||||
jj bookmark delete chore/release-${NEW}
|
||||
```
|
||||
|
||||
## Verification gates (must all pass before pushing)
|
||||
|
||||
- `jj diff --name-only -r @` returns exactly 3 files
|
||||
- `jj diff --stat -r @` shows `+3 / -3`
|
||||
- `grep -E "${OLD//./\\.}" fastvideo/version.py pyproject.toml pyproject_other.toml` returns no matches
|
||||
- `pre-commit run --files <the-three>` passes
|
||||
- No `uv.lock` in the change
|
||||
- No source-code files touched
|
||||
|
||||
## Conventions (enforced)
|
||||
|
||||
- Commit subject: `[chore]: release v${NEW}` (under 72 chars).
|
||||
- NEVER add AI co-author trailers (`Co-Authored-By: Claude`, "Generated with…", etc.).
|
||||
- NEVER `--no-verify`.
|
||||
- NEVER `uv lock` as part of a release — main doesn't track the lockfile.
|
||||
- Tag format: `vX.Y.Z` (with leading `v`), matching prior releases.
|
||||
|
||||
## Why three files?
|
||||
|
||||
`pyproject.toml` and `pyproject_other.toml` are two co-existing project metadata files (the project ships both — the latter is a slimmer variant without dreamverse/job-runner extras). Both carry an authoritative `version = "X.Y.Z"` field and must stay in lock-step. `fastvideo/version.py` is the runtime source of truth re-exported by `fastvideo/__init__.py`.
|
||||
|
||||
## Publish workflow contract
|
||||
|
||||
`.github/workflows/publish-fastvideo.yml` triggers on `push` to `main` when `pyproject.toml` changes. It compares the new `version` field to the previous commit's `version` field and, if different, builds + publishes to PyPI. The version bump in `pyproject_other.toml` does NOT trigger the workflow (only `pyproject.toml` is in the `paths:` filter), but keeping the two in sync prevents installer surprises for users of the alternate metadata file.
|
||||
|
||||
## PyPI publish failure modes
|
||||
|
||||
The publish workflow ran on the merge commit but the PyPI upload can still fail at the OIDC trusted-publishing exchange. Always verify the workflow succeeded — do not assume "merge implies published":
|
||||
|
||||
```bash
|
||||
gh run list --repo hao-ai-lab/FastVideo --workflow publish-fastvideo.yml --limit 3
|
||||
```
|
||||
|
||||
Look for the run on the release merge commit. If it shows `failure`, dump the failed log:
|
||||
|
||||
```bash
|
||||
gh run view <run-id> --repo hao-ai-lab/FastVideo --log-failed | tail -80
|
||||
```
|
||||
|
||||
### Known failure: `invalid-publisher` (Trusted Publisher claim mismatch)
|
||||
|
||||
The most common failure surfaces as:
|
||||
|
||||
```
|
||||
Trusted publishing exchange failure:
|
||||
* `invalid-publisher`: valid token, but no corresponding publisher
|
||||
(Publisher with matching claims was not found)
|
||||
* environment: MISSING
|
||||
```
|
||||
|
||||
This means the PyPI Trusted Publisher registered for the project expects an `environment` claim (e.g. `pypi`) that the workflow job does not set. Two recovery paths:
|
||||
|
||||
**A. Fix the trusted publisher + re-run the workflow** (cleaner long-term):
|
||||
1. On `pypi.org/manage/project/fastvideo/settings/publishing/`, either remove the `Environment name` field from the registered publisher, OR add `environment: pypi` (matching the existing PyPI config) to the `build-publish-main` job in `.github/workflows/publish-fastvideo.yml`.
|
||||
2. Re-run the failed workflow:
|
||||
```bash
|
||||
gh run rerun <run-id> --repo hao-ai-lab/FastVideo --failed
|
||||
```
|
||||
|
||||
**B. Manual one-shot publish** (faster, no infra change):
|
||||
|
||||
```bash
|
||||
git checkout <merge-sha> # the v${NEW} merge commit on main
|
||||
uv build # builds sdist + wheel into dist/
|
||||
uv publish --token <PYPI_TOKEN> # or: twine upload dist/*
|
||||
```
|
||||
|
||||
PyPI is **immutable per version** — if any artifact for `${NEW}` got uploaded (sdist or wheel), you cannot re-upload it. Check before retrying:
|
||||
|
||||
```bash
|
||||
curl -s https://pypi.org/pypi/fastvideo/${NEW}/json | python3 -c "import sys,json; d=json.load(sys.stdin); print('on pypi:', list(d['urls'][0].keys()) if d.get('urls') else 'NOT_PUBLISHED')"
|
||||
```
|
||||
|
||||
If `NOT_PUBLISHED`, either recovery path works. If anything is already up, you have to cut a `${NEW}.postN` patch release instead.
|
||||
|
||||
### Tag and GitHub Release are independent
|
||||
|
||||
The `git tag v${NEW}` and `gh release create v${NEW}` steps are **independent of PyPI publish success**. If you created the tag + release before noticing the publish failure, that's fine — keep them; just complete the PyPI publish via path A or B above. Do NOT delete and re-create the tag, because doing so will cause confusion in dependents that pin to the tag.
|
||||
@@ -334,7 +334,7 @@ class TestLtx2Presets:
|
||||
import fastvideo.registry # noqa: F401
|
||||
presets = get_presets_for_family("ltx2")
|
||||
names = {p.name for p in presets}
|
||||
assert names == {"ltx2_base", "ltx2_3_base", "ltx2_distilled", "ltx2_two_stage"}
|
||||
assert names == {"ltx2_base", "ltx2_distilled", "ltx2_two_stage"}
|
||||
|
||||
def test_ltx2_base_lookup(self) -> None:
|
||||
import fastvideo.registry # noqa: F401
|
||||
|
||||
+17
-17
@@ -15,16 +15,16 @@ classifiers = [
|
||||
|
||||
dependencies = [
|
||||
# Core Libraries
|
||||
"scipy>=1.14.1",
|
||||
"six>=1.16.0",
|
||||
"h5py>=3.12.1",
|
||||
"scipy==1.14.1",
|
||||
"six==1.16.0",
|
||||
"h5py==3.12.1",
|
||||
"requests>=2.32.2",
|
||||
|
||||
# Machine Learning & Transformers
|
||||
"transformers>=4.57.3",
|
||||
"transformers==4.57.3",
|
||||
"tokenizers>=0.20.1",
|
||||
"sentencepiece>=0.2.0",
|
||||
"timm>=1.0.11",
|
||||
"sentencepiece==0.2.0",
|
||||
"timm==1.0.11",
|
||||
"peft>=0.15.0",
|
||||
"diffusers>=0.33.1",
|
||||
"torch==2.11.0",
|
||||
@@ -35,21 +35,21 @@ dependencies = [
|
||||
"accelerate==1.0.1",
|
||||
|
||||
# Computer Vision & Image Processing
|
||||
"opencv-python>=4.10.0.84",
|
||||
"opencv-python==4.10.0.84",
|
||||
"pillow>=10.3.0",
|
||||
"imageio>=2.36.0",
|
||||
"imageio-ffmpeg>=0.5.1",
|
||||
"imageio==2.36.0",
|
||||
"imageio-ffmpeg==0.5.1",
|
||||
"einops",
|
||||
|
||||
# Experiment Tracking & Logging
|
||||
"wandb>=0.21.0",
|
||||
"loguru",
|
||||
"test-tube>=0.7.5",
|
||||
"test-tube==0.7.5",
|
||||
|
||||
# Miscellaneous Utilities
|
||||
"tqdm",
|
||||
"pytest",
|
||||
"PyYAML>=6.0.1",
|
||||
"PyYAML==6.0.1",
|
||||
"protobuf>=5.28.3",
|
||||
"gradio==5.32.0",
|
||||
"moviepy>=2.0.0",
|
||||
@@ -73,17 +73,17 @@ dependencies = [
|
||||
# Training Dependencies
|
||||
"torchdata",
|
||||
"pyarrow",
|
||||
"datasets>=4.0.0",
|
||||
"datasets==4.0.0",
|
||||
"av",
|
||||
|
||||
# Preprocessing Dependencies
|
||||
"torchcodec",
|
||||
"ray>=2.49.1",
|
||||
"ftfy>=6.3.1",
|
||||
"ftfy==6.3.1",
|
||||
|
||||
# Job Runner
|
||||
"fastapi>=0.129.0",
|
||||
"uvicorn>=0.41.0"
|
||||
"fastapi==0.129.0",
|
||||
"uvicorn==0.41.0"
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
@@ -130,7 +130,7 @@ lint = [
|
||||
|
||||
test = [
|
||||
"av",
|
||||
"pytorch-msssim>=1.0.0",
|
||||
"pytorch-msssim==1.0.0",
|
||||
"pytest",
|
||||
"pandas",
|
||||
"plotly",
|
||||
@@ -170,7 +170,7 @@ streaming = [
|
||||
]
|
||||
|
||||
dreamverse = [
|
||||
"uvicorn[standard]>=0.41.0",
|
||||
"uvicorn[standard]==0.41.0",
|
||||
"cerebras-cloud-sdk",
|
||||
"flash-attn-cute @ git+https://github.com/XOR-op/flash-attention.git@fa4-compile#subdirectory=flash_attn/cute",
|
||||
"flashinfer-python",
|
||||
|
||||
+13
-13
@@ -15,16 +15,16 @@ classifiers = [
|
||||
|
||||
dependencies = [
|
||||
# Core Libraries
|
||||
"scipy>=1.14.1",
|
||||
"six>=1.16.0",
|
||||
"h5py>=3.12.1",
|
||||
"scipy==1.14.1",
|
||||
"six==1.16.0",
|
||||
"h5py==3.12.1",
|
||||
"requests>=2.32.2",
|
||||
|
||||
# Machine Learning & Transformers
|
||||
"transformers>=4.57.3",
|
||||
"transformers==4.57.3",
|
||||
"tokenizers>=0.20.1",
|
||||
"sentencepiece>=0.2.0",
|
||||
"timm>=1.0.11",
|
||||
"sentencepiece==0.2.0",
|
||||
"timm==1.0.11",
|
||||
"peft>=0.15.0",
|
||||
"diffusers>=0.33.1",
|
||||
"torch==2.11.0",
|
||||
@@ -34,21 +34,21 @@ dependencies = [
|
||||
"accelerate==1.0.1",
|
||||
|
||||
# Computer Vision & Image Processing
|
||||
"opencv-python>=4.10.0.84",
|
||||
"opencv-python==4.10.0.84",
|
||||
"pillow>=10.3.0",
|
||||
"imageio>=2.36.0",
|
||||
"imageio-ffmpeg>=0.5.1",
|
||||
"imageio==2.36.0",
|
||||
"imageio-ffmpeg==0.5.1",
|
||||
"einops",
|
||||
|
||||
# Experiment Tracking & Logging
|
||||
"wandb>=0.21.0",
|
||||
"loguru",
|
||||
"test-tube>=0.7.5",
|
||||
"test-tube==0.7.5",
|
||||
|
||||
# Miscellaneous Utilities
|
||||
"tqdm",
|
||||
"pytest",
|
||||
"PyYAML>=6.0.1",
|
||||
"PyYAML==6.0.1",
|
||||
"protobuf>=5.28.3",
|
||||
"gradio==5.32.0",
|
||||
"moviepy>=2.0.0",
|
||||
@@ -71,7 +71,7 @@ dependencies = [
|
||||
# Training Dependencies
|
||||
"torchdata",
|
||||
"pyarrow",
|
||||
"datasets>=4.0.0",
|
||||
"datasets==4.0.0",
|
||||
"av",
|
||||
|
||||
# Preprocessing Dependencies
|
||||
@@ -93,7 +93,7 @@ lint = [
|
||||
|
||||
test = [
|
||||
"av",
|
||||
"pytorch-msssim>=1.0.0",
|
||||
"pytorch-msssim==1.0.0",
|
||||
"pytest",
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"benchmark_id": "fastwan-t2v-1.3b-vsa-dmd-gb10-1gpu",
|
||||
"description": "FastWan2.1 T2V 1.3B DMD/VSA local Nsight profile for one DGX Spark GB10 GPU",
|
||||
"model": {
|
||||
"model_path": "FastVideo/FastWan2.1-T2V-1.3B-Diffusers",
|
||||
"model_short_name": "FastWan2.1-T2V-1.3B-DMD-VSA"
|
||||
},
|
||||
"init_kwargs": {
|
||||
"num_gpus": 1,
|
||||
"sp_size": 1,
|
||||
"tp_size": 1,
|
||||
"vae_sp": false,
|
||||
"vae_tiling": true,
|
||||
"VSA_sparsity": 0.8,
|
||||
"dmd_denoising_steps": [1000, 757, 522],
|
||||
"enable_torch_compile": false,
|
||||
"dit_cpu_offload": false,
|
||||
"dit_layerwise_offload": false,
|
||||
"vae_cpu_offload": false,
|
||||
"text_encoder_cpu_offload": true,
|
||||
"pin_cpu_memory": false,
|
||||
"text_encoder_precisions": ["fp32"]
|
||||
},
|
||||
"generation_kwargs": {
|
||||
"height": 480,
|
||||
"width": 832,
|
||||
"num_frames": 81,
|
||||
"num_inference_steps": 3,
|
||||
"seed": 1024,
|
||||
"fps": 16,
|
||||
"neg_prompt": "Bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards"
|
||||
},
|
||||
"test_prompts": [
|
||||
"Will Smith casually eats noodles, his relaxed demeanor contrasting with the energetic background of a bustling street food market. The scene captures a mix of humor and authenticity. Mid-shot framing, vibrant lighting."
|
||||
],
|
||||
"run_config": {
|
||||
"num_warmup_runs": 2,
|
||||
"num_measurement_runs": 5,
|
||||
"required_gpus": 1
|
||||
},
|
||||
"thresholds": {
|
||||
"GB10": {
|
||||
"max_generation_time_s": 120.0,
|
||||
"max_peak_memory_mb": 30000.0
|
||||
},
|
||||
"default": {
|
||||
"max_generation_time_s": 120.0,
|
||||
"max_peak_memory_mb": 30000.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"benchmark_id": "wan-t2v-1.3b-gb10-1gpu",
|
||||
"description": "Wan2.1 T2V 1.3B local Nsight profile for one DGX Spark GB10 GPU",
|
||||
"model": {
|
||||
"model_path": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
"model_short_name": "Wan2.1-T2V-1.3B"
|
||||
},
|
||||
"init_kwargs": {
|
||||
"num_gpus": 1,
|
||||
"flow_shift": 7.0,
|
||||
"sp_size": 1,
|
||||
"tp_size": 1,
|
||||
"vae_sp": false,
|
||||
"vae_tiling": true,
|
||||
"text_encoder_precisions": ["fp32"]
|
||||
},
|
||||
"generation_kwargs": {
|
||||
"height": 480,
|
||||
"width": 832,
|
||||
"num_frames": 45,
|
||||
"num_inference_steps": 4,
|
||||
"guidance_scale": 3,
|
||||
"embedded_cfg_scale": 6,
|
||||
"seed": 1024,
|
||||
"fps": 24,
|
||||
"neg_prompt": "Bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards"
|
||||
},
|
||||
"test_prompts": [
|
||||
"Will Smith casually eats noodles, his relaxed demeanor contrasting with the energetic background of a bustling street food market. The scene captures a mix of humor and authenticity. Mid-shot framing, vibrant lighting."
|
||||
],
|
||||
"run_config": {
|
||||
"num_warmup_runs": 2,
|
||||
"num_measurement_runs": 5,
|
||||
"required_gpus": 1
|
||||
},
|
||||
"thresholds": {
|
||||
"GB10": {
|
||||
"max_generation_time_s": 120.0,
|
||||
"max_peak_memory_mb": 30000.0
|
||||
},
|
||||
"default": {
|
||||
"max_generation_time_s": 120.0,
|
||||
"max_peak_memory_mb": 30000.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Profile FastWan2.1 T2V 1.3B DMD/VSA on one DGX Spark GB10 GPU with Nsight Systems.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
export CONFIG_PATH="${CONFIG_PATH:-${SCRIPT_DIR}/configs/fastwan-t2v-1.3b-vsa-dmd-gb10-1gpu.json}"
|
||||
export OUTPUT_ROOT="${OUTPUT_ROOT:-outputs/nsys/gb10-vsa}"
|
||||
export FASTVIDEO_ATTENTION_BACKEND="${FASTVIDEO_ATTENTION_BACKEND:-VIDEO_SPARSE_ATTN}"
|
||||
|
||||
exec "${SCRIPT_DIR}/profile_wan_t2v_1_3b_nsys.sh" "$@"
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Profile Wan2.1 T2V 1.3B on one DGX Spark GB10 GPU with Nsight Systems.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
export CONFIG_PATH="${CONFIG_PATH:-${SCRIPT_DIR}/configs/wan-t2v-1.3b-gb10-1gpu.json}"
|
||||
export OUTPUT_ROOT="${OUTPUT_ROOT:-outputs/nsys/gb10}"
|
||||
|
||||
exec "${SCRIPT_DIR}/profile_wan_t2v_1_3b_nsys.sh" "$@"
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env bash
|
||||
# Profile a Wan2.1 T2V 1.3B inference benchmark with Nsight Systems.
|
||||
#
|
||||
# Intended environment:
|
||||
# - FastVideo Python 3.10-3.12 environment
|
||||
# - CUDA-visible NVIDIA GPU(s)
|
||||
# - Nsight Systems CLI available on PATH
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/performance/profile_wan_t2v_1_3b_nsys.sh
|
||||
#
|
||||
# Optional overrides:
|
||||
# PYTHON_BIN=python3.12
|
||||
# NSYS_BIN=nsys
|
||||
# CONFIG_PATH=.buildkite/performance-benchmarks/tests/wan-t2v-1.3b.json
|
||||
# OUTPUT_ROOT=outputs/nsys
|
||||
# NSYS_EXTRA_ARGS="--some-nsys-flag=value"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "${REPO_ROOT}"
|
||||
|
||||
PYTHON_BIN="${PYTHON_BIN:-python}"
|
||||
NSYS_BIN="${NSYS_BIN:-nsys}"
|
||||
CONFIG_PATH="${CONFIG_PATH:-.buildkite/performance-benchmarks/tests/wan-t2v-1.3b.json}"
|
||||
OUTPUT_ROOT="${OUTPUT_ROOT:-outputs/nsys}"
|
||||
|
||||
if ! command -v "${PYTHON_BIN}" >/dev/null 2>&1; then
|
||||
echo "Python executable not found: ${PYTHON_BIN}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${PYTHON_BIN}" - <<'PY'
|
||||
import sys
|
||||
|
||||
if not ((3, 10) <= sys.version_info[:2] <= (3, 12)):
|
||||
raise SystemExit(f"Expected Python 3.10-3.12, got {sys.version.split()[0]}")
|
||||
PY
|
||||
|
||||
if ! command -v "${NSYS_BIN}" >/dev/null 2>&1; then
|
||||
echo "Nsight Systems CLI not found: ${NSYS_BIN}. Install Nsight Systems before profiling." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "${CONFIG_PATH}" ]]; then
|
||||
echo "Benchmark config not found: ${CONFIG_PATH}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BENCHMARK_ID="$("${PYTHON_BIN}" - <<'PY' "${CONFIG_PATH}"
|
||||
import json
|
||||
import sys
|
||||
|
||||
with open(sys.argv[1], encoding="utf-8") as f:
|
||||
print(json.load(f)["benchmark_id"])
|
||||
PY
|
||||
)"
|
||||
TIMESTAMP="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
RUN_DIR="${OUTPUT_ROOT}/${BENCHMARK_ID}/${TIMESTAMP}"
|
||||
|
||||
mkdir -p "${RUN_DIR}"
|
||||
|
||||
NSYS_ARGS=(
|
||||
profile
|
||||
--force-overwrite=true
|
||||
--capture-range=cudaProfilerApi
|
||||
--capture-range-end=stop
|
||||
--sample=none
|
||||
--cpuctxsw=none
|
||||
--stats=true
|
||||
--output "${RUN_DIR}/${BENCHMARK_ID}"
|
||||
)
|
||||
|
||||
NSYS_TRACE="${NSYS_TRACE:-cuda,nvtx,cudnn,cublas,osrt}"
|
||||
|
||||
if [[ "${NSYS_ENABLE_NCCL_TRACE:-0}" == "1" ]]; then
|
||||
if "${NSYS_BIN}" profile --help 2>&1 | grep -Eq "(^|[[:space:],'])nccl([[:space:],']|$)"; then
|
||||
NSYS_TRACE="${NSYS_TRACE},nccl"
|
||||
else
|
||||
echo "Skipping NCCL trace: ${NSYS_BIN} does not list nccl as a supported trace domain."
|
||||
fi
|
||||
fi
|
||||
|
||||
NSYS_ARGS+=(--trace="${NSYS_TRACE}")
|
||||
|
||||
if "${NSYS_BIN}" profile --help 2>&1 | grep -q -- "--cuda-trace-scope"; then
|
||||
NSYS_ARGS+=(--cuda-trace-scope=process-tree)
|
||||
fi
|
||||
|
||||
if "${NSYS_BIN}" profile --help 2>&1 | grep -q -- "--trace-fork-before-exec"; then
|
||||
NSYS_ARGS+=(--trace-fork-before-exec=true)
|
||||
fi
|
||||
|
||||
if [[ -n "${NSYS_EXTRA_ARGS:-}" ]]; then
|
||||
# shellcheck disable=SC2206
|
||||
EXTRA_ARGS=(${NSYS_EXTRA_ARGS})
|
||||
NSYS_ARGS+=("${EXTRA_ARGS[@]}")
|
||||
fi
|
||||
|
||||
export TOKENIZERS_PARALLELISM="${TOKENIZERS_PARALLELISM:-false}"
|
||||
export FASTVIDEO_STAGE_LOGGING="${FASTVIDEO_STAGE_LOGGING:-1}"
|
||||
|
||||
echo "Repo root: ${REPO_ROOT}"
|
||||
echo "Python: $("${PYTHON_BIN}" --version)"
|
||||
echo "Nsight Systems: $("${NSYS_BIN}" --version | head -n 1)"
|
||||
echo "Config: ${CONFIG_PATH}"
|
||||
echo "Output: ${RUN_DIR}"
|
||||
"${PYTHON_BIN}" - <<'PY'
|
||||
try:
|
||||
import torch
|
||||
except ImportError:
|
||||
print("Torch: unavailable")
|
||||
else:
|
||||
print(f"Torch: {torch.__version__}")
|
||||
print(f"Torch CUDA runtime: {torch.version.cuda}")
|
||||
print(f"CUDA available: {torch.cuda.is_available()}")
|
||||
if torch.cuda.is_available():
|
||||
print(f"CUDA device count: {torch.cuda.device_count()}")
|
||||
for idx in range(torch.cuda.device_count()):
|
||||
print(
|
||||
f"CUDA device {idx}: {torch.cuda.get_device_name(idx)} "
|
||||
f"capability={torch.cuda.get_device_capability(idx)}"
|
||||
)
|
||||
PY
|
||||
echo "Starting nsys profile..."
|
||||
|
||||
"${NSYS_BIN}" "${NSYS_ARGS[@]}" \
|
||||
"${PYTHON_BIN}" scripts/performance/run_inference_profile_from_config.py \
|
||||
--config "${CONFIG_PATH}" \
|
||||
--output-dir "${RUN_DIR}/generated_videos" \
|
||||
--summary-path "${RUN_DIR}/profile_summary.json"
|
||||
|
||||
echo "Done. Profile outputs are under ${RUN_DIR}"
|
||||
@@ -0,0 +1,348 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Run one config-driven inference benchmark under an nsys capture range."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ctypes
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from collections.abc import Mapping
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
STAGE_METRIC_MAP: dict[str, str] = {
|
||||
"TextEncodingStage": "text_encoder_time_s",
|
||||
"DenoisingStage": "dit_time_s",
|
||||
"DmdDenoisingStage": "dit_time_s",
|
||||
"DecodingStage": "vae_decode_time_s",
|
||||
}
|
||||
|
||||
_CUDART: ctypes.CDLL | None = None
|
||||
_CUDART_LIBRARY_NAMES: tuple[str, ...] = (
|
||||
"libcudart.so",
|
||||
"libcudart.so.13",
|
||||
"libcudart.so.12",
|
||||
"libcudart.so.11.0",
|
||||
)
|
||||
|
||||
|
||||
def _load_cudart() -> ctypes.CDLL:
|
||||
global _CUDART
|
||||
if _CUDART is not None:
|
||||
return _CUDART
|
||||
|
||||
errors: list[str] = []
|
||||
for name in _CUDART_LIBRARY_NAMES:
|
||||
try:
|
||||
lib = ctypes.CDLL(name)
|
||||
except OSError as exc:
|
||||
errors.append(f"{name}: {exc}")
|
||||
continue
|
||||
lib.cudaProfilerStart.restype = ctypes.c_int
|
||||
lib.cudaProfilerStop.restype = ctypes.c_int
|
||||
_CUDART = lib
|
||||
return lib
|
||||
|
||||
raise RuntimeError("Unable to load CUDA runtime for cudaProfilerApi capture range: " + "; ".join(errors))
|
||||
|
||||
|
||||
def _cuda_profiler_start() -> None:
|
||||
err = _load_cudart().cudaProfilerStart()
|
||||
if err != 0:
|
||||
raise RuntimeError(f"cudaProfilerStart failed with CUDA error code {err}")
|
||||
|
||||
|
||||
def _cuda_profiler_stop() -> None:
|
||||
err = _load_cudart().cudaProfilerStop()
|
||||
if err != 0:
|
||||
raise RuntimeError(f"cudaProfilerStop failed with CUDA error code {err}")
|
||||
|
||||
|
||||
def _make_worker_cuda_profiler_start_payload() -> bytes:
|
||||
import cloudpickle
|
||||
|
||||
library_names = _CUDART_LIBRARY_NAMES
|
||||
|
||||
def worker_cuda_profiler_start(worker_wrapper: Any) -> dict[str, Any]:
|
||||
import ctypes
|
||||
import os
|
||||
|
||||
for name in library_names:
|
||||
try:
|
||||
lib = ctypes.CDLL(name)
|
||||
break
|
||||
except OSError:
|
||||
lib = None
|
||||
if lib is None:
|
||||
raise RuntimeError("Unable to load CUDA runtime in worker")
|
||||
lib.cudaProfilerStart.restype = ctypes.c_int
|
||||
err = lib.cudaProfilerStart()
|
||||
if err != 0:
|
||||
raise RuntimeError(f"cudaProfilerStart failed with CUDA error code {err}")
|
||||
return {
|
||||
"pid": os.getpid(),
|
||||
"rank": getattr(worker_wrapper, "rpc_rank", None),
|
||||
"event": "cudaProfilerStart",
|
||||
}
|
||||
|
||||
return cloudpickle.dumps(worker_cuda_profiler_start)
|
||||
|
||||
|
||||
def _make_worker_cuda_profiler_stop_payload() -> bytes:
|
||||
import cloudpickle
|
||||
|
||||
library_names = _CUDART_LIBRARY_NAMES
|
||||
|
||||
def worker_cuda_profiler_stop(worker_wrapper: Any) -> dict[str, Any]:
|
||||
import ctypes
|
||||
import os
|
||||
import torch
|
||||
|
||||
torch.cuda.synchronize()
|
||||
for name in library_names:
|
||||
try:
|
||||
lib = ctypes.CDLL(name)
|
||||
break
|
||||
except OSError:
|
||||
lib = None
|
||||
if lib is None:
|
||||
raise RuntimeError("Unable to load CUDA runtime in worker")
|
||||
lib.cudaProfilerStop.restype = ctypes.c_int
|
||||
err = lib.cudaProfilerStop()
|
||||
if err != 0:
|
||||
raise RuntimeError(f"cudaProfilerStop failed with CUDA error code {err}")
|
||||
return {
|
||||
"pid": os.getpid(),
|
||||
"rank": getattr(worker_wrapper, "rpc_rank", None),
|
||||
"event": "cudaProfilerStop",
|
||||
}
|
||||
|
||||
return cloudpickle.dumps(worker_cuda_profiler_stop)
|
||||
|
||||
|
||||
def _load_config(path: Path) -> dict[str, Any]:
|
||||
with path.open(encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def _remap_init_kwargs(init_kwargs: dict[str, Any]) -> dict[str, Any]:
|
||||
remapped = dict(init_kwargs)
|
||||
text_enc_prec = remapped.pop("text_encoder_precisions", None)
|
||||
if text_enc_prec is not None:
|
||||
remapped["text_encoder_precisions"] = tuple(text_enc_prec)
|
||||
return remapped
|
||||
|
||||
|
||||
def _extract_component_times(result: dict[str, Any]) -> dict[str, float | None]:
|
||||
component_times: dict[str, float | None] = {
|
||||
"text_encoder_time_s": None,
|
||||
"dit_time_s": None,
|
||||
"vae_decode_time_s": None,
|
||||
}
|
||||
logging_info = result.get("logging_info")
|
||||
if logging_info is None:
|
||||
return component_times
|
||||
if isinstance(logging_info, Mapping):
|
||||
stages: dict[str, Any] = logging_info.get("stages", {}) or {}
|
||||
else:
|
||||
stages = getattr(logging_info, "stages", {}) or {}
|
||||
for stage_name, stage_data in stages.items():
|
||||
metric_key = STAGE_METRIC_MAP.get(stage_name)
|
||||
if metric_key is None:
|
||||
continue
|
||||
elapsed = stage_data.get("execution_time")
|
||||
if elapsed is None:
|
||||
continue
|
||||
existing = component_times[metric_key]
|
||||
component_times[metric_key] = elapsed if existing is None else existing + elapsed
|
||||
return component_times
|
||||
|
||||
|
||||
def _run_generation(
|
||||
generator: Any,
|
||||
prompt: str,
|
||||
generation_kwargs: dict[str, Any],
|
||||
) -> tuple[float, float, dict[str, float | None]]:
|
||||
import torch
|
||||
|
||||
torch.cuda.synchronize()
|
||||
start = time.perf_counter()
|
||||
result = generator.generate_video(prompt, **generation_kwargs)
|
||||
torch.cuda.synchronize()
|
||||
elapsed = time.perf_counter() - start
|
||||
peak_memory_mb = result.get("peak_memory_mb", 0.0) or 0.0
|
||||
return elapsed, peak_memory_mb, _extract_component_times(result)
|
||||
|
||||
|
||||
def _shutdown_executor(generator: Any | None) -> None:
|
||||
from fastvideo.worker.multiproc_executor import MultiprocExecutor
|
||||
|
||||
if generator is None:
|
||||
return
|
||||
if isinstance(generator.executor, MultiprocExecutor):
|
||||
generator.executor.shutdown()
|
||||
|
||||
|
||||
def _start_capture(generator: Any) -> list[dict[str, Any]]:
|
||||
from fastvideo.worker.multiproc_executor import MultiprocExecutor
|
||||
|
||||
worker_payload = _make_worker_cuda_profiler_start_payload()
|
||||
_cuda_profiler_start()
|
||||
responses: list[dict[str, Any]] = []
|
||||
if isinstance(generator.executor, MultiprocExecutor):
|
||||
responses = generator.executor.collective_rpc(worker_payload)
|
||||
return responses
|
||||
|
||||
|
||||
def _stop_capture(generator: Any) -> list[dict[str, Any]]:
|
||||
import torch
|
||||
from fastvideo.worker.multiproc_executor import MultiprocExecutor
|
||||
|
||||
worker_payload = _make_worker_cuda_profiler_stop_payload()
|
||||
responses: list[dict[str, Any]] = []
|
||||
if isinstance(generator.executor, MultiprocExecutor):
|
||||
responses = generator.executor.collective_rpc(worker_payload)
|
||||
torch.cuda.synchronize()
|
||||
_cuda_profiler_stop()
|
||||
return responses
|
||||
|
||||
|
||||
def _write_summary(path: Path, summary: dict[str, Any]) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("w", encoding="utf-8") as f:
|
||||
json.dump(summary, f, indent=2)
|
||||
f.write("\n")
|
||||
|
||||
|
||||
def _collect_torch_runtime_info(torch_module: Any, num_devices: int) -> dict[str, Any]:
|
||||
return {
|
||||
"torch_version": torch_module.__version__,
|
||||
"torch_cuda_version": torch_module.version.cuda,
|
||||
"cuda_device_count": torch_module.cuda.device_count(),
|
||||
"cuda_arch_list": torch_module.cuda.get_arch_list(),
|
||||
"device_capabilities": [
|
||||
list(torch_module.cuda.get_device_capability(i))
|
||||
for i in range(num_devices)
|
||||
],
|
||||
"fastvideo_attention_backend": os.environ.get("FASTVIDEO_ATTENTION_BACKEND"),
|
||||
}
|
||||
|
||||
|
||||
def run_profile(args: argparse.Namespace) -> None:
|
||||
global logger
|
||||
|
||||
import torch
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.logger import init_logger
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
cfg = _load_config(args.config)
|
||||
run_config = cfg.get("run_config", {})
|
||||
required_gpus = int(run_config.get("required_gpus", 1))
|
||||
available_gpus = torch.cuda.device_count()
|
||||
if available_gpus < required_gpus:
|
||||
raise RuntimeError(f"Need {required_gpus} CUDA GPUs for {cfg['benchmark_id']}, found {available_gpus}")
|
||||
|
||||
model_info = cfg["model"]
|
||||
init_kwargs = _remap_init_kwargs(cfg.get("init_kwargs", {}))
|
||||
generation_kwargs = dict(cfg.get("generation_kwargs", {}))
|
||||
generation_kwargs["output_path"] = str(args.output_dir)
|
||||
prompt = cfg.get("test_prompts", ["A cinematic video."])[0]
|
||||
num_warmup = int(run_config.get("num_warmup_runs", 1))
|
||||
|
||||
args.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
os.environ["FASTVIDEO_STAGE_LOGGING"] = "1"
|
||||
|
||||
generator: VideoGenerator | None = None
|
||||
capture_started = False
|
||||
worker_start_events: list[dict[str, Any]] = []
|
||||
worker_stop_events: list[dict[str, Any]] = []
|
||||
try:
|
||||
logger.info("Loading model %s with init kwargs: %s", model_info["model_path"], init_kwargs)
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
model_path=model_info["model_path"],
|
||||
**init_kwargs,
|
||||
)
|
||||
|
||||
for i in range(num_warmup):
|
||||
logger.info("Warmup run %d/%d", i + 1, num_warmup)
|
||||
_run_generation(generator, prompt, generation_kwargs)
|
||||
|
||||
logger.info("Starting cudaProfilerApi capture range")
|
||||
worker_start_events = _start_capture(generator)
|
||||
capture_started = True
|
||||
elapsed, peak_mb, component_times = _run_generation(generator, prompt, generation_kwargs)
|
||||
finally:
|
||||
if capture_started and generator is not None:
|
||||
logger.info("Stopping cudaProfilerApi capture range")
|
||||
worker_stop_events = _stop_capture(generator)
|
||||
_shutdown_executor(generator)
|
||||
|
||||
device_names = [torch.cuda.get_device_name(i) for i in range(required_gpus)]
|
||||
runtime_info = _collect_torch_runtime_info(torch, required_gpus)
|
||||
throughput_fps = None
|
||||
num_frames = generation_kwargs.get("num_frames")
|
||||
if isinstance(num_frames, (int, float)) and elapsed > 0:
|
||||
throughput_fps = num_frames / elapsed
|
||||
|
||||
summary = {
|
||||
"benchmark_id": cfg["benchmark_id"],
|
||||
"model_short_name": model_info.get("model_short_name", ""),
|
||||
"model_path": model_info["model_path"],
|
||||
"config_path": str(args.config),
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"device_names": device_names,
|
||||
"device_capabilities": runtime_info["device_capabilities"],
|
||||
"available_gpus": available_gpus,
|
||||
"required_gpus": required_gpus,
|
||||
"torch_version": runtime_info["torch_version"],
|
||||
"torch_cuda_version": runtime_info["torch_cuda_version"],
|
||||
"cuda_arch_list": runtime_info["cuda_arch_list"],
|
||||
"fastvideo_attention_backend": runtime_info["fastvideo_attention_backend"],
|
||||
"num_warmup_runs": num_warmup,
|
||||
"num_profiled_runs": 1,
|
||||
"profiled_generation_time_s": round(elapsed, 3),
|
||||
"throughput_fps": round(throughput_fps, 3) if throughput_fps is not None else None,
|
||||
"peak_memory_mb": round(peak_mb, 1),
|
||||
"component_times_s": component_times,
|
||||
"generation_kwargs": generation_kwargs,
|
||||
"worker_start_events": worker_start_events,
|
||||
"worker_stop_events": worker_stop_events,
|
||||
}
|
||||
_write_summary(args.summary_path, summary)
|
||||
logger.info("Profile summary written to %s", args.summary_path)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"--config",
|
||||
type=Path,
|
||||
default=Path(".buildkite/performance-benchmarks/tests/wan-t2v-1.3b.json"),
|
||||
help="Benchmark JSON config to profile.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
type=Path,
|
||||
required=True,
|
||||
help="Directory for generated videos.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--summary-path",
|
||||
type=Path,
|
||||
required=True,
|
||||
help="Path for the profile summary JSON.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_profile(parse_args())
|
||||
Reference in New Issue
Block a user