inferencekit-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. inferencekit/__init__.py +9 -0
  2. inferencekit/__main__.py +5 -0
  3. inferencekit/analysis/__init__.py +1 -0
  4. inferencekit/analysis/ncu_csv.py +284 -0
  5. inferencekit/analysis/nsys_sqlite.py +518 -0
  6. inferencekit/canonical.py +104 -0
  7. inferencekit/checkpoint.py +396 -0
  8. inferencekit/cli.py +274 -0
  9. inferencekit/cuda/bw_peak.cu +252 -0
  10. inferencekit/cuda/torch_bw.py +60 -0
  11. inferencekit/engines/__init__.py +7 -0
  12. inferencekit/engines/adapters.py +190 -0
  13. inferencekit/engines/clip/__init__.py +13 -0
  14. inferencekit/engines/clip/comfyui.py +749 -0
  15. inferencekit/engines/clip/diffusers_ltx.py +601 -0
  16. inferencekit/engines/clip/noise.py +46 -0
  17. inferencekit/engines/clip/official_ltx.py +316 -0
  18. inferencekit/engines/clip/server.py +542 -0
  19. inferencekit/engines/clip/stepdump.py +182 -0
  20. inferencekit/engines/gates.py +94 -0
  21. inferencekit/engines/sglang_worker.py +86 -0
  22. inferencekit/engines/vllm_worker.py +129 -0
  23. inferencekit/evidence.py +427 -0
  24. inferencekit/gpuinfo.py +93 -0
  25. inferencekit/hardware.py +135 -0
  26. inferencekit/mcp_server.py +178 -0
  27. inferencekit/ops/__init__.py +30 -0
  28. inferencekit/ops/bench.py +184 -0
  29. inferencekit/ops/bench_audio.py +384 -0
  30. inferencekit/ops/bench_clip.py +819 -0
  31. inferencekit/ops/bench_image.py +393 -0
  32. inferencekit/ops/bench_rounds.py +297 -0
  33. inferencekit/ops/bench_serve.py +866 -0
  34. inferencekit/ops/ceiling.py +135 -0
  35. inferencekit/ops/compare.py +378 -0
  36. inferencekit/ops/correctness.py +153 -0
  37. inferencekit/ops/doctor.py +486 -0
  38. inferencekit/ops/gates.py +1772 -0
  39. inferencekit/ops/gates_clip.py +930 -0
  40. inferencekit/ops/intake.py +1321 -0
  41. inferencekit/ops/ledger.py +749 -0
  42. inferencekit/ops/manifest.py +309 -0
  43. inferencekit/ops/ncu.py +183 -0
  44. inferencekit/ops/nsys.py +224 -0
  45. inferencekit/ops/prompts.py +316 -0
  46. inferencekit/ops/quality.py +2161 -0
  47. inferencekit/ops/quality_clip.py +690 -0
  48. inferencekit/ops/quality_clip_steps.py +341 -0
  49. inferencekit/ops/report.py +317 -0
  50. inferencekit/ops/review.py +119 -0
  51. inferencekit/ops/rocprof.py +89 -0
  52. inferencekit/ops/roofline.py +392 -0
  53. inferencekit/ops/store.py +61 -0
  54. inferencekit/proc.py +172 -0
  55. inferencekit/prompts/ATTRIBUTION.md +56 -0
  56. inferencekit/prompts/__init__.py +167 -0
  57. inferencekit/prompts/build.py +518 -0
  58. inferencekit/prompts/dev-load.jsonl.gz +0 -0
  59. inferencekit/prompts/dev-load.manifest.json +212 -0
  60. inferencekit/prompts/dev.jsonl.gz +0 -0
  61. inferencekit/prompts/dev.manifest.json +214 -0
  62. inferencekit/prompts/exclusions.json +54 -0
  63. inferencekit/quality/__init__.py +23 -0
  64. inferencekit/quality/client.py +66 -0
  65. inferencekit/quality/data/gsm8k_LICENSE.txt +21 -0
  66. inferencekit/quality/fake.py +329 -0
  67. inferencekit/quality/gsm8k.py +224 -0
  68. inferencekit/quality/logprobs.py +106 -0
  69. inferencekit/quality/metrics.py +235 -0
  70. inferencekit/quality/niah.py +63 -0
  71. inferencekit/quality/parquet.py +296 -0
  72. inferencekit/quality/sampling.py +262 -0
  73. inferencekit/quality/step_replay.py +217 -0
  74. inferencekit/quality/video_metrics.py +304 -0
  75. inferencekit/registry.py +206 -0
  76. inferencekit/review/__init__.py +6 -0
  77. inferencekit/review/diff.py +163 -0
  78. inferencekit/review/media.py +181 -0
  79. inferencekit/review/page.py +920 -0
  80. inferencekit/review/rundir.py +844 -0
  81. inferencekit/roofline_headers.py +504 -0
  82. inferencekit/schemas/evidence.v1.json +130 -0
  83. inferencekit/schemas/evidence.v2.json +499 -0
  84. inferencekit/schemas/intake.v1.json +276 -0
  85. inferencekit/schemas/run-manifest.v1.json +118 -0
  86. inferencekit/serve/__init__.py +8 -0
  87. inferencekit/serve/audio_client.py +330 -0
  88. inferencekit/serve/client.py +446 -0
  89. inferencekit/serve/clip_client.py +351 -0
  90. inferencekit/serve/clip_rounds.py +429 -0
  91. inferencekit/serve/image_client.py +373 -0
  92. inferencekit/serve/loadgen.py +136 -0
  93. inferencekit/serve/metrics.py +203 -0
  94. inferencekit/serve/profiles.py +105 -0
  95. inferencekit/serve/prompts_io.py +125 -0
  96. inferencekit/serve/rounds.py +1111 -0
  97. inferencekit/serve/servers.py +897 -0
  98. inferencekit/stats.py +249 -0
  99. inferencekit_cli-0.1.0.dist-info/METADATA +233 -0
  100. inferencekit_cli-0.1.0.dist-info/RECORD +104 -0
  101. inferencekit_cli-0.1.0.dist-info/WHEEL +4 -0
  102. inferencekit_cli-0.1.0.dist-info/entry_points.txt +2 -0
  103. inferencekit_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
  104. inferencekit_cli-0.1.0.dist-info/licenses/NOTICE +9 -0
@@ -0,0 +1,316 @@
1
+ """The official LTX-2 inference pipelines (the ``ltx_pipelines`` package) behind the clip server.
2
+
3
+ python -m inferencekit.engines.clip.server --pipeline official_ltx --model DIR --task t2v \\
4
+ --pipeline-arg builder=MODULE:CALLABLE [--pipeline-arg builder.NAME=VALUE ...] \\
5
+ [--pipeline-arg step_module=ATTR.PATH --pipeline-arg forwards_per_step=N]
6
+
7
+ Run on a GPU host with ltx-pipelines v1.4.2 (15-16 Oct 2026: the LTX-2 monolith checkpoint, and LTX-2.5's split
8
+ files through ``builder.transformer/text_encoder/video_vae/audio_vae``, with the one_stage builder below; on
9
+ LTX-2.5 bench rounds and gates clip PASS for t2v and i2v). Written before the package was installed: Diffusers'
10
+ encode_video cites
11
+ ``packages/ltx-pipelines/src/ltx_pipelines/utils/media_io.py`` in the LTX-2 repository, so the import name
12
+ is ``ltx_pipelines``. Everything else is looked up when the server starts or a clip runs, and refused when
13
+ it is not there, rather than guessed:
14
+
15
+ * The pipeline: ``builder=MODULE:CALLABLE`` (required; e.g. a pipeline class or a small function of
16
+ your own in that venv) is called as ``builder(model=..., device=..., dtype=..., **builder.* args)``;
17
+ the object it returns makes one clip per call (``generate`` if it has one, else ``__call__``).
18
+ * Call arguments are matched by name (ALIASES, e.g. ``num_inference_steps`` or ``steps``). A request
19
+ field with no matching argument is refused (400), never dropped.
20
+ * Noise: if the call takes ``generator``, it gets ``torch.Generator("cpu").manual_seed(noise_seed)``;
21
+ else, if it takes ``seed``, it gets ``noise_seed``. Whether the package draws video then audio noise
22
+ from it the way noise.py does is unchecked, so ``effective.noise_check`` is false: do not use this
23
+ engine as one side of a shared-noise quality comparison until a check shows it.
24
+ * Steps: ``step_module=ATTR.PATH`` names the denoiser module on the pipeline object; its forward
25
+ calls are counted and divided by ``forwards_per_step`` (default 1; 2 when guidance runs the
26
+ conditional and unconditional passes as separate calls). Without it steps_run is null (not counted)
27
+ and bench clip says the steps were not checked.
28
+ * Output: if the call takes ``output_path`` the package writes the mp4 itself; otherwise it must return
29
+ (video, audio) or an object with ``video``/``frames`` and ``audio``, frames shaped [F, H, W, 3]
30
+ (uint8, or float in [0, 1]), which are muxed with ``diffusers.utils.encode_video`` (needs Diffusers
31
+ and PyAV in that venv). Audio needs ``--pipeline-arg audio_sample_rate=N``.
32
+
33
+ Stages: only ``generate`` (the whole call) and ``mux``; the package's inner stages are not hooked.
34
+
35
+ Per-step dumps (stepdump.py, for quality clip-steps): NOT written. The package's denoising loop is not hooked (it
36
+ was not read when this wrapper was written), so an engine served through this wrapper has no dumps and cannot pass
37
+ quality clip-steps; a hook belongs where the package calls its sampler step, recording x, the guided velocity and
38
+ the next latent with stepdump.StepRecorder and returning it as Generated.step_dump.
39
+ """
40
+
41
+ from __future__ import annotations
42
+
43
+ import hashlib
44
+ import importlib
45
+ import inspect
46
+ import io
47
+
48
+ from inferencekit.engines.clip.server import BadRequest, ClipJob, Generated, PipelineOptions, StageClock
49
+
50
+ # request field -> call argument names to try, in order
51
+ ALIASES = {
52
+ "prompt": ("prompt",),
53
+ "width": ("width",),
54
+ "height": ("height",),
55
+ "num_frames": ("num_frames", "frames", "length"),
56
+ "fps": ("frame_rate", "fps"),
57
+ "steps": ("num_inference_steps", "steps", "num_steps"),
58
+ "guidance_scale": ("guidance_scale", "cfg_guidance_scale", "cfg_scale", "cfg"),
59
+ "image": ("image", "images", "conditioning_image"),
60
+ }
61
+ OPTIONS = {"builder", "step_module", "forwards_per_step", "audio_sample_rate", "negative_prompt"}
62
+
63
+
64
+ def _resolve(spec: str):
65
+ mod, _, attr = str(spec).partition(":")
66
+ if not attr:
67
+ raise ValueError(f"builder must be MODULE:CALLABLE, got {spec!r}")
68
+ obj = importlib.import_module(mod)
69
+ for part in attr.split("."):
70
+ obj = getattr(obj, part)
71
+ return obj
72
+
73
+
74
+ def make_pipeline(opts: PipelineOptions) -> OfficialLTX:
75
+ try:
76
+ import ltx_pipelines # noqa: F401 - the guard: refuse early when the package is missing
77
+ except ImportError as e:
78
+ raise ImportError("official_ltx needs the ltx_pipelines package (LTX-2 repository) in this "
79
+ f"interpreter: {e}") from None
80
+ import torch
81
+
82
+ unknown = sorted(k for k in opts.extra if k not in OPTIONS and not k.startswith("builder."))
83
+ if unknown:
84
+ raise ValueError(f"unknown --pipeline-arg {unknown}; known: {sorted(OPTIONS)} and builder.NAME")
85
+ if not opts.extra.get("builder"):
86
+ raise ValueError("official_ltx needs --pipeline-arg builder=MODULE:CALLABLE (see the module docstring)")
87
+ dtypes = {"bf16": torch.bfloat16, "fp16": torch.float16, "fp32": torch.float32}
88
+ if opts.dtype not in dtypes:
89
+ raise ValueError(f"--dtype must be one of {sorted(dtypes)}")
90
+ if opts.offload != "none" or opts.attention:
91
+ raise ValueError("official_ltx takes no --offload or --attention (pass the package's own options "
92
+ "as builder.NAME=VALUE)")
93
+ build = _resolve(opts.extra["builder"])
94
+ bkw = {k[8:]: v for k, v in opts.extra.items() if k.startswith("builder.")}
95
+ pipe = build(model=opts.model, device=opts.device, dtype=dtypes[opts.dtype], **bkw)
96
+ version = getattr(importlib.import_module("ltx_pipelines"), "__version__", None)
97
+ return OfficialLTX(pipe, opts, torch, {"pipeline": f"ltx_pipelines:{opts.extra['builder']}",
98
+ "ltx_pipelines_version": version, "torch_version": torch.__version__})
99
+
100
+
101
+ class OfficialLTX:
102
+ def __init__(self, pipe, opts: PipelineOptions, torch_mod, facts: dict | None = None):
103
+ self.pipe, self.opts, self.torch = pipe, opts, torch_mod
104
+ self.facts = dict(facts or {})
105
+ self.fn = getattr(pipe, "generate", None) or pipe
106
+ self.params = set(inspect.signature(self.fn).parameters)
107
+ self.forwards = 0
108
+ self.per_step = int(opts.extra.get("forwards_per_step") or 1)
109
+ self.counting = False
110
+ self._out_path: str | None = None
111
+ if opts.extra.get("step_module"):
112
+ mod = pipe
113
+ for part in str(opts.extra["step_module"]).split("."):
114
+ mod = getattr(mod, part)
115
+ mod.register_forward_pre_hook(self._count)
116
+ self.counting = True
117
+
118
+ def _count(self, module, args):
119
+ self.forwards += 1
120
+
121
+ def _arg(self, field: str) -> str:
122
+ for name in ALIASES[field]:
123
+ if name in self.params:
124
+ return name
125
+ raise BadRequest(f"the pipeline call takes none of {list(ALIASES[field])} for {field}")
126
+
127
+ def describe(self) -> dict:
128
+ neg = self.opts.extra.get("negative_prompt")
129
+ own = self.pipe.describe() if callable(getattr(self.pipe, "describe", None)) else {}
130
+ return {**self.facts, "model": self.opts.model, "task": self.opts.task, "dtype": self.opts.dtype,
131
+ "attention": "package default", "device": self.opts.device,
132
+ "steps_counted": self.counting or hasattr(self.pipe, "steps_run"),
133
+ "negative_prompt_sha256": hashlib.sha256(str(neg).encode()).hexdigest() if neg is not None else None,
134
+ **own}
135
+
136
+ def noise_shapes(self, job: ClipJob) -> dict:
137
+ if job.task != self.opts.task:
138
+ raise BadRequest(f"this server loaded the {self.opts.task} pipeline; start another for {job.task}")
139
+ if "output_path" not in self.params and float(job.fps) != int(job.fps):
140
+ raise BadRequest(f"fps must be a whole number for this muxer, got {job.fps:g}")
141
+ return {} # the package draws its own noise (see the module docstring)
142
+
143
+ def generate(self, job: ClipJob, noise: dict, clock: StageClock) -> Generated:
144
+ kw = {self._arg(f): getattr(job, f) for f in ("prompt", "width", "height", "num_frames", "fps", "steps",
145
+ "guidance_scale")}
146
+ if job.task == "i2v":
147
+ from PIL import Image
148
+
149
+ kw[self._arg("image")] = Image.open(io.BytesIO(job.image)).convert("RGB")
150
+ if "generator" in self.params:
151
+ kw["generator"], how = self.torch.Generator(device="cpu").manual_seed(job.noise_seed), "cpu generator"
152
+ elif "seed" in self.params:
153
+ kw["seed"], how = job.noise_seed, "package seed"
154
+ else:
155
+ raise BadRequest("the pipeline call takes neither generator nor seed: the noise cannot be fixed")
156
+ if self.opts.extra.get("negative_prompt") is not None:
157
+ if "negative_prompt" not in self.params:
158
+ raise BadRequest("negative_prompt was set but the pipeline call does not take it")
159
+ kw["negative_prompt"] = self.opts.extra["negative_prompt"]
160
+ self._out_path = None
161
+ if "output_path" in self.params:
162
+ import os
163
+ import tempfile
164
+
165
+ fd, self._out_path = tempfile.mkstemp(suffix=".mp4", dir=os.getcwd())
166
+ os.close(fd)
167
+ kw["output_path"] = self._out_path
168
+ self.forwards = 0
169
+ with clock.span("generate"):
170
+ ret = self.fn(**kw)
171
+ if self.counting: # else nothing counted them: the server reports steps_run null, not 0
172
+ clock.set_steps(self.forwards // self.per_step)
173
+ elif isinstance(getattr(self.pipe, "steps_run", None), int): # a builder that counts its own loop
174
+ clock.set_steps(self.pipe.steps_run)
175
+ video, audio = (ret if isinstance(ret, tuple) else (getattr(ret, "video", getattr(ret, "frames", None)),
176
+ getattr(ret, "audio", None)))
177
+ eff = {"noise_check": False, "generator": how}
178
+ if self._out_path: # the package wrote the file: audio is what the builder says it muxed, else unknown
179
+ audio_written = getattr(self.pipe, "audio_written", None)
180
+ return Generated(video=None, width=job.width, height=job.height, frames=job.num_frames, fps=job.fps,
181
+ effective={**eff, "shape_source": "request (the package wrote the file)",
182
+ "audio": audio_written if isinstance(audio_written, bool) else None})
183
+ if video is None:
184
+ raise RuntimeError("the pipeline returned no frames and took no output_path")
185
+ frames = video[0] if getattr(video, "ndim", 4) == 5 else video
186
+ n, h, w = (int(d) for d in frames.shape[:3])
187
+ rate = self.opts.extra.get("audio_sample_rate")
188
+ wave = None
189
+ if audio is not None and job.audio is not False and rate:
190
+ wave = audio[0] if getattr(audio, "ndim", 2) == 3 else audio
191
+ return Generated(video=frames, width=w, height=h, frames=n, fps=job.fps, audio=wave,
192
+ audio_sample_rate=int(rate) if wave is not None else None, effective=eff)
193
+
194
+ def write(self, out: Generated, path, clock: StageClock) -> None:
195
+ if out.video is None and self._out_path:
196
+ import shutil
197
+
198
+ shutil.move(self._out_path, path)
199
+ return
200
+ from diffusers.utils import encode_video
201
+
202
+ encode_video(out.video, fps=int(out.fps), output_path=str(path), audio=out.audio,
203
+ audio_sample_rate=out.audio_sample_rate)
204
+
205
+
206
+ # --------------------------------------------------------------------------- a builder for the one-stage pipeline
207
+
208
+
209
+ def one_stage(model: str, device: str, dtype, gemma_root: str | None = None, transformer: str | None = None,
210
+ text_encoder: str | None = None, video_vae: str | None = None, audio_vae: str | None = None,
211
+ negative_prompt: str | None = None) -> OneStage:
212
+ """``builder=inferencekit.engines.clip.official_ltx:one_stage``: the official text/image-to-video one-stage
213
+ pipeline (``ltx_pipelines.ti2vid_one_stage.TI2VidOneStagePipeline``, the full model with guidance), with
214
+ the package's own defaults for everything the request does not set.
215
+
216
+ ``--model`` is the monolith checkpoint (e.g. ltx-2-19b-dev.safetensors) with ``builder.gemma_root=DIR``
217
+ (the Gemma text encoder directory), or a split pack: ``builder.transformer=``, ``builder.text_encoder=``,
218
+ ``builder.video_vae=``, ``builder.audio_vae=`` (``--model`` is then only recorded). Guidance: the request's
219
+ guidance_scale is the video CFG scale; every other guider setting (STG, rescale, modality, the audio
220
+ guider) is the package's default for the checkpoint (``detect_params``), recorded in describe(). The
221
+ negative prompt is the package's DEFAULT_NEGATIVE_PROMPT unless ``builder.negative_prompt`` is given. The
222
+ package draws its own noise on the GPU from the seed (no shared-noise recipe), writes the mp4 itself and
223
+ builds the transformer on every call, as it ships. Steps are counted by the denoise loop's calls.
224
+ """
225
+ import torch
226
+ from ltx_pipelines.ti2vid_one_stage import TI2VidOneStagePipeline
227
+ from ltx_pipelines.utils.constants import detect_params
228
+ from ltx_pipelines.utils.model_paths import ModelPaths
229
+
230
+ if dtype != torch.bfloat16:
231
+ raise ValueError("the official one-stage pipeline runs in BF16 only (--dtype bf16)")
232
+ split = {k: v for k, v in (("transformer_path", transformer), ("text_encoder_path", text_encoder),
233
+ ("video_vae_path", video_vae), ("audio_vae_path", audio_vae)) if v}
234
+ if split:
235
+ paths = ModelPaths.from_split(**split)
236
+ checkpoint = transformer or model
237
+ else:
238
+ if not gemma_root:
239
+ raise ValueError("a monolith checkpoint needs --pipeline-arg builder.gemma_root=DIR (the text encoder)")
240
+ paths, checkpoint = ModelPaths.from_monolith(model, gemma_root), model
241
+ params = detect_params(checkpoint)
242
+ pipe = TI2VidOneStagePipeline(model_paths=paths, loras=[], device=torch.device(device))
243
+ if negative_prompt is None:
244
+ from ltx_pipelines.utils.args import DEFAULT_NEGATIVE_PROMPT
245
+ negative_prompt = DEFAULT_NEGATIVE_PROMPT
246
+ return OneStage(pipe, params, negative_prompt)
247
+
248
+
249
+ class _CountingStage:
250
+ """The pipeline's diffusion stage, with its denoiser wrapped so each loop step is counted."""
251
+
252
+ def __init__(self, stage, owner: OneStage):
253
+ self._stage, self._owner = stage, owner
254
+
255
+ def __getattr__(self, name):
256
+ return getattr(self._stage, name)
257
+
258
+ def __call__(self, *args, denoiser, **kwargs):
259
+ owner = self._owner
260
+
261
+ def counted(*a, **k):
262
+ owner.steps_run += 1
263
+ return denoiser(*a, **k)
264
+
265
+ return self._stage(*args, denoiser=counted, **kwargs)
266
+
267
+
268
+ class OneStage:
269
+ """What ``one_stage`` returns: ``generate(...)`` makes one clip and writes it to ``output_path``."""
270
+
271
+ def __init__(self, pipe, params, negative_prompt: str):
272
+ self.pipe, self.params, self.negative_prompt = pipe, params, negative_prompt
273
+ self.steps_run: int | None = None
274
+ self.audio_written: bool | None = None # whether the last clip's file got an audio track
275
+ pipe.stage = _CountingStage(pipe.stage, self)
276
+
277
+ def describe(self) -> dict:
278
+ vg, ag = self.params.video_guider_params, self.params.audio_guider_params
279
+ return {"video_guider": {k: getattr(vg, k) for k in ("stg_scale", "rescale_scale", "modality_scale",
280
+ "skip_step", "stg_blocks")},
281
+ "audio_guider": {k: getattr(ag, k) for k in ("cfg_scale", "stg_scale", "rescale_scale",
282
+ "modality_scale", "skip_step", "stg_blocks")},
283
+ "negative_prompt_sha256": hashlib.sha256(self.negative_prompt.encode()).hexdigest()}
284
+
285
+ def generate(self, prompt, width, height, num_frames, frame_rate, num_inference_steps, guidance_scale, seed,
286
+ output_path, image=None):
287
+ import dataclasses
288
+ import os
289
+ import tempfile
290
+
291
+ import torch
292
+ from ltx_core.model.video_vae import get_video_chunks_number
293
+ from ltx_pipelines.utils.media_io import encode_video
294
+ from ltx_pipelines.utils.types import ImageConditioningInput
295
+
296
+ images, tmp = [], None
297
+ if image is not None: # the package conditions on an image file at frame 0, strength 1.0
298
+ fd, tmp = tempfile.mkstemp(suffix=".png", dir=os.path.dirname(os.path.abspath(output_path)))
299
+ os.close(fd)
300
+ image.save(tmp)
301
+ images = [ImageConditioningInput(tmp, 0, 1.0)]
302
+ self.steps_run, self.audio_written = 0, None
303
+ try:
304
+ with torch.inference_mode():
305
+ out = self.pipe(prompt=prompt, negative_prompt=self.negative_prompt, seed=int(seed), height=int(height),
306
+ width=int(width), frame_rate=float(frame_rate), num_frames=int(num_frames),
307
+ num_inference_steps=int(num_inference_steps),
308
+ video_guider_params=dataclasses.replace(self.params.video_guider_params,
309
+ cfg_scale=float(guidance_scale)),
310
+ audio_guider_params=self.params.audio_guider_params, images=images)
311
+ encode_video(video=out.video, fps=int(frame_rate), audio=out.audio, output_path=str(output_path),
312
+ video_chunks_number=get_video_chunks_number(out.num_frames, out.tiling_config))
313
+ self.audio_written = out.audio is not None
314
+ finally:
315
+ if tmp:
316
+ os.unlink(tmp)