Repository navigation
Add Kandinsky S2V 1.0 (speech-to-video) as a modular pipeline - #15164
Merged
Merged
Conversation
…e inputs - Drop KandinskyS2VPiflowScheduler; the distilled blocks use PiflowScheduler. - PiflowScheduler: on the final step, pass the nominal segment to the policy and clamp only the rollout end to eps, as the native K6 and S2V samplers do. - Keep the S2V RoPE tables as plain attributes so `.to(torch.bfloat16)` leaves them in fp32. - Raise when only one of negative_prompt_embeds / negative_pooled_prompt_embeds is passed, and when the Euler path gets num_inference_steps < 2.
…tional deps - KandinskyS2VModulation calls its layer instead of reading the weight, and keeps the modulation layers in fp32 via _keep_in_fp32_modules (as in K6). - Remove the transformer's `duration` argument; it can only be the frame count. - Import librosa, soundfile and torchvision behind is_*_available(). - PiflowScheduler: walk raw time one segment at a time and give the policy the nominal segment on every step, as the native samplers do. - Update the Pro offload peak in the docs (about 50 GiB).
- Fold the one-line helpers into their callers, inline the memory embedding, name the projection layers explicitly and spell out the block arguments. - Accept `audio` as a mono 16 kHz float waveform (tensor or array) besides a path. - Run the fp16 test with the pipeline loaded in fp16; drop the variant test. - Docs: offload Pro with memory_reserve_margin="40GiB" and show a full run.
- Take `image` as a PIL image and `audio` as a 16 kHz waveform; the docs load them. - Pass components to the encode helpers explicitly. - Drop the guider from the text encoder: standalone it encodes a negative prompt only when passed; in the pipeline it follows the guider. - Remove the input step: the text encoder plans the chunks, the VAE encoder picks the resolution. - Group prepare and the chunk loop into one `denoise` step. - Distilled blocks: no guider and no negative prompt.
…ssor - Inline the grouped attention into the processor. - `prompt` defaults to DEFAULT_PROMPT in its signature; check_inputs only validates. - Add auto docstrings to the distilled text encoder and prepare steps. - Drop `output_type="latent"`; the latents are in the state as `latent_chunks`. - Preprocess the reference frame with the video processor. - Remove three tests the review asked to drop.
- encode_prompt_and_speech and shared_transformer_kwargs take their components and values explicitly and return their results. - The negative prompt gets its own prepare block in the base pipeline; the distilled pipeline uses the shared prepare step. - check_inputs of the prepare steps take explicit values.
main checks that the denoise group leaves latents in one canonical form (test_latents_output_in_canonical_form). Point it at latent_chunks: one chunk per entry, channels last.
…sters - Speech cross-attention keeps static shapes: the per-frame audio groups are computed from shapes as Python ints and gathered into a padded, masked batch, so the transformer compiles with fullgraph=True. One forward is bit-identical to the original model on all four checkpoints. - Drop FromOriginalModelMixin: single-file loading is not wired up. - Model tests compose the testers generate_model_tests.py emits (memory, compile incl. dynamic shapes, training, attention); skips name their reason. - Modular tests pin expected_workflow_defaults for base and distilled and add the guider tester for base. - Remove dead code: unread attributes, the batched visual-embedding branch, unused helper parameters.
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
yiyixuxu
reviewed
Nov 2, 2026
| def hook(_module, _inputs, output): | ||
| captured["features"] = output.last_hidden_state | ||
|
|
||
| handle = text_encoder.audio_tower.register_forward_hook(hook) |
Collaborator
There was a problem hiding this comment.
should we use the official api get_audio_features? it should not make that big noticeable ifference on performance, no?
Contributor
Author
There was a problem hiding this comment.
Thanks! Switched to get_audio_features in df2dca1, the features are bit-identical. As you
expected, the cost is negligible (+20 ms on a 12 s track).
The speech features now come from the Thinker's public get_audio_features rather than a forward hook on its audio tower. The tower runs twice per call, once inside the Thinker forward for the text hidden states and once for the features: +20 ms on a 12 s track (bf16, H100). The features and the text embeddings are bit-identical to before.
yiyixuxu
reviewed
Nov 3, 2026
yiyixuxu
reviewed
Nov 3, 2026
Co-authored-by: YiYi Xu <yixu310@gmail.com>
Contributor
Author
|
thanks a lot @yiyixuxu and @sayakpaul for all the reviews here and in the staging repo, and for |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Adds Kandinsky S2V 1.0, a speech-to-video model. Given a reference frame and a speech track, the
model animates the person in the frame to match the speech: the whole figure, not only the
head. Audio is a condition, not an output.
It was reviewed and approved in the staging repository:
https://github.com/huggingface/diffusers-new-model-addition-kandinskyX-avatar/pull/3
Since that approval, one commit addresses the self-review below (compile-friendly speech
attention, the full set of model testers, workflow and guider contracts, dead code):
9ba233b
Everything else is the approved code, rebased on current
main.It is versioned apart from the other Kandinsky models, so it gets files of its own, and it is
integrated as a modular pipeline only — no
DiffusionPipelineclass, nothing inpipelines/:KandinskyS2VTransformer3DModel—models/transformers/transformer_kandinsky_s2v.pyPiflowScheduler, fixed to match the referencesampler in kandinskylab/kandinsky-6 bit for bit (it differed on the final step and on the
raw-time walk)
KandinskyS2VAutoBlocks/KandinskyS2VModularPipelineandKandinskyS2VDistilledAutoBlocks/KandinskyS2VDistilledModularPipeline—modular_pipelines/kandinsky_s2v/The pipeline takes a PIL image and a mono 16 kHz waveform and reads no files. Its top-level
blocks —
text_encoder,vae_encoder,denoiseanddecode— each run on their own; the textencoder plans the chunks from the speech, and declares no guider.
A clip is generated chunk by chunk (5 s each) with FramePack memory carried between chunks.
The chunk loop is a
LoopSequentialPipelineBlocks; the timestep loop is a plain loop insideone block, as in Helios, since loop blocks cannot nest. Base and distilled are separate block
sets rather than a switch: the distilled scheduler and 16-step default need component specs of
their own, and the distilled blocks have no guider and no negative prompt, since guidance is
distilled into the student. Each checkpoint names its blocks in
modular_model_index.json, soModularPipeline.from_pretrainedpicks the variant from the checkpoint alone.Docs: a pipeline page and a transformer page; the
PiflowSchedulerpage mentions S2V.Usage
Pro does not fit an 80 GB card fully resident; the pipeline page shows offloading through a
ComponentsManagerwithmemory_reserve_margin="40GiB"(50.4 GiB peak on Pro distilled).Checkpoints
Four bundles, one per configuration, each with a
modular_model_index.json:kandinskylab/Kandinsky-S2V-1.0-{Lite,Pro,Lite-distill,Pro-distill}-Diffusers.They are private until release; the reviewers of the staging PR have access. To run the usage
example above, set
HF_TOKENto a token with access tokandinskylab. The tests do not needit: the two tiny repositories they load are public.
Tests
utils/generate_model_tests.pyemits: model, memory,compile (including dynamic shapes and repeated blocks), training and attention;
(
expected_workflow_defaultspins each one's components, inputs and defaults), plus theguider mixin for base; each loads a tiny public repository —
kandinskylab/tiny-kandinsky-s2v-modular-pipeandkandinskylab/tiny-kandinsky-s2v-distilled-modular-pipe(happy to move them underhf-internal-testing);that speech past the encoder limit is rejected;
_keep_in_fp32_modulesin fp32;.to(torch.bfloat16);PiflowScheduler's final step against a hand-rolled policy and rollout.Skipped, each for a property of the model:
num_images_per_prompt, for base and distilled: one prompt, one video per call;test_fuse_unfuse_qkv_projections: the projections are namedto_query/to_key/to_valueafter the training checkpoints, as in Kandinsky 6;
test_layerwise_casting_memory: the affine normalisations take fp32 input, as thecheckpoints keep them in fp32, and the test first casts the whole model with
.to();test_compile_works_with_aot: AOTInductor lowers the lookup into the RoPE tables, plain fp32attributes so that casting the model keeps them fp32, to a CPU kernel on a CUDA tensor.
On one H100, on this branch (rebased on current
main): 112 passed, 16 skipped (the above, onetest that needs several GPUs, and the workflow-map tests for a pipeline with a single
workflow). The Kandinsky 6 tests give the same result as on
main: 166 passed, and the same 3failures (
test_pipeline_kandinsky6_sr::test_inference_batch_single_identicalandtest_pipeline_kandinsky6_ti2va::…::test_group_offloading_inference_block_level, bothvariants).
make quality(with the pinned ruff 0.9.10),check_copies,check_dummies,check_doc_toc,check_support_list,check_forward_call_docstrings,check_return_annotationsandmodular_auto_docstringpass.Self-review notes
I ran the
self-reviewskill, with the rubric from currentmain, in two rounds and fixedwhat it reported as blocking:
FromOriginalModelMixinwithout single-file loading wired up: removed;generate_model_tests.pyemits;torch.compile(fullgraph=True)on data-dependent shapes: itnow computes the per-frame audio groups as Python ints and gathers them into a padded, masked
batch. One forward stays bit-identical to the original model on all four checkpoints;
expected_workflow_defaultsfor both variants, and the guider tester for base.Left as they are, for your call:
promptis optional and defaults to the prompt the checkpoints were trained with (an emptyprompt gives no text tokens). The default is long, so it shows in the auto docstrings.
latentsinput: noise is drawn per chunk fromgenerator, as inwan_animate_2.num_inference_steps=Ngives N−1 Euler updates: the raw grid is theone the original sampler uses, and the block description says so.
requires_unconditional_embedswithout declaring a guider, aswan_animate_2does, so that it runs on its own.PiflowSchedulerfix changes Kandinsky 6 distilled outputs slightly. No Kandinsky 6 testpins output slices, and they pass as on
main.(
flash,_flash_3,sage,aiter) fail in those blocks; native and the varlen backendswork.
test_layerwise_casting_memoryandtest_compile_works_with_aotare skipped with a reason;strict
xfailwould also fit._skip_layerwise_casting_patternsis not set.kandinskylab.Before submitting
self-reviewskill on the diff?documentation guidelines, and
here are tips on formatting docstrings.
Who can review?
@yiyixuxu @sayakpaul