Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 70 additions & 140 deletions backend/api_model_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,37 @@
}


def _resolution_spec(
*,
fps_to_durations: dict[LTXVideoGenFps, tuple[LTXVideoGenDuration, ...]],
) -> LTXVideoGenerationResolutionSpec:
_ApiDurationEnvelope = tuple[LTXVideoGenDuration, ...]
_ApiFpsDurationMap = dict[LTXVideoGenFps, _ApiDurationEnvelope]
_ApiResolutionMap = dict[LTXVideoGenResolution, LTXVideoGenerationResolutionSpec]

# API duration envelopes (seconds). ltxv-api accepts 2–20s. GenSpace floors the
# picker at 6s; gap fill uses smallest_valid from this full list.
# 20s is Fast 720p/1080p at 24/25 and the A2V standard-tier audio cap.
_API_DURATIONS_TO_10S: _ApiDurationEnvelope = (2, 3, 4, 5, 6, 8, 10)
_API_DURATIONS_TO_20S: _ApiDurationEnvelope = (2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 18, 20)

_API_FPS_STANDARD: _ApiFpsDurationMap = {
24: _API_DURATIONS_TO_10S,
25: _API_DURATIONS_TO_10S,
48: _API_DURATIONS_TO_10S,
50: _API_DURATIONS_TO_10S,
}
_API_FPS_EXTENDED: _ApiFpsDurationMap = {
24: _API_DURATIONS_TO_20S,
25: _API_DURATIONS_TO_20S,
48: _API_DURATIONS_TO_10S,
50: _API_DURATIONS_TO_10S,
}
# ltx-2-5-pro has no 48 fps in MODEL_CAPABILITY_MATRIX.
_API_FPS_PRO_2_5: _ApiFpsDurationMap = {
24: _API_DURATIONS_TO_10S,
25: _API_DURATIONS_TO_10S,
50: _API_DURATIONS_TO_10S,
}


def _resolution_spec(fps_to_durations: _ApiFpsDurationMap) -> LTXVideoGenerationResolutionSpec:
return LTXVideoGenerationResolutionSpec(
fps_to_durations={
fps: list(durations)
Expand All @@ -41,166 +68,69 @@ def _resolution_spec(
)


_API_STANDARD_RESOLUTION = _resolution_spec(_API_FPS_STANDARD)
_API_EXTENDED_RESOLUTION = _resolution_spec(_API_FPS_EXTENDED)
_API_PRO_2_5_RESOLUTION = _resolution_spec(_API_FPS_PRO_2_5)

# Fast t2v/i2v: 720p/1080p get 20s at 24/25; 1440p/4K stay at 10s.
_API_FAST_RESOLUTIONS: _ApiResolutionMap = {
"720p": _API_EXTENDED_RESOLUTION,
"1080p": _API_EXTENDED_RESOLUTION,
"1440p": _API_STANDARD_RESOLUTION,
"2160p": _API_STANDARD_RESOLUTION,
}
# Pro 2.3 t2v/i2v: 10s at every fps and resolution, including 720p.
_API_PRO_RESOLUTIONS: _ApiResolutionMap = {
"720p": _API_STANDARD_RESOLUTION,
"1080p": _API_STANDARD_RESOLUTION,
"1440p": _API_STANDARD_RESOLUTION,
"2160p": _API_STANDARD_RESOLUTION,
}
# Pro 2.5 t2v/i2v/a2v: 720p+1080p, 24/25/50, 10s. No 48 fps, no 1440p/4K.
_API_PRO_2_5_RESOLUTIONS: _ApiResolutionMap = {
"720p": _API_PRO_2_5_RESOLUTION,
"1080p": _API_PRO_2_5_RESOLUTION,
}
# A2V for Pro 2.3 / Fast 2.5: 20s audio at 720p/1080p, 10s at 1440p/4K.
_API_A2V_RESOLUTIONS: _ApiResolutionMap = {
"720p": _API_EXTENDED_RESOLUTION,
"1080p": _API_EXTENDED_RESOLUTION,
"1440p": _API_STANDARD_RESOLUTION,
"2160p": _API_STANDARD_RESOLUTION,
}


ltx_api_model_specs: tuple[tuple[LTXVideoGenPipeline, LTXVideoGenerationSpec], ...] = (
(
"fast",
LTXVideoGenerationSpec(
display_name="LTX-2.3 Fast (API)",
supported_resolutions_durations={
"1080p": _resolution_spec(
fps_to_durations={
24: (6, 8, 10, 12, 14, 16, 18, 20),
25: (6, 8, 10, 12, 14, 16, 18, 20),
48: (6, 8, 10),
50: (6, 8, 10),
},
),
"1440p": _resolution_spec(
fps_to_durations={
24: (6, 8, 10),
25: (6, 8, 10),
48: (6, 8, 10),
50: (6, 8, 10),
},
),
"2160p": _resolution_spec(
fps_to_durations={
24: (6, 8, 10),
25: (6, 8, 10),
48: (6, 8, 10),
50: (6, 8, 10),
},
),
},
supported_resolutions_durations=_API_FAST_RESOLUTIONS,
# No A2V envelope: ltxv-api audio-to-video does not accept ltx-2-3-fast.
),
),
(
"pro",
LTXVideoGenerationSpec(
display_name="LTX-2.3 Pro (API)",
supported_resolutions_durations={
"1080p": _resolution_spec(
fps_to_durations={
24: (6, 8, 10),
25: (6, 8, 10),
48: (6, 8, 10),
50: (6, 8, 10),
},
),
"1440p": _resolution_spec(
fps_to_durations={
24: (6, 8, 10),
25: (6, 8, 10),
48: (6, 8, 10),
50: (6, 8, 10),
},
),
"2160p": _resolution_spec(
fps_to_durations={
24: (6, 8, 10),
25: (6, 8, 10),
48: (6, 8, 10),
50: (6, 8, 10),
},
),
},
a2v_supported_resolutions_durations={
"1080p": _resolution_spec(
fps_to_durations={
24: (6, 8, 10),
25: (6, 8, 10),
48: (6, 8, 10),
50: (6, 8, 10),
},
),
},
supported_resolutions_durations=_API_PRO_RESOLUTIONS,
a2v_supported_resolutions_durations=_API_A2V_RESOLUTIONS,
),
),
# ltx-2-5-fast: t2v/i2v duration envelope matches API Fast. A2V is 1080p
# (ltxv-api MAX_AUDIO_SECONDS for this model).
(
"fast-2.5",
LTXVideoGenerationSpec(
display_name="LTX-2.5 Fast (API)",
supported_resolutions_durations={
"1080p": _resolution_spec(
fps_to_durations={
24: (6, 8, 10, 12, 14, 16, 18, 20),
25: (6, 8, 10, 12, 14, 16, 18, 20),
48: (6, 8, 10),
50: (6, 8, 10),
},
),
"1440p": _resolution_spec(
fps_to_durations={
24: (6, 8, 10),
25: (6, 8, 10),
48: (6, 8, 10),
50: (6, 8, 10),
},
),
"2160p": _resolution_spec(
fps_to_durations={
24: (6, 8, 10),
25: (6, 8, 10),
48: (6, 8, 10),
50: (6, 8, 10),
},
),
},
a2v_supported_resolutions_durations={
"1080p": _resolution_spec(
fps_to_durations={
24: (6, 8, 10),
25: (6, 8, 10),
48: (6, 8, 10),
50: (6, 8, 10),
},
),
},
supported_resolutions_durations=_API_FAST_RESOLUTIONS,
a2v_supported_resolutions_durations=_API_A2V_RESOLUTIONS,
),
),
(
"pro-2.5",
LTXVideoGenerationSpec(
display_name="LTX-2.5 Pro (API)",
supported_resolutions_durations={
"1080p": _resolution_spec(
fps_to_durations={
24: (6, 8, 10),
25: (6, 8, 10),
48: (6, 8, 10),
50: (6, 8, 10),
},
),
"1440p": _resolution_spec(
fps_to_durations={
24: (6, 8, 10),
25: (6, 8, 10),
48: (6, 8, 10),
50: (6, 8, 10),
},
),
"2160p": _resolution_spec(
fps_to_durations={
24: (6, 8, 10),
25: (6, 8, 10),
48: (6, 8, 10),
50: (6, 8, 10),
},
),
},
a2v_supported_resolutions_durations={
"1080p": _resolution_spec(
fps_to_durations={
24: (6, 8, 10),
25: (6, 8, 10),
48: (6, 8, 10),
50: (6, 8, 10),
},
),
},
supported_resolutions_durations=_API_PRO_2_5_RESOLUTIONS,
a2v_supported_resolutions_durations=_API_PRO_2_5_RESOLUTIONS,
),
),
)
Expand Down
2 changes: 1 addition & 1 deletion backend/api_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ class LtxInsufficientFundsErrorResponse(BaseModel):


LTXVideoGenResolution: TypeAlias = Literal["540p", "720p", "1080p", "1440p", "2160p"]
LTXVideoGenDuration: TypeAlias = Literal[5, 6, 8, 10, 12, 14, 16, 18, 20]
LTXVideoGenDuration: TypeAlias = Literal[2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 18, 20]
LTXVideoGenFps: TypeAlias = Literal[24, 25, 48, 50]
LTXVideoGenPipeline: TypeAlias = Literal["fast", "pro", "fast-2.5", "pro-2.5"]

Expand Down
2 changes: 1 addition & 1 deletion backend/handlers/text_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def _prepare_api_embeddings(self, prompt: str, enhance_prompt: bool) -> TextEnco
api_key=settings.ltx_api_key,
checkpoint_path=str(get_existing_cp_path(self.models_dir, model_spec.model_cp)),
enhance_prompt=enhance_prompt,
api_model_id=model_spec.api_text_encoder_model_id,
api_model=model_spec.api_prompt_embedding_model,
)
if encoded is not None:
self._cache_prompt(prompt, enhance_prompt, encoded)
Expand Down
2 changes: 1 addition & 1 deletion backend/ltx2_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
del _safetensors_loader_fix
import services.patches.safetensors_metadata_fix as _safetensors_metadata_fix # pyright: ignore[reportUnusedImport] # Remove once safetensors supports read-only mmap
del _safetensors_metadata_fix
import services.patches.pinned_pool_fix as _pinned_pool_fix # pyright: ignore[reportUnusedImport] # Remove once ltx-core restores bounded pinned pool
import services.patches.pinned_pool_fix as _pinned_pool_fix # pyright: ignore[reportUnusedImport] # Remove once ltx-core alloc_buffer does not report pinned-host failure as CUDA VRAM OOM (LTX-Desktop#141)
del _pinned_pool_fix
import services.patches.ic_lora_stage2_lora as _ic_lora_stage2_lora # pyright: ignore[reportUnusedImport] # EXPERIMENTAL: remove once upstream ships PR #494 (use_lora_in_stage_2)
del _ic_lora_stage2_lora
Expand Down
32 changes: 25 additions & 7 deletions backend/runtime_config/ltx_api_text_encoder_ids.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
"""API text-encoder model ids used by /v1/prompt-embedding.
"""Selectors for `/v1/prompt-embedding`.

These values are the worker `encrypted_wandb_properties` payload (Fernet), not a
user secret. Split 2.5 checkpoints omit this header, so generation falls back
to this override. Rotate the blob when serving rotates the key.
LTX 2.5 OS checkpoints identify the text encoder via `gemma_source_checkpoint`
metadata (`ltx_version` + `gemma_version`), not a usable `model_id`. The public
endpoint accepts exactly one of:

- `model_id` — legacy Comfy / LTX 2.3 (`encrypted_wandb_properties` on the checkpoint)
- `model` — `{ ltx_version, gemma_version }` for LTX 2.5+

Split 2.5 checkpoints omit `encrypted_wandb_properties`, so generation uses this
`model` override. Keep it in lockstep with the gateway's
`LTX_2_5_GEMMA_SOURCE_CHECKPOINT`.
"""

# Worker key for LTX 2.5 Fast prompt-embedding. Do not replace with a plaintext
# id unless the worker is verified to accept one.
LTX_2_5_API_TEXT_ENCODER_MODEL_ID = "gAAAAABqcenRC9mecBlpR24xY8xWrCgERvgD5LK80sJw1J9WuSbi_J_SYXw4L3UCrCBciI5T6KJ7o5ZOld2hM6rYbs6R7_XftiN6MghrdbNt1bMZ_boxWcZ-HFvWQYgkAhJGjqLUPLyiCzDf4Idi1UQ8JebKcdK2qTxMCAEDIqLEAg_IwfPHd93YPHzT11gURnQ9vWEkJU385nRu8SRnI1ubKyGx78FmPSa8CRdh3I0zrJawmi0On2BnehplBzKU-Ub2IZaGKOP5pN9yLPW6fjgHi28d20hlNu5SoQl2fD1QnJBYNPbd6YIWYgmL7dYZzZFNLLpwYsXaY48Kh_uPY8p_wALCu_Vl2ONcEvy9spFRW5tRn0YaCs8SuIrv_8MaEO8xG0s5vLmsRfhA29yvhUIqOAuTo13vuqCKlVXv9gm17wjzkK2zcPzE_e26KUGyQepqLVHDWWxxZyPtXiWIkdOwFyzdTYlEjegEMmpMoT2dk-A-AOLdmx6QSpNd7oHcgoS78OsMO-hKG9pdjpmA6ryn3YwvAYumiFuTFM9ELKph2LEdFCyMUFp4mBaLTIxpROEc9b6xSRTQOYxSvzMkPsuJYYsdJXHB4MiDYGmzNoghIpFNABiv_i2Q8H4b4M5wi8PuYLQICiKbYCEOvlF6U_9TlIZpWf3Pt7DU5ufsCHAhYmUmSYQDerlNISYojFqOBRzyik3tqExlDdrIr51bn83YyJSnvC_q88K06dtwC4CrmVyGI0QDMcGowxLqDvdrsfYwxBLBUY_No1D6qA8_SyxlMchRF0rOzUJ_gI3MJJGeQ9loWIb-oG_RveHXQsWTP82wy7TmlOxi1BkplatMPR07qAINEYGBMApZ7ZuMWAV70c80tevyZkSCBu4OY3wMZmVHDQHIO4NM4_261A8e7ISg7fP6mJIkBehc9yGvZuE3eGQK1PD6USYjbabs-TVsLo1ZUn5_5kCn8cMCnyT2djYi3zG2CaFTfBmUSsF9ubXzk4BLDY3b1B9V4RtQf0cOUMSOvBDqi2Rvv0YXMxZcS4OdQDdUxzGC5_XekkM_HWITsyazl8jx713Dk_5fV-Z3n0_pjSVLVZUVr1DRnPkUsSDUpzq9zZ6G-i2c2AoUA2T1LRvZCKs8inqz5Y-6WJJP2R9IMpG22pjWc_Rpg2Fdd1SDnd9umy_AR-VNu2ZP0IC13MpvheyoQnPfIln7yZVupdvitfIeFKaibatgmWgVqtEevP_zavu-MnmDBpKd2mTBAPHD7X9QJXkZUtgebPjJQ3j9SQrJTIS899O4QZxC0r4h9bzpM7bEsKGO6KrnCsMISE5KgNGccPATxnOTuhQznjCfT763DpQz7MJxI8PRHEo2aR4b1uSjILrNMLczMIOx6GZPAxvPHM7HjBQZQSZThiRhhqh1kJk47w13vN2VNy0XSIwPjVPj84Af_yxB6K-2BiLQtn6ndhjDlTJ-nPBhLXltfnfKKksUt_LzYl2pd76i15oUCJrs52SNAyeN_D2uEcQ0FiSUB4u-hiYilpg_dtV2N2Jx9DmeewTcJEY9VCQr7Ccvp__EgJaDLFgFCPMPMAp_BsgUbJJIQEDGhjYJLypOIpBqJgwfIprvn7P7dVTseNL9MrZEgYI4IbABj6hVHsPGDBtTXiTU9PtTrLBiMAlJNE4Kdkd99YJuTWqLdps-rIfyagNoeJ5ItPzmDvy0oeDyyl93dLRpHtwyRtvBUB3IWGknVfX7Gj8GzfnvmIGw85Rh000AhtIIp8zH7WXIxNc8SF5zQZM06_LVePt77Ag58ihsADiRJZEniUlTGSGynZNr3Rrv4tAZUIkhIIUVGVNejtLQhLLIwY_TsblO_6H2cBb4Ep-2T4vnNk1zD0pwMDQXoHy_U3aG00FOgsSje4sTbqGOvUeO7PCEhZ8a9OoDXoNV_Myi"
from typing import TypedDict


class LtxApiPromptEmbeddingModel(TypedDict):
ltx_version: str
gemma_version: str


# Same pair the LTX API video path uses for LTX-2.5 prompt-encode.
# Source: https://github.com/LightricksResearch/ltxv-api/pull/1604
LTX_2_5_API_PROMPT_EMBEDDING_MODEL: LtxApiPromptEmbeddingModel = {
"ltx_version": "2.5.0",
"gemma_version": "gemma4-12b-ltx-v1",
}
Loading
Loading