Skip to content

Commit 7b7287d

Browse files
author
Michal Warda
committed
Add prepared conditioning benchmark path
1 parent 330290e commit 7b7287d

2 files changed

Lines changed: 256 additions & 2 deletions

File tree

sam_audio/model/model.py

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import Any, Dict, Optional
77

88
import torch
9+
import torch.nn.functional as F
910
from core.audio_visual_encoder import PEAudioFrame, PEAudioFrameTransform
1011
from torchdiffeq import odeint
1112

@@ -99,6 +100,16 @@ class SeparationResult:
99100
noise: torch.Tensor
100101

101102

103+
@dataclass
104+
class PreparedSeparation:
105+
batch: Batch
106+
candidates: int
107+
forward_args: Dict[str, torch.Tensor]
108+
projected_static: torch.Tensor
109+
memory_base: Optional[torch.Tensor]
110+
predict_spans: bool = False
111+
112+
102113
class SAMAudio(BaseModel):
103114
config_cls = SAMAudioConfig
104115
revision = None
@@ -155,6 +166,44 @@ def align_inputs(
155166
aligned = self.embed_anchors(aligned, anchor_ids, anchor_alignment)
156167
return aligned
157168

169+
def _get_projected_static_inputs(
170+
self,
171+
audio_features: torch.Tensor,
172+
masked_video_features: Optional[torch.Tensor] = None,
173+
anchor_ids: Optional[torch.Tensor] = None,
174+
anchor_alignment: Optional[torch.Tensor] = None,
175+
):
176+
feature_channels = audio_features.size(-1)
177+
expected_channels = feature_channels * 3
178+
if self.proj.in_features == expected_channels:
179+
audio_weight = self.proj.weight[
180+
:, 2 * feature_channels : 3 * feature_channels
181+
]
182+
projected = F.linear(audio_features, audio_weight, self.proj.bias)
183+
else:
184+
static_input = torch.cat(
185+
[
186+
torch.zeros_like(audio_features),
187+
torch.zeros_like(audio_features),
188+
audio_features,
189+
],
190+
dim=2,
191+
)
192+
projected = self.proj(static_input)
193+
aligned = self.align_masked_video(projected, masked_video_features)
194+
return self.embed_anchors(aligned, anchor_ids, anchor_alignment)
195+
196+
def _align_prepared_inputs(
197+
self,
198+
noisy_audio: torch.Tensor,
199+
projected_static: torch.Tensor,
200+
):
201+
feature_channels = noisy_audio.size(-1)
202+
if self.proj.in_features >= feature_channels:
203+
noise_weight = self.proj.weight[:, :feature_channels]
204+
return projected_static + F.linear(noisy_audio, noise_weight, bias=None)
205+
return self.align_inputs(noisy_audio, noisy_audio.new_zeros(noisy_audio.shape))
206+
158207
def forward(
159208
self,
160209
noisy_audio: torch.Tensor,
@@ -207,10 +256,40 @@ def forward(
207256
memory_padding_mask=text_mask,
208257
)
209258

259+
def forward_prepared(
260+
self,
261+
noisy_audio: torch.Tensor,
262+
prepared: PreparedSeparation,
263+
time: torch.Tensor,
264+
):
265+
aligned_inputs = self._align_prepared_inputs(
266+
noisy_audio, prepared.projected_static
267+
)
268+
timestep_emb = self.timestep_emb(time, pos=time).unsqueeze(1)
269+
memory = timestep_emb
270+
if prepared.memory_base is not None:
271+
memory = prepared.memory_base + timestep_emb
272+
return self.transformer(
273+
aligned_inputs,
274+
time,
275+
padding_mask=prepared.forward_args["audio_pad_mask"],
276+
memory=memory,
277+
memory_padding_mask=prepared.forward_args["text_mask"],
278+
)
279+
210280
def _get_audio_features(self, audios: torch.Tensor):
211281
audio_features = self.audio_codec(audios).transpose(1, 2)
212282
return torch.cat([audio_features, audio_features], dim=2)
213283

284+
def _get_audio_features_dedup(self, audios: torch.Tensor):
285+
if audios.size(0) <= 1:
286+
return self._get_audio_features(audios)
287+
first = audios[:1]
288+
if torch.equal(audios, first.expand_as(audios)):
289+
features = self._get_audio_features(first)
290+
return features.expand(audios.size(0), *features.shape[1:])
291+
return self._get_audio_features(audios)
292+
214293
def _get_video_features(self, video, audio_features):
215294
B, T, _ = audio_features.shape
216295
if video is None:
@@ -256,6 +335,65 @@ def _get_forward_args(self, batch: Batch, candidates: int = 1):
256335
),
257336
}
258337

338+
def prepare_audio(
339+
self,
340+
batch: Batch,
341+
candidates: int = 1,
342+
predict_spans: bool = False,
343+
) -> PreparedSeparation:
344+
audio_features = self._get_audio_features_dedup(batch.audios)
345+
text_features, text_mask = self.text_encoder(batch.descriptions)
346+
masked_video_features = self._get_video_features(
347+
batch.masked_video, audio_features
348+
)
349+
350+
forward_args = {
351+
"audio_features": self._repeat_for_reranking(audio_features, candidates),
352+
"text_features": self._repeat_for_reranking(text_features, candidates),
353+
"text_mask": self._repeat_for_reranking(text_mask, candidates),
354+
"masked_video_features": self._repeat_for_reranking(
355+
masked_video_features, candidates
356+
),
357+
"anchor_ids": self._repeat_for_reranking(batch.anchor_ids, candidates),
358+
"anchor_alignment": self._repeat_for_reranking(
359+
batch.anchor_alignment, candidates
360+
),
361+
"audio_pad_mask": self._repeat_for_reranking(
362+
batch.audio_pad_mask, candidates
363+
),
364+
}
365+
366+
if predict_spans and hasattr(self, "span_predictor") and batch.anchors is None:
367+
batch = self.predict_spans(
368+
batch=batch,
369+
audio_features=audio_features,
370+
audio_pad_mask=batch.audio_pad_mask,
371+
)
372+
forward_args["anchor_ids"] = self._repeat_for_reranking(
373+
batch.anchor_ids, candidates
374+
)
375+
forward_args["anchor_alignment"] = self._repeat_for_reranking(
376+
batch.anchor_alignment, candidates
377+
)
378+
379+
memory_base = None
380+
if forward_args["text_features"] is not None:
381+
memory_base = self.memory_proj(forward_args["text_features"])
382+
projected_static = self._get_projected_static_inputs(
383+
forward_args["audio_features"],
384+
masked_video_features=forward_args["masked_video_features"],
385+
anchor_ids=forward_args["anchor_ids"],
386+
anchor_alignment=forward_args["anchor_alignment"],
387+
)
388+
return PreparedSeparation(
389+
batch=batch,
390+
candidates=candidates,
391+
forward_args=forward_args,
392+
projected_static=projected_static,
393+
memory_base=memory_base,
394+
predict_spans=predict_spans,
395+
)
396+
259397
def predict_spans(
260398
self, batch: Batch, audio_features: torch.Tensor, audio_pad_mask: torch.Tensor
261399
) -> Batch:
@@ -383,6 +521,105 @@ def vector_field(t, noisy_audio):
383521
noise=noise,
384522
)
385523

524+
@torch.inference_mode()
525+
def separate_prepared(
526+
self,
527+
prepared: PreparedSeparation,
528+
prompts: Optional[list[str]] = None,
529+
noise: Optional[torch.Tensor] = None,
530+
ode_opt: Dict[str, Any] = DFLT_ODE_OPT,
531+
reranking_candidates: Optional[int] = None,
532+
predict_spans: bool = False,
533+
) -> SeparationResult:
534+
del prompts
535+
if reranking_candidates is None:
536+
reranking_candidates = prepared.candidates
537+
if reranking_candidates != prepared.candidates:
538+
raise ValueError(
539+
"`reranking_candidates` must match the candidate count used by `prepare_audio`"
540+
)
541+
if predict_spans and not prepared.predict_spans and prepared.batch.anchors is None:
542+
prepared = self.prepare_audio(
543+
prepared.batch,
544+
candidates=prepared.candidates,
545+
predict_spans=True,
546+
)
547+
548+
audio_features = prepared.forward_args["audio_features"]
549+
B, T, C = audio_features.shape
550+
C = C // 2
551+
552+
if noise is None:
553+
noise = torch.randn_like(audio_features)
554+
555+
def vector_field(t, noisy_audio):
556+
return self.forward_prepared(
557+
noisy_audio=noisy_audio,
558+
prepared=prepared,
559+
time=t.expand(noisy_audio.size(0)),
560+
)
561+
562+
if ode_opt.get("method") == "fixed_midpoint":
563+
generated = _fixed_midpoint_integrate(
564+
vector_field, noise, ode_opt.get("options", {})
565+
)
566+
else:
567+
states = odeint(
568+
vector_field,
569+
noise,
570+
torch.tensor([0.0, 1.0], device=noise.device),
571+
**ode_opt,
572+
)
573+
generated = states[-1]
574+
generated_features = generated.transpose(1, 2)
575+
wavs = self.audio_codec.decode(generated_features.reshape(2 * B, C, T)).view(
576+
B, 2, -1
577+
)
578+
579+
batch = prepared.batch
580+
bsz = wavs.size(0) // reranking_candidates
581+
sizes = self.audio_codec.feature_idx_to_wav_idx(batch.sizes)
582+
target_wavs = self.unbatch(
583+
wavs[:, 0].view(bsz, reranking_candidates, -1), sizes
584+
)
585+
residual_wavs = self.unbatch(
586+
wavs[:, 1].view(bsz, reranking_candidates, -1), sizes
587+
)
588+
589+
if (
590+
reranking_candidates > 1
591+
and batch.masked_video is not None
592+
and self.visual_ranker is not None
593+
):
594+
scores = self.visual_ranker(
595+
extracted_audio=target_wavs,
596+
videos=batch.masked_video,
597+
sample_rate=self.audio_codec.sample_rate,
598+
)
599+
idxs = scores.argmax(dim=1)
600+
elif reranking_candidates > 1 and self.text_ranker is not None:
601+
input_audio = [
602+
audio[:, :size].expand(reranking_candidates, -1)
603+
for audio, size in zip(batch.audios, sizes, strict=False)
604+
]
605+
scores = self.text_ranker(
606+
extracted_audio=target_wavs,
607+
input_audio=input_audio,
608+
descriptions=batch.descriptions,
609+
sample_rate=self.audio_codec.sample_rate,
610+
)
611+
idxs = scores.argmax(dim=1)
612+
else:
613+
idxs = torch.zeros(bsz, dtype=torch.long, device=noise.device)
614+
615+
return SeparationResult(
616+
target=[wav[idx] for wav, idx in zip(target_wavs, idxs, strict=False)],
617+
residual=[
618+
wavs[idx] for wavs, idx in zip(residual_wavs, idxs, strict=False)
619+
],
620+
noise=noise,
621+
)
622+
386623
def unbatch(self, wavs: torch.Tensor, sizes: torch.Tensor, time_dim: int = -1):
387624
result = []
388625
for row, size in zip(wavs, sizes, strict=False):

scripts/h100_benchmark.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,12 @@ def make_separate_kwargs(args: argparse.Namespace) -> dict[str, Any]:
270270
return kwargs
271271

272272

273+
def candidate_count(args: argparse.Namespace) -> int:
274+
if args.reranking == "adaptive":
275+
return 1
276+
return int(args.reranking)
277+
278+
273279
def run_one(
274280
*,
275281
model: Any,
@@ -310,7 +316,11 @@ def run_one(
310316
inference_start = now_ms()
311317
with sdpa_context(args.sdpa_backend):
312318
if args.cache_conditioning:
313-
handle = model.prepare_audio(batch)
319+
handle = model.prepare_audio(
320+
batch,
321+
candidates=candidate_count(args),
322+
predict_spans=args.predict_spans,
323+
)
314324
result = model.separate_prepared(handle, prompts=prompts, **make_separate_kwargs(args))
315325
elif args.reranking == "adaptive":
316326
result = model.separate_adaptive_rerank(batch, predict_spans=args.predict_spans)
@@ -429,7 +439,14 @@ def main() -> int:
429439
if dtype != torch.float32:
430440
batch.audios = batch.audios.to(dtype)
431441
with sdpa_context(args.sdpa_backend):
432-
if args.reranking == "adaptive":
442+
if args.cache_conditioning:
443+
handle = model.prepare_audio(
444+
batch,
445+
candidates=candidate_count(args),
446+
predict_spans=args.predict_spans,
447+
)
448+
model.separate_prepared(handle, **make_separate_kwargs(args))
449+
elif args.reranking == "adaptive":
433450
model.separate_adaptive_rerank(batch, predict_spans=args.predict_spans)
434451
else:
435452
model.separate(batch, **make_separate_kwargs(args))

0 commit comments

Comments
 (0)