Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42a4bd7a18 | ||
|
|
d2dd78bc42 | ||
|
|
e095388095 | ||
|
|
138f057ed7 | ||
|
|
ff0444a959 | ||
|
|
20be8c3dfa | ||
|
|
dc178be814 | ||
|
|
d72db9193b | ||
|
|
a9095abdc1 | ||
|
|
3c0792de21 | ||
|
|
cae8fa18dc | ||
|
|
821e5a0832 |
@@ -232,25 +232,13 @@ by the harness and is not config-declarable.)
|
||||
|
||||
Recipe fingerprinting, hardware/software profile IDs, exact-identity
|
||||
comparison, and dashboard cohort grouping land with this change: v2 records
|
||||
compare only within their identity cohort and are never compared against v1
|
||||
records (or vice versa). Legacy records missing any comparison identity field
|
||||
still run and are normalized for reporting, but they skip rolling-baseline
|
||||
comparison entirely (`baseline_status: "skipped_missing_identity"`, comparator
|
||||
verdict `PASS`, never baseline eligible); only static thresholds gate them.
|
||||
Each comparison reports an explicit `comparator_status` on the normalized
|
||||
record and in the Markdown summary:
|
||||
|
||||
| Status | Meaning | CI |
|
||||
|---|---|---|
|
||||
| `PASS` | Comparable baseline found with no gated regression, or comparison was skipped for a record without the v2 identity block. | passes |
|
||||
| `REGRESSION` | A gated metric regressed past both its percent and absolute floors. | fails |
|
||||
| `CALIBRATION_NEEDED` | No comparable baseline exists. Gating is inactive and the record does **not** seed a baseline — seed new cohorts explicitly via the reseed workflow. The record also carries `baseline_status: "initialized_new_cohort"`. | passes |
|
||||
| `RECIPE_MISMATCH` | Baseline history exists for the same variant/hardware/software cohort but under a different `recipe_fingerprint`. Represent recipe changes as a new `variant_id`, or reseed. | fails |
|
||||
| `INFRA_ERROR` | The comparison itself failed (for example, baseline history could not be loaded). | fails |
|
||||
| `QUALITY_BLOCKED` | Reserved for the promoted-baseline workflow; never emitted by the comparator. | n/a |
|
||||
|
||||
Metric-specific threshold policies and promoted baselines remain separate
|
||||
follow-ups.
|
||||
compare only within their identity cohort, and a record that opens a NEW
|
||||
cohort is marked `baseline_status: "initialized_new_cohort"` (regression
|
||||
gating starts once that cohort accumulates history). Legacy v1 configs still
|
||||
run and are normalized for reporting, but their records skip rolling-baseline
|
||||
comparison entirely (`baseline_status: "skipped_missing_identity"`, never
|
||||
baseline eligible); only static thresholds gate them. Metric-specific
|
||||
threshold policies and promoted baselines remain separate follow-ups.
|
||||
|
||||
### Raw record (`results/perf_*.json`)
|
||||
|
||||
@@ -379,8 +367,6 @@ result, used as the rolling-baseline source of truth.
|
||||
"build_id": "<buildkite-build-id>",
|
||||
"job_id": "<buildkite-job-id>",
|
||||
"quality_metadata": { "quality_status": "canonical" },
|
||||
"baseline_status": "compared",
|
||||
"comparator_status": "PASS",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
@@ -501,10 +487,10 @@ When the rolling-baseline phase runs, it emits:
|
||||
2. The pytest test auto-discovers all configs — no test code needed. CI
|
||||
picks it up on the next `/test performance` run.
|
||||
|
||||
3. The first run with no HF history reports `CALIBRATION_NEEDED`: it passes,
|
||||
but it does not seed the shared baseline. Seed the cohort explicitly with
|
||||
the `reseed-performance-baseline` skill; subsequent scheduled-main runs
|
||||
then compare against it and keep the rolling baseline advancing.
|
||||
3. The first persisted main-branch run with no HF history initializes the
|
||||
baseline (passes automatically). Subsequent runs compare against it. Local
|
||||
and pull-request runs with no HF history also pass, but they do not seed the
|
||||
shared baseline.
|
||||
|
||||
4. If the benchmark targets a GPU not currently in `thresholds`, either add
|
||||
that GPU as a key or rely on the `default` block. Note that `default` is
|
||||
@@ -524,13 +510,8 @@ When the rolling-baseline phase runs, it emits:
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**`CALIBRATION_NEEDED: no comparable baseline`** — first run for this cohort.
|
||||
The run passes but does not seed a baseline; seed it explicitly with the
|
||||
`reseed-performance-baseline` skill.
|
||||
|
||||
**`RECIPE_MISMATCH`** — the benchmark recipe changed without a new
|
||||
`variant_id`. Either bump `variant_id` to open a new cohort, or reseed the
|
||||
baseline if the existing variant should adopt the new recipe.
|
||||
**"No baseline for ... Initializing"** — first run for this comparison cohort.
|
||||
Run will pass and (if persisting) seed the first record.
|
||||
|
||||
**Persistent failure right after a torch / kernel / image upgrade** —
|
||||
genuine regression *or* baseline drift. Compare the failing normalized record
|
||||
|
||||
@@ -56,7 +56,7 @@ pipeline initialization and sampling.
|
||||
| Model Name | HuggingFace Model ID | Resolutions | TeaCache | Sliding Tile Attn (Legacy Branch) | Sage Attn | VSA | BSA |
|
||||
|------------|---------------------|-------------|----------|-------------------|-----------|-----|-----|
|
||||
| FastWan2.1 T2V 1.3B | `FastVideo/FastWan2.1-T2V-1.3B-Diffusers` | 480P | ⭕ | ⭕ | ⭕ | ✅ | ⭕ |
|
||||
| FastWan2.2 TI2V 5B Full Attn* | `FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers` | 720P | ⭕ | ⭕ | ⭕ | ✅ | ⭕ |
|
||||
| FastWan2.2 5B Full Attn T2V-only* | `FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers` | 720P | ⭕ | ⭕ | ⭕ | ❌ | ⭕ |
|
||||
| Wan2.2 TI2V 5B | `Wan-AI/Wan2.2-TI2V-5B-Diffusers` | 720P | ⭕ | ⭕ | ✅ | ⭕ | ⭕ |
|
||||
| DreamX-World 5B Cam | `FastVideo/DreamX-World-5B-Cam-Diffusers` | 480P | ⭕ | ⭕ | ⭕ | ⭕ | ⭕ |
|
||||
| DreamX-World 5B AR | `FastVideo/DreamX-World-5B-Diffusers` | 704px1280p | ⭕ | ⭕ | ⭕ | ⭕ | ⭕ |
|
||||
@@ -79,6 +79,9 @@ pipeline initialization and sampling.
|
||||
| Matrix Game 3.0 Base Distilled | `FastVideo/Matrix-Game-3.0-Base-Distilled-Diffusers` | 720x1280 | ⭕ | ⭕ | ⭕ | ⭕ | ⭕ |
|
||||
| GEN3C Cosmos 7B | `FastVideo/GEN3C-Cosmos-7B-Diffusers` | 704px1280p | ❌ | ❌ | ❌ | ⭕ | ⭕ |
|
||||
|
||||
*FastWan2.2 5B Full Attn is T2V-only and uses dense attention; do not run it with
|
||||
`FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN`.
|
||||
|
||||
**Note**: Wan2.2 TI2V 5B has some quality issues when performing I2V generation. We are working on fixing this issue.
|
||||
|
||||
***Lucy Edit Dev uses a non-commercial model license. FastVideo support is
|
||||
|
||||
@@ -12,6 +12,7 @@ from fastvideo.configs.models.encoders import (BaseEncoderOutput, CLIPVisionConf
|
||||
from fastvideo.configs.models.vaes import WanVAEConfig
|
||||
from fastvideo.configs.models.vaes.wanvae import WanVAEArchConfig
|
||||
from fastvideo.configs.pipelines.base import PipelineConfig
|
||||
from fastvideo.platforms import AttentionBackendEnum
|
||||
|
||||
|
||||
def t5_postprocess_text(outputs: BaseEncoderOutput) -> torch.Tensor:
|
||||
@@ -64,7 +65,7 @@ class WanT2V480PConfig(PipelineConfig):
|
||||
|
||||
# WanConfig-specific added parameters
|
||||
|
||||
def __post_init__(self):
|
||||
def __post_init__(self) -> None:
|
||||
self.vae_config.load_encoder = False
|
||||
self.vae_config.load_decoder = True
|
||||
|
||||
@@ -278,6 +279,33 @@ class FastWan2_2_TI2V_5B_Config(Wan2_2_TI2V_5B_Config):
|
||||
dmd_denoising_steps: list[int] | None = field(default_factory=lambda: [1000, 757, 522])
|
||||
|
||||
|
||||
@dataclass
|
||||
class FastWan2_2_TI2V_5B_FullAttn_Config(FastWan2_2_TI2V_5B_Config):
|
||||
dit_config: DiTConfig = field(
|
||||
default_factory=lambda: WanVideoConfig(arch_config=WanVideoArchConfig(_supported_attention_backends=(
|
||||
AttentionBackendEnum.FLASH_ATTN,
|
||||
AttentionBackendEnum.TORCH_SDPA,
|
||||
AttentionBackendEnum.SAGE_ATTN,
|
||||
AttentionBackendEnum.SAGE_ATTN_THREE,
|
||||
AttentionBackendEnum.ATTN_QAT_INFER,
|
||||
))))
|
||||
ti2v_task: bool = False
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
super().__post_init__()
|
||||
self.vae_config.load_encoder = False
|
||||
|
||||
def check_pipeline_config(self) -> None:
|
||||
super().check_pipeline_config()
|
||||
|
||||
from fastvideo import envs
|
||||
if envs.FASTVIDEO_ATTENTION_BACKEND == "VIDEO_SPARSE_ATTN":
|
||||
raise ValueError("FastWan2.2-TI2V-5B-FullAttn-Diffusers is a dense FullAttn T2V-only checkpoint "
|
||||
"and does not support FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN. "
|
||||
"Unset FASTVIDEO_ATTENTION_BACKEND or use a dense backend such as FLASH_ATTN or "
|
||||
"TORCH_SDPA.")
|
||||
|
||||
|
||||
@dataclass
|
||||
class Wan2_2_T2V_A14B_Config(WanT2V480PConfig):
|
||||
flow_shift: float | None = 12.0
|
||||
|
||||
@@ -607,6 +607,19 @@ class WanTransformerBlock_VSA(nn.Module):
|
||||
return hidden_states
|
||||
|
||||
|
||||
def _select_wan_transformer_block(config: WanVideoConfig) -> type[nn.Module]:
|
||||
attn_backend = envs.FASTVIDEO_ATTENTION_BACKEND
|
||||
if attn_backend == "VIDEO_SPARSE_ATTN":
|
||||
supported_backends = config._supported_attention_backends or ()
|
||||
if AttentionBackendEnum.VIDEO_SPARSE_ATTN not in supported_backends:
|
||||
raise ValueError("FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN requires a Wan VSA checkpoint/config, "
|
||||
"but this Wan config does not support VIDEO_SPARSE_ATTN. "
|
||||
"For FastWan2.2-TI2V-5B-FullAttn-Diffusers, unset FASTVIDEO_ATTENTION_BACKEND or use "
|
||||
"a dense backend such as FLASH_ATTN or TORCH_SDPA.")
|
||||
return WanTransformerBlock_VSA
|
||||
return WanTransformerBlock
|
||||
|
||||
|
||||
class WanTransformer3DModel(BaseDiT):
|
||||
_fsdp_shard_conditions = WanVideoConfig()._fsdp_shard_conditions
|
||||
_compile_conditions = WanVideoConfig()._compile_conditions
|
||||
@@ -651,8 +664,7 @@ class WanTransformer3DModel(BaseDiT):
|
||||
)
|
||||
|
||||
# 3. Transformer blocks
|
||||
attn_backend = envs.FASTVIDEO_ATTENTION_BACKEND
|
||||
transformer_block = WanTransformerBlock_VSA if attn_backend == "VIDEO_SPARSE_ATTN" else WanTransformerBlock
|
||||
transformer_block = _select_wan_transformer_block(config)
|
||||
self.blocks = nn.ModuleList([
|
||||
transformer_block(inner_dim,
|
||||
config.ffn_dim,
|
||||
@@ -661,7 +673,7 @@ class WanTransformer3DModel(BaseDiT):
|
||||
config.cross_attn_norm,
|
||||
config.eps,
|
||||
config.added_kv_proj_dim,
|
||||
self._supported_attention_backends,
|
||||
config._supported_attention_backends,
|
||||
quant_config=config.quant_config,
|
||||
prefix=f"{config.prefix}.blocks.{i}")
|
||||
for i in range(config.num_layers)
|
||||
|
||||
@@ -75,6 +75,25 @@ if not is_flash_attn_2_available():
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
def _resolve_torch_dtype(dtype, default: torch.dtype = torch.float32) -> torch.dtype:
|
||||
if isinstance(dtype, torch.dtype):
|
||||
return dtype
|
||||
if isinstance(dtype, str):
|
||||
normalized = dtype.strip().removeprefix("torch.").lower()
|
||||
return {
|
||||
"bfloat16": torch.bfloat16,
|
||||
"bf16": torch.bfloat16,
|
||||
"float16": torch.float16,
|
||||
"fp16": torch.float16,
|
||||
"half": torch.float16,
|
||||
"float32": torch.float32,
|
||||
"fp32": torch.float32,
|
||||
"float": torch.float32,
|
||||
}.get(normalized, default)
|
||||
return default
|
||||
|
||||
|
||||
class Qwen2_5_VLMLP(nn.Module):
|
||||
def __init__(self, config, bias: bool = False):
|
||||
super().__init__()
|
||||
@@ -304,10 +323,13 @@ class Qwen2_5_VisionTransformerPretrainedModel(nn.Module):
|
||||
config_class = Qwen2_5_VLVisionConfig
|
||||
_no_split_modules = ["Qwen2_5_VLVisionBlock"]
|
||||
|
||||
def __init__(self, config) -> None:
|
||||
def __init__(self, config, parent_torch_dtype=None) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.dtype = torch.bfloat16 if config.torch_dtype == "bfloat16" else torch.float32
|
||||
config_torch_dtype = getattr(config, "torch_dtype", None)
|
||||
self.dtype = _resolve_torch_dtype(
|
||||
config_torch_dtype if config_torch_dtype is not None else parent_torch_dtype
|
||||
)
|
||||
|
||||
self.spatial_merge_size = config.spatial_merge_size
|
||||
self.patch_size = config.patch_size
|
||||
@@ -1458,7 +1480,10 @@ class Qwen2_5_VLForConditionalGenerationSimple(nn.Module):
|
||||
super().__init__()
|
||||
config = _flatten_text_config(config)
|
||||
self.config = config
|
||||
self.visual = Qwen2_5_VisionTransformerPretrainedModel(config.vision_config)
|
||||
self.visual = Qwen2_5_VisionTransformerPretrainedModel(
|
||||
config.vision_config,
|
||||
parent_torch_dtype=getattr(config, "torch_dtype", None),
|
||||
)
|
||||
|
||||
self.model = Qwen2_5_VLModel(config)
|
||||
self.vocab_size = config.vocab_size
|
||||
|
||||
+11
-1
@@ -46,6 +46,7 @@ from fastvideo.configs.pipelines.turbodiffusion import (
|
||||
from fastvideo.configs.pipelines.wan import (
|
||||
FastWan2_1_T2V_480P_Config,
|
||||
FastWan2_2_TI2V_5B_Config,
|
||||
FastWan2_2_TI2V_5B_FullAttn_Config,
|
||||
LucyEditDevConfig,
|
||||
SelfForcingWan2_2_T2V480PConfig,
|
||||
SelfForcingWanT2V480PConfig,
|
||||
@@ -985,12 +986,21 @@ def _register_configs() -> None:
|
||||
pipeline_config_cls=FastWan2_2_TI2V_5B_Config,
|
||||
workload_types=(WorkloadType.T2V, WorkloadType.I2V),
|
||||
hf_model_paths=[
|
||||
"FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers",
|
||||
"FastVideo/FastWan2.2-TI2V-5B-Diffusers",
|
||||
],
|
||||
model_family="wan",
|
||||
default_preset="fast_wan_2_2_ti2v_5b",
|
||||
)
|
||||
register_configs(
|
||||
sampling_param_cls=None,
|
||||
pipeline_config_cls=FastWan2_2_TI2V_5B_FullAttn_Config,
|
||||
workload_types=(WorkloadType.T2V, ),
|
||||
hf_model_paths=[
|
||||
"FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers",
|
||||
],
|
||||
model_family="wan",
|
||||
default_preset="fast_wan_2_2_ti2v_5b",
|
||||
)
|
||||
register_configs(
|
||||
sampling_param_cls=None,
|
||||
pipeline_config_cls=LucyEditDevConfig,
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import torch.nn as nn
|
||||
|
||||
import pytest
|
||||
|
||||
from fastvideo.configs.pipelines.wan import (
|
||||
FastWan2_2_TI2V_5B_Config,
|
||||
FastWan2_2_TI2V_5B_FullAttn_Config,
|
||||
)
|
||||
from fastvideo.models.dits import wanvideo as wanvideo_module
|
||||
from fastvideo.models.dits.wanvideo import (
|
||||
WanTransformerBlock,
|
||||
WanTransformerBlock_VSA,
|
||||
WanTransformer3DModel,
|
||||
_select_wan_transformer_block,
|
||||
)
|
||||
from fastvideo.platforms import AttentionBackendEnum
|
||||
from fastvideo.registry import (
|
||||
get_pipeline_config_cls_from_name,
|
||||
get_registered_models_with_workloads,
|
||||
)
|
||||
|
||||
FULLATTN_MODEL_ID = "FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers"
|
||||
FULLATTN_SHORT_NAME = "FastWan2.2-TI2V-5B-FullAttn-Diffusers"
|
||||
FASTWAN_TI2V_MODEL_ID = "FastVideo/FastWan2.2-TI2V-5B-Diffusers"
|
||||
|
||||
|
||||
def _workloads_for(model_id: str) -> list[str]:
|
||||
for model in get_registered_models_with_workloads():
|
||||
if model["id"] == model_id:
|
||||
return model["workload_types"]
|
||||
raise AssertionError(f"{model_id} was not registered")
|
||||
|
||||
|
||||
def test_fastwan_fullattn_registry_is_t2v_only() -> None:
|
||||
assert get_pipeline_config_cls_from_name(FULLATTN_MODEL_ID) is FastWan2_2_TI2V_5B_FullAttn_Config
|
||||
assert get_pipeline_config_cls_from_name(FULLATTN_SHORT_NAME) is FastWan2_2_TI2V_5B_FullAttn_Config
|
||||
assert get_pipeline_config_cls_from_name(FASTWAN_TI2V_MODEL_ID) is FastWan2_2_TI2V_5B_Config
|
||||
|
||||
assert _workloads_for(FULLATTN_MODEL_ID) == ["t2v"]
|
||||
i2v_model_ids = {model["id"] for model in get_registered_models_with_workloads("i2v")}
|
||||
assert FULLATTN_MODEL_ID not in i2v_model_ids
|
||||
assert FASTWAN_TI2V_MODEL_ID in i2v_model_ids
|
||||
|
||||
|
||||
def test_fastwan_fullattn_config_is_dense_t2v_only(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.delenv("FASTVIDEO_ATTENTION_BACKEND", raising=False)
|
||||
|
||||
config = FastWan2_2_TI2V_5B_FullAttn_Config()
|
||||
|
||||
assert config.ti2v_task is False
|
||||
assert config.vae_config.load_encoder is False
|
||||
assert config.vae_config.load_decoder is True
|
||||
assert config.dit_config.expand_timesteps is True
|
||||
|
||||
supported_backends = config.dit_config._supported_attention_backends
|
||||
assert AttentionBackendEnum.VIDEO_SPARSE_ATTN not in supported_backends
|
||||
assert AttentionBackendEnum.FLASH_ATTN in supported_backends
|
||||
assert AttentionBackendEnum.TORCH_SDPA in supported_backends
|
||||
|
||||
config.check_pipeline_config()
|
||||
|
||||
|
||||
def test_fastwan_fullattn_config_rejects_vsa_backend(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("FASTVIDEO_ATTENTION_BACKEND", "VIDEO_SPARSE_ATTN")
|
||||
|
||||
with pytest.raises(ValueError, match="FullAttn.*VIDEO_SPARSE_ATTN"):
|
||||
FastWan2_2_TI2V_5B_FullAttn_Config().check_pipeline_config()
|
||||
|
||||
|
||||
def test_wan_block_selection_rejects_vsa_for_fullattn(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("FASTVIDEO_ATTENTION_BACKEND", "VIDEO_SPARSE_ATTN")
|
||||
|
||||
with pytest.raises(ValueError, match="VIDEO_SPARSE_ATTN.*FullAttn"):
|
||||
_select_wan_transformer_block(FastWan2_2_TI2V_5B_FullAttn_Config().dit_config)
|
||||
|
||||
|
||||
def test_wan_block_selection_rejects_vsa_with_missing_backend_list(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("FASTVIDEO_ATTENTION_BACKEND", "VIDEO_SPARSE_ATTN")
|
||||
config = FastWan2_2_TI2V_5B_FullAttn_Config().dit_config
|
||||
config._supported_attention_backends = None # type: ignore[assignment]
|
||||
|
||||
with pytest.raises(ValueError, match="VIDEO_SPARSE_ATTN.*FullAttn"):
|
||||
_select_wan_transformer_block(config)
|
||||
|
||||
|
||||
def test_wan_block_selection_preserves_existing_vsa_config(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("FASTVIDEO_ATTENTION_BACKEND", "VIDEO_SPARSE_ATTN")
|
||||
assert _select_wan_transformer_block(FastWan2_2_TI2V_5B_Config().dit_config) is WanTransformerBlock_VSA
|
||||
|
||||
monkeypatch.setenv("FASTVIDEO_ATTENTION_BACKEND", "TORCH_SDPA")
|
||||
assert _select_wan_transformer_block(FastWan2_2_TI2V_5B_Config().dit_config) is WanTransformerBlock
|
||||
|
||||
|
||||
def test_wan_transformer_uses_config_supported_backends(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.delenv("FASTVIDEO_ATTENTION_BACKEND", raising=False)
|
||||
captured_backends = []
|
||||
|
||||
class CapturingWanTransformerBlock(nn.Module):
|
||||
|
||||
def __init__(self,
|
||||
dim: int,
|
||||
ffn_dim: int,
|
||||
num_heads: int,
|
||||
qk_norm: str = "rms_norm_across_heads",
|
||||
cross_attn_norm: bool = False,
|
||||
eps: float = 1e-6,
|
||||
added_kv_proj_dim: int | None = None,
|
||||
supported_attention_backends: tuple[
|
||||
AttentionBackendEnum, ...] | None = None,
|
||||
**kwargs) -> None:
|
||||
super().__init__()
|
||||
captured_backends.append(supported_attention_backends)
|
||||
|
||||
monkeypatch.setattr(wanvideo_module, "WanTransformerBlock", CapturingWanTransformerBlock)
|
||||
monkeypatch.setattr(wanvideo_module, "get_sp_world_size", lambda: 1)
|
||||
|
||||
config = FastWan2_2_TI2V_5B_FullAttn_Config().dit_config
|
||||
config.arch_config.num_attention_heads = 1
|
||||
config.arch_config.attention_head_dim = 4
|
||||
config.arch_config.in_channels = 4
|
||||
config.arch_config.out_channels = 4
|
||||
config.arch_config.num_layers = 2
|
||||
config.arch_config.ffn_dim = 8
|
||||
config.arch_config.text_dim = 4
|
||||
config.arch_config.freq_dim = 4
|
||||
config.arch_config.patch_size = (1, 1, 1)
|
||||
config.arch_config.__post_init__()
|
||||
|
||||
WanTransformer3DModel(config=config, hf_config={})
|
||||
|
||||
assert captured_backends == [
|
||||
config._supported_attention_backends,
|
||||
config._supported_attention_backends,
|
||||
]
|
||||
assert AttentionBackendEnum.VIDEO_SPARSE_ATTN not in captured_backends[0]
|
||||
@@ -50,9 +50,10 @@ def test_resolver_skips_cute_without_opt_in(monkeypatch) -> None:
|
||||
monkeypatch.setattr(builtins, "__import__", spying_import)
|
||||
|
||||
mod = _reload_resolver_module()
|
||||
resolved = mod._resolve_flash_attn_varlen_func()
|
||||
resolved, version = mod._resolve_flash_attn_varlen_func()
|
||||
assert resolved is not None
|
||||
assert resolved.__name__ == "flash_attn_varlen_func"
|
||||
assert version in ("2", "3")
|
||||
assert "fastvideo.attention.utils.flash_attn_cute" not in attempted
|
||||
|
||||
|
||||
@@ -92,9 +93,10 @@ def test_resolver_returns_flash_attn_when_interface_unavailable(monkeypatch) ->
|
||||
monkeypatch.setattr(builtins, "__import__", patched_import)
|
||||
|
||||
mod = _reload_resolver_module()
|
||||
resolved = mod._resolve_flash_attn_varlen_func()
|
||||
resolved, version = mod._resolve_flash_attn_varlen_func()
|
||||
assert resolved is not None
|
||||
assert resolved.__module__.startswith("flash_attn")
|
||||
assert version == "2"
|
||||
|
||||
|
||||
def test_module_level_impl_is_resolver_output() -> None:
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
import torch
|
||||
|
||||
from fastvideo.models.encoders.qwen2_5_vl_custom import (
|
||||
Qwen2_5_VisionTransformerPretrainedModel,
|
||||
Qwen2_5_VLForConditionalGenerationSimple,
|
||||
)
|
||||
|
||||
|
||||
def _vision_config(torch_dtype=None):
|
||||
return SimpleNamespace(
|
||||
torch_dtype=torch_dtype,
|
||||
spatial_merge_size=2,
|
||||
patch_size=14,
|
||||
fullatt_block_indexes=[],
|
||||
window_size=112,
|
||||
temporal_patch_size=2,
|
||||
in_channels=3,
|
||||
hidden_size=8,
|
||||
num_heads=2,
|
||||
depth=0,
|
||||
_attn_implementation="sdpa",
|
||||
out_hidden_size=8,
|
||||
)
|
||||
|
||||
|
||||
def _full_config(torch_dtype):
|
||||
return SimpleNamespace(
|
||||
vision_config=_vision_config(),
|
||||
torch_dtype=torch_dtype,
|
||||
vocab_size=16,
|
||||
hidden_size=8,
|
||||
num_hidden_layers=0,
|
||||
num_attention_heads=2,
|
||||
pad_token_id=0,
|
||||
_attn_implementation="sdpa",
|
||||
rms_norm_eps=1e-6,
|
||||
max_position_embeddings=32,
|
||||
rope_theta=1_000_000.0,
|
||||
rope_scaling=None,
|
||||
)
|
||||
|
||||
|
||||
def test_vision_dtype_uses_parent_bfloat16_string_when_vision_dtype_missing():
|
||||
model = Qwen2_5_VisionTransformerPretrainedModel(
|
||||
_vision_config(),
|
||||
parent_torch_dtype="bfloat16",
|
||||
)
|
||||
|
||||
assert model.dtype == torch.bfloat16
|
||||
|
||||
|
||||
def test_vision_dtype_accepts_parent_torch_dtype_object():
|
||||
model = Qwen2_5_VisionTransformerPretrainedModel(
|
||||
_vision_config(),
|
||||
parent_torch_dtype=torch.bfloat16,
|
||||
)
|
||||
|
||||
assert model.dtype == torch.bfloat16
|
||||
|
||||
|
||||
def test_vision_dtype_strips_parent_dtype_string_whitespace():
|
||||
model = Qwen2_5_VisionTransformerPretrainedModel(
|
||||
_vision_config(),
|
||||
parent_torch_dtype=" torch.bfloat16 ",
|
||||
)
|
||||
|
||||
assert model.dtype == torch.bfloat16
|
||||
|
||||
|
||||
def test_vision_dtype_prefers_explicit_vision_dtype_over_parent_dtype():
|
||||
model = Qwen2_5_VisionTransformerPretrainedModel(
|
||||
_vision_config(torch_dtype="float16"),
|
||||
parent_torch_dtype="bfloat16",
|
||||
)
|
||||
|
||||
assert model.dtype == torch.float16
|
||||
|
||||
|
||||
def test_vision_dtype_falls_back_to_float32_for_missing_or_unknown_dtype():
|
||||
missing = Qwen2_5_VisionTransformerPretrainedModel(_vision_config())
|
||||
unknown = Qwen2_5_VisionTransformerPretrainedModel(
|
||||
_vision_config(),
|
||||
parent_torch_dtype="not-a-real-dtype",
|
||||
)
|
||||
|
||||
assert missing.dtype == torch.float32
|
||||
assert unknown.dtype == torch.float32
|
||||
|
||||
|
||||
def test_conditional_generation_passes_parent_dtype_to_visual_tower():
|
||||
model = Qwen2_5_VLForConditionalGenerationSimple(_full_config("torch.bfloat16"))
|
||||
|
||||
assert model.visual.dtype == torch.bfloat16
|
||||
@@ -89,14 +89,6 @@ COMPARISON_IDENTITY_KEYS = (
|
||||
"hardware_profile_id",
|
||||
"software_profile_id",
|
||||
)
|
||||
# Comparator verdicts (issue #1532), stored as record["comparator_status"].
|
||||
STATUS_PASS = "PASS"
|
||||
STATUS_REGRESSION = "REGRESSION"
|
||||
STATUS_CALIBRATION_NEEDED = "CALIBRATION_NEEDED"
|
||||
STATUS_RECIPE_MISMATCH = "RECIPE_MISMATCH"
|
||||
STATUS_INFRA_ERROR = "INFRA_ERROR"
|
||||
# Reserved for the promoted-baseline workflow; never emitted by this comparator.
|
||||
STATUS_QUALITY_BLOCKED = "QUALITY_BLOCKED"
|
||||
|
||||
|
||||
def _should_persist_tracking() -> bool:
|
||||
@@ -351,90 +343,6 @@ def _check_regressions(
|
||||
return failures
|
||||
|
||||
|
||||
def _recipe_mismatch_fingerprints(
|
||||
record: dict[str, Any],
|
||||
identity_filters: dict[str, str],
|
||||
) -> list[str]:
|
||||
"""Return baseline recipe fingerprints for the same variant cohort.
|
||||
|
||||
Non-empty means the same (workload, variant, version, hardware, software)
|
||||
cohort has baseline history under a DIFFERENT recipe fingerprint: the
|
||||
recipe changed without being represented as a new variant.
|
||||
"""
|
||||
if not identity_filters:
|
||||
return []
|
||||
variant_filters = {key: value for key, value in identity_filters.items() if key != "recipe_fingerprint"}
|
||||
same_variant = load_records_for_model(
|
||||
TRACKING_ROOT,
|
||||
record["model_id"],
|
||||
record["gpu_type"],
|
||||
**variant_filters,
|
||||
successful_only=True,
|
||||
baseline_eligible_only=True,
|
||||
)
|
||||
fingerprints = {str(r.get("recipe_fingerprint")) for r in same_variant}
|
||||
return sorted(fingerprints - {identity_filters["recipe_fingerprint"]})
|
||||
|
||||
|
||||
def _compare_record(
|
||||
record: dict[str, Any],
|
||||
metric_policies: tuple[MetricPolicy, ...],
|
||||
) -> tuple[list[str], list[dict[str, Any]]]:
|
||||
"""Compare one normalized record against its exact-identity cohort.
|
||||
|
||||
Sets ``record["comparator_status"]`` (and ``record["baseline_status"]``
|
||||
for cohort bookkeeping) and returns ``(failures, baseline_records)``.
|
||||
"""
|
||||
model = record.get("model_id", "unknown")
|
||||
try:
|
||||
identity_filters = _comparison_identity_filters(record)
|
||||
baseline_records = load_records_for_model(
|
||||
TRACKING_ROOT,
|
||||
record["model_id"],
|
||||
record["gpu_type"],
|
||||
**identity_filters,
|
||||
last_n=5,
|
||||
successful_only=True,
|
||||
baseline_eligible_only=True,
|
||||
)
|
||||
if baseline_records:
|
||||
record["baseline_status"] = "compared"
|
||||
failures = _check_regressions(record, baseline_records, metric_policies)
|
||||
record["comparator_status"] = STATUS_REGRESSION if failures else STATUS_PASS
|
||||
return failures, baseline_records
|
||||
|
||||
mismatched = _recipe_mismatch_fingerprints(record, identity_filters)
|
||||
except Exception as exc:
|
||||
record["comparator_status"] = STATUS_INFRA_ERROR
|
||||
return [f"{model} baseline comparison hit an infra error: {exc}"], []
|
||||
|
||||
if mismatched:
|
||||
record["comparator_status"] = STATUS_RECIPE_MISMATCH
|
||||
return [
|
||||
f"{model} recipe fingerprint {record.get('recipe_fingerprint')} does not "
|
||||
f"match baseline fingerprint(s) {', '.join(mismatched)} for variant "
|
||||
f"{record.get('variant_id')}. Represent recipe changes as a new "
|
||||
"variant_id, or reseed the baseline."
|
||||
], []
|
||||
|
||||
# No comparable baseline anywhere: a brand-new cohort. Make that loud and
|
||||
# machine-readable instead of an indistinguishable pass, so a cohort shift
|
||||
# (intended or accidental, e.g. an identity-field change) never silently
|
||||
# blinds the comparison — and never silently seeds a passing baseline.
|
||||
record["baseline_status"] = "initialized_new_cohort"
|
||||
record["comparator_status"] = STATUS_CALIBRATION_NEEDED
|
||||
print("=" * 72)
|
||||
print(f"CALIBRATION_NEEDED: no comparable baseline for {model} on "
|
||||
f"{record.get('gpu_type', 'unknown')}"
|
||||
f"{_format_identity_filters(identity_filters)}")
|
||||
print("Regression gating is INACTIVE for this record and it will NOT "
|
||||
"seed a baseline. Seed the cohort explicitly via the reseed "
|
||||
"workflow. If this cohort shift is unexpected, check the identity "
|
||||
"fields above.")
|
||||
print("=" * 72)
|
||||
return [], []
|
||||
|
||||
|
||||
def _compact_value(value: float | None, precision: int = 3) -> str:
|
||||
if value is None:
|
||||
return "n/a"
|
||||
@@ -486,7 +394,6 @@ def _build_summary_row(
|
||||
return {
|
||||
"model_id": record["model_id"],
|
||||
"gpu_type": record["gpu_type"],
|
||||
"comparator_status": record.get("comparator_status", STATUS_PASS),
|
||||
"baseline_n": len(baseline_records),
|
||||
"metrics": metric_values,
|
||||
"worst_regression_pct": worst_regression_pct,
|
||||
@@ -528,10 +435,7 @@ def _build_markdown_summary(
|
||||
else "none"
|
||||
)
|
||||
failing_metrics = ", ".join(row["failing_metrics"]) if row["failing_metrics"] else "none"
|
||||
status = row["comparator_status"]
|
||||
if row["failed"] and status == STATUS_PASS:
|
||||
# Baseline comparison passed but the fixed-threshold phase failed.
|
||||
status = "FAIL"
|
||||
status = "FAIL" if row["failed"] else "PASS"
|
||||
|
||||
lines.append(f"| {row['model_id']} | {row['gpu_type']} | "
|
||||
f"{row['baseline_n']} | "
|
||||
@@ -597,26 +501,46 @@ def main() -> int:
|
||||
if identity_filters is None:
|
||||
# Records without the full v2 identity block skip rolling-baseline
|
||||
# comparison entirely: only the static thresholds gate them and
|
||||
# they never become baseline eligible. Nothing was compared and
|
||||
# nothing failed, so the comparator verdict is PASS;
|
||||
# baseline_status keeps the skip machine-readable.
|
||||
# they never become baseline eligible.
|
||||
record["baseline_status"] = "skipped_missing_identity"
|
||||
record["comparator_status"] = STATUS_PASS
|
||||
failures: list[str] = []
|
||||
else:
|
||||
failures, baseline_records = _compare_record(record, metric_policies)
|
||||
baseline_records = load_records_for_model(
|
||||
TRACKING_ROOT,
|
||||
record["model_id"],
|
||||
record["gpu_type"],
|
||||
**identity_filters,
|
||||
last_n=5,
|
||||
successful_only=True,
|
||||
baseline_eligible_only=True,
|
||||
)
|
||||
|
||||
if not baseline_records:
|
||||
# A brand-new cohort has NO regression gating until history
|
||||
# accumulates — make that loud and machine-readable instead of
|
||||
# an indistinguishable pass, so a cohort shift (intended or
|
||||
# accidental, e.g. an identity-field change) never silently
|
||||
# blinds the comparison.
|
||||
record["baseline_status"] = "initialized_new_cohort"
|
||||
print("=" * 72)
|
||||
print(f"WARNING: NO BASELINE — initializing a NEW cohort for "
|
||||
f"{record['model_id']} on {record['gpu_type']}"
|
||||
f"{_format_identity_filters(identity_filters)}")
|
||||
print("Regression gating is INACTIVE for this cohort until "
|
||||
"baseline history accumulates. If this cohort shift is "
|
||||
"unexpected, check the identity fields above.")
|
||||
print("=" * 72)
|
||||
failures = []
|
||||
else:
|
||||
record["baseline_status"] = "compared"
|
||||
failures = _check_regressions(record, baseline_records, metric_policies)
|
||||
if static_threshold_failed:
|
||||
failures.append(f"{record['model_id']} fixed-threshold phase failed "
|
||||
f"(PERF_PYTEST_RC={os.environ.get('PERF_PYTEST_RC')})")
|
||||
|
||||
record["success"] = not failures
|
||||
# Only compared-and-PASS records may advance the rolling baseline: a
|
||||
# CALIBRATION_NEEDED record must never silently seed a new cohort, and
|
||||
# records without the v2 identity block never become eligible.
|
||||
record["baseline_eligible"] = (
|
||||
identity_filters is not None
|
||||
and record["comparator_status"] == STATUS_PASS
|
||||
and _is_baseline_eligible(record["run_source"], record["success"])
|
||||
identity_filters is not None and _is_baseline_eligible(record["run_source"], record["success"])
|
||||
)
|
||||
all_failures.extend(failures)
|
||||
|
||||
|
||||
@@ -421,196 +421,3 @@ def test_informational_metric_remains_visible_without_failing():
|
||||
assert row["metrics"]["throughput"]["regressed"] is False
|
||||
assert row["threshold_exceeded_metrics"] == ["throughput"]
|
||||
assert row["failing_metrics"] == []
|
||||
|
||||
|
||||
def _v2_raw_result(**overrides):
|
||||
raw = _raw_result()
|
||||
raw.update({
|
||||
"workload_id": "wan-t2v",
|
||||
"variant_id": "1.3b-sp2",
|
||||
"benchmark_version": 2,
|
||||
"recipe_fingerprint": "recipe-1",
|
||||
"hardware_profile_id": "hw-1",
|
||||
"software_profile_id": "sw-1",
|
||||
})
|
||||
raw.update(overrides)
|
||||
return raw
|
||||
|
||||
|
||||
def _v2_baseline_record(**overrides):
|
||||
record = {
|
||||
"gpu_type": "NVIDIA L40S",
|
||||
"workload_id": "wan-t2v",
|
||||
"variant_id": "1.3b-sp2",
|
||||
"benchmark_version": 2,
|
||||
"recipe_fingerprint": "recipe-1",
|
||||
"hardware_profile_id": "hw-1",
|
||||
"software_profile_id": "sw-1",
|
||||
"latency": 10.0,
|
||||
"throughput": 4.5,
|
||||
"memory": 10000.0,
|
||||
"timestamp": "2026-06-15T00:00:00+00:00",
|
||||
"success": True,
|
||||
"run_source": "scheduled_main",
|
||||
"baseline_eligible": True,
|
||||
}
|
||||
record.update(overrides)
|
||||
return record
|
||||
|
||||
|
||||
def _run_compare(monkeypatch, tmp_path, raw_result, baseline_records):
|
||||
"""Run compare_baseline.main() against a local tracking root.
|
||||
|
||||
Returns (exit_code, normalized_record, markdown_report).
|
||||
"""
|
||||
results_dir = tmp_path / "results"
|
||||
results_dir.mkdir()
|
||||
(results_dir / "perf_current.json").write_text(json.dumps(raw_result))
|
||||
|
||||
model_dir = tmp_path / "tracking" / raw_result["benchmark_id"]
|
||||
model_dir.mkdir(parents=True)
|
||||
for index, record in enumerate(baseline_records):
|
||||
(model_dir / f"rec{index}.json").write_text(json.dumps(record))
|
||||
|
||||
reports_dir = tmp_path / "reports"
|
||||
monkeypatch.setattr(compare_baseline, "RESULTS_DIR", str(results_dir))
|
||||
monkeypatch.setattr(compare_baseline, "TRACKING_ROOT", str(tmp_path / "tracking"))
|
||||
monkeypatch.setattr(compare_baseline, "PERF_REPORTS_DIR", str(reports_dir))
|
||||
monkeypatch.setattr(compare_baseline, "UPLOAD_POLICY", "never")
|
||||
monkeypatch.setattr(compare_baseline, "sync_from_hf", lambda *args, **kwargs: None)
|
||||
monkeypatch.setenv("PERF_RUN_SOURCE", "scheduled_main")
|
||||
monkeypatch.delenv("PERF_PYTEST_RC", raising=False)
|
||||
monkeypatch.delenv("GITHUB_STEP_SUMMARY", raising=False)
|
||||
|
||||
exit_code = compare_baseline.main()
|
||||
|
||||
normalized_paths = list((reports_dir / "results").glob("normalized_perf_*.json"))
|
||||
assert len(normalized_paths) == 1
|
||||
markdown = "\n".join(path.read_text() for path in reports_dir.glob("perf_*.md"))
|
||||
return exit_code, json.loads(normalized_paths[0].read_text()), markdown
|
||||
|
||||
|
||||
def test_main_pass_with_comparable_baseline(monkeypatch, tmp_path):
|
||||
exit_code, record, markdown = _run_compare(
|
||||
monkeypatch, tmp_path, _v2_raw_result(), [_v2_baseline_record()])
|
||||
|
||||
assert exit_code == 0
|
||||
assert record["comparator_status"] == "PASS"
|
||||
assert record["baseline_status"] == "compared"
|
||||
assert record["success"] is True
|
||||
# Scheduled-main PASS records keep advancing the rolling baseline.
|
||||
assert record["baseline_eligible"] is True
|
||||
assert "| PASS |" in markdown
|
||||
|
||||
|
||||
def test_main_regression_on_gated_metric_fails_ci(monkeypatch, tmp_path):
|
||||
exit_code, record, markdown = _run_compare(
|
||||
monkeypatch, tmp_path, _v2_raw_result(avg_generation_time_s=20.0),
|
||||
[_v2_baseline_record()])
|
||||
|
||||
assert exit_code == 1
|
||||
assert record["comparator_status"] == "REGRESSION"
|
||||
assert record["success"] is False
|
||||
assert record["baseline_eligible"] is False
|
||||
assert "| REGRESSION |" in markdown
|
||||
|
||||
|
||||
def test_main_missing_baseline_is_calibration_needed_and_does_not_seed(monkeypatch, tmp_path):
|
||||
exit_code, record, markdown = _run_compare(
|
||||
monkeypatch, tmp_path, _v2_raw_result(), [])
|
||||
|
||||
assert exit_code == 0
|
||||
assert record["comparator_status"] == "CALIBRATION_NEEDED"
|
||||
assert record["baseline_status"] == "initialized_new_cohort"
|
||||
assert record["success"] is True
|
||||
# Visible, but never silently seeds a passing baseline.
|
||||
assert record["baseline_eligible"] is False
|
||||
assert "| CALIBRATION_NEEDED |" in markdown
|
||||
|
||||
|
||||
def test_main_recipe_change_without_new_variant_is_recipe_mismatch(monkeypatch, tmp_path):
|
||||
exit_code, record, markdown = _run_compare(
|
||||
monkeypatch, tmp_path, _v2_raw_result(recipe_fingerprint="recipe-2"),
|
||||
[_v2_baseline_record()])
|
||||
|
||||
assert exit_code == 1
|
||||
assert record["comparator_status"] == "RECIPE_MISMATCH"
|
||||
assert record["success"] is False
|
||||
assert record["baseline_eligible"] is False
|
||||
assert "| RECIPE_MISMATCH |" in markdown
|
||||
|
||||
|
||||
def test_main_recipe_change_as_new_variant_is_calibration_needed(monkeypatch, tmp_path):
|
||||
exit_code, record, _ = _run_compare(
|
||||
monkeypatch, tmp_path,
|
||||
_v2_raw_result(variant_id="1.3b-sp2-r2", recipe_fingerprint="recipe-2"),
|
||||
[_v2_baseline_record()])
|
||||
|
||||
assert exit_code == 0
|
||||
assert record["comparator_status"] == "CALIBRATION_NEEDED"
|
||||
|
||||
|
||||
def test_main_v2_record_never_compares_against_v1_baselines(monkeypatch, tmp_path):
|
||||
legacy_v1_baseline = {
|
||||
"gpu_type": "NVIDIA L40S",
|
||||
# Would be a >100% latency regression if the comparator (wrongly)
|
||||
# matched the v2 record against v1 history.
|
||||
"latency": 1.0,
|
||||
"timestamp": "2026-06-15T00:00:00+00:00",
|
||||
"success": True,
|
||||
}
|
||||
|
||||
exit_code, record, _ = _run_compare(
|
||||
monkeypatch, tmp_path, _v2_raw_result(), [legacy_v1_baseline])
|
||||
|
||||
assert exit_code == 0
|
||||
assert record["comparator_status"] == "CALIBRATION_NEEDED"
|
||||
|
||||
|
||||
def test_main_legacy_v1_record_skips_comparison_with_pass_verdict(monkeypatch, tmp_path):
|
||||
legacy_v1_baseline = {
|
||||
"gpu_type": "NVIDIA L40S",
|
||||
# Would be a >100% latency regression if the comparator (wrongly)
|
||||
# compared the identity-less v1 record against this history.
|
||||
"latency": 1.0,
|
||||
"timestamp": "2026-06-15T00:00:00+00:00",
|
||||
"success": True,
|
||||
}
|
||||
|
||||
exit_code, record, markdown = _run_compare(
|
||||
monkeypatch, tmp_path, _raw_result(), [legacy_v1_baseline])
|
||||
|
||||
assert exit_code == 0
|
||||
assert record["comparator_status"] == "PASS"
|
||||
assert record["baseline_status"] == "skipped_missing_identity"
|
||||
assert record["baseline_eligible"] is False
|
||||
assert "| PASS |" in markdown
|
||||
|
||||
|
||||
def test_main_partial_identity_skips_comparison(monkeypatch, tmp_path):
|
||||
raw = _v2_raw_result()
|
||||
del raw["software_profile_id"]
|
||||
|
||||
exit_code, record, _ = _run_compare(monkeypatch, tmp_path, raw, [])
|
||||
|
||||
assert exit_code == 0
|
||||
assert record["comparator_status"] == "PASS"
|
||||
assert record["baseline_status"] == "skipped_missing_identity"
|
||||
assert record["success"] is True
|
||||
assert record["baseline_eligible"] is False
|
||||
|
||||
|
||||
def test_main_baseline_load_failure_is_infra_error_and_fails_ci(monkeypatch, tmp_path):
|
||||
def _boom(*_args, **_kwargs):
|
||||
raise OSError("hf store unavailable")
|
||||
|
||||
monkeypatch.setattr(compare_baseline, "load_records_for_model", _boom)
|
||||
|
||||
exit_code, record, markdown = _run_compare(
|
||||
monkeypatch, tmp_path, _v2_raw_result(), [])
|
||||
|
||||
assert exit_code == 1
|
||||
assert record["comparator_status"] == "INFRA_ERROR"
|
||||
assert record["success"] is False
|
||||
assert record["baseline_eligible"] is False
|
||||
assert "| INFRA_ERROR |" in markdown
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Run with:
|
||||
# FASTVIDEO_ATTENTION_BACKEND=FLASH_ATTN fastvideo generate --config scripts/inference/inference_wan_VSA_DMD_5B_720P.yaml
|
||||
# FullAttn checkpoints use dense attention; do not run this config with VIDEO_SPARSE_ATTN.
|
||||
generator:
|
||||
model_path: FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers
|
||||
engine:
|
||||
|
||||
Reference in New Issue
Block a user