ChristophSchuhmann's picture
Publish study artifacts 1-150 of 1487
a2313f5 verified
Raw History Blame Contribute Delete
10.5 kB
"""Causal reward-guided chunk search with persistent global/codec KV caches.
Winner-of-N: keep one prefix and fork it N ways at each chunk.
Top-2: retain up to two global winners, split N children (extra to the best).
Only common acoustic
token prefixes are irreversible streaming output; unrelated beams are not spliced.
"""
import math
import time
import numpy as np
import torch
from performance_core import combine_branches, StreamingCodec
def expansion_indices(winners, n):
if not winners or n < len(winners):
raise ValueError('Candidate budget must cover retained beam count')
quotient, remainder = divmod(n, len(winners))
return [index for position,index in enumerate(winners)
for _ in range(quotient + (position < remainder))]
def common_prefix(codes, rows):
if len(rows) == 1:
return int(codes[rows[0], :, 0].ne(-1).sum())
same = codes[rows].eq(codes[rows[0]].unsqueeze(0)).all(-1).all(0)
same &= codes[rows[0], :, 0].ne(-1)
differing = (~same).nonzero()
return int(differing[0]) if len(differing) else codes.shape[1]
def reorder_codec(codec, indexes, n):
"""Pinned codec state has per-row tensors, all with batch axis zero.
Copy via index_select, including duplicates; never share mutable child KV.
Fail closed if a new codec revision adds an unsupported state shape.
"""
for module in codec.modules():
state = getattr(module, '_streaming_state', None)
if state is None:
continue
for name, value in vars(state).items():
if torch.is_tensor(value):
if value.ndim == 0 or value.shape[0] != n:
raise ValueError(f'Unsupported codec state tensor {name}: {tuple(value.shape)}')
setattr(state, name, value.index_select(0, indexes))
class ChunkSearch:
def __init__(self, engine, scorer):
self.engine, self.scorer = engine, scorer
@torch.inference_mode()
def generate(self, spec, n=8, beam_width=1, chunk_frames=10, max_seconds=6.6,
guidance=1.5, temperature=1., seed=777, reference_path=None, on_commit=None):
if not 1 <= n <= 16 or beam_width not in [1,2]:
raise ValueError('Search supports batch sizes 1 through 16 and beam width 1/2')
requested_beam_width = beam_width
beam_width = min(n, beam_width)
if chunk_frames not in [10,20]:
raise ValueError('Search chunk must be 800 or 1600 ms')
engine, mdl = self.engine, self.engine.model
cfg = mdl.config
budget = min(750, math.ceil(max_seconds/.08))
p = {'max_new_tokens':budget, 'audio_temperature':temperature, 'audio_top_p':.95,
'audio_top_k':50, 'audio_repetition_penalty':1., 'text_temperature':0. if temperature==0 else 1.,
'text_top_p':1., 'text_top_k':50}
timeline, committed, first_commit = [], 0, None
started = time.perf_counter()
with engine.lock, torch.cuda.device(engine.device), torch.autocast('cuda',dtype=torch.bfloat16):
reference = engine.reference(reference_path) if reference_path else None
branches = [engine.pack(spec['prompt'],spec,reference,n)]
if guidance != 1 and spec['prompt'] != spec['unconditional']:
branches.append(engine.pack(spec['unconditional'],spec,reference,n))
ids, initial = combine_branches(branches,cfg.pad_token_id,cfg.audio_pad_token_id)
nb, length = len(branches), ids.shape[1]
masks = torch.zeros(n*nb,length+budget,dtype=torch.bool,device=engine.device)
masks[:,:length] = initial
past, model_input, steps = None, ids, 0
codes = torch.empty(n,0,12,dtype=torch.long,device=engine.device)
finished = torch.zeros(n,dtype=torch.bool,device=engine.device)
pcm = [np.empty(0,dtype=np.float32) for _ in range(n)]
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
capture_start = engine.fast.capture_seconds
decoder = engine.proc.audio_tokenizer
with StreamingCodec(decoder,n,cfg.audio_pad_token_id) as streaming:
while steps < budget:
phase_start = time.perf_counter()
event_start,event_end = [torch.cuda.Event(enable_timing=True) for _ in range(2)]
event_start.record()
new = []
for _ in range(min(chunk_frames,budget-steps)):
out = mdl.transformer(inputs_embeds=mdl._build_inputs_embeds(model_input),input_ids=None,
attention_mask=masks[:,:length+steps],past_key_values=past,use_cache=True,
position_ids=None,output_attentions=False,output_hidden_states=False,
return_dict=True,cu_seqlens=None,num_sequences=None)
past = out.past_key_values
local = mdl._global_hidden_to_local(out.last_hidden_state[:,-1]).to(mdl.local_transformer.ln_f.weight.dtype)
stop,sampled = engine.fast.frame(local,n,nb,guidance,p,graph=True)
continues = stop.eq(cfg.audio_assistant_slot_token_id) & ~finished
finished |= stop.eq(cfg.audio_end_token_id)
if not bool(continues.any()):
break
frame = sampled.masked_fill(~continues.unsqueeze(-1),cfg.audio_pad_token_id)
new.append(frame)
row = mdl._build_generation_row(n,ids.device,frame)
row[:,0,0] = torch.where(continues,row[:,0,0],cfg.pad_token_id)
model_input = row.repeat(nb,1,1)
masks[:,length+steps] = continues.repeat(nb)
steps += 1
if new:
chunk = torch.stack(new,dim=1)
codes = torch.cat((codes,chunk),dim=1)
wave,lengths = streaming.decode(chunk)
for i in range(n):
pcm[i] = np.concatenate((pcm[i],wave[i,:int(lengths[i])].float().cpu().numpy()))
event_end.record()
event_end.synchronize()
generation_seconds = time.perf_counter()-phase_start
endpoint = steps>=budget or bool(finished.all()) or not new
# Scoring exits cuda:0 autocast so cuda:1's FP32 quality
# tower/head precision remains unchanged.
with torch.autocast('cuda', enabled=False):
scores = self.scorer.score(pcm,spec['text'],[endpoint or bool(x) for x in finished.cpu()])
order = sorted(range(n),key=lambda i:(-scores[i]['reward'],scores[i]['prefix_wer'],i))
winners = order[:beam_width] if not endpoint else order[:1]
safe = common_prefix(codes,winners)
if safe < committed:
raise RuntimeError('Search attempted to revise committed acoustic prefix')
committed_seconds = max(0,safe-committed)*.08
entry = {'step':len(timeline)+1,'frame':steps,'retained_rows':winners,
'committed_audio_seconds':committed_seconds,'committed_until_seconds':safe*.08,
'generation_wall_seconds':generation_seconds,'tts_codec_device_seconds':event_start.elapsed_time(event_end)/1000,
'scores':scores, **self.scorer.last_timing}
if safe > committed:
if first_commit is None:
first_commit = time.perf_counter()-started
segment = pcm[winners[0]][committed*3840:safe*3840].copy()
if on_commit:
on_commit(segment,entry)
committed = safe
timeline.append(entry)
if endpoint:
winner = winners[0]
break
indexes = torch.tensor(expansion_indices(winners,n),device=engine.device)
branch_indexes = torch.cat([indexes+branch*n for branch in range(nb)])
past = tuple(tuple(value.index_select(0,branch_indexes) for value in layer) for layer in past)
masks = masks.index_select(0,branch_indexes)
model_input = model_input.index_select(0,branch_indexes)
codes = codes.index_select(0,indexes)
finished = finished.index_select(0,indexes)
pcm = [pcm[i].copy() for i in indexes.cpu().tolist()]
reorder_codec(decoder,indexes,n)
else:
winner = 0
output = pcm[winner]
engine.last_codes = [codes[winner][codes[winner,:,0].ne(cfg.audio_pad_token_id)].cpu()]
capture_seconds = engine.fast.capture_seconds-capture_start
total = time.perf_counter()-started
seconds = len(output)/48000
if not seconds:
raise RuntimeError('Search generated no audio; try another seed/reference')
performance = {'n':n,'beam_width':beam_width,'requested_beam_width':requested_beam_width,
'chunk_ms':chunk_frames*80,'max_seconds':max_seconds,
'actual_seconds':seconds,'search_wall_seconds':total,'search_rtf':total/seconds,
'first_commit_seconds':first_commit,'tts_codec_seconds':sum(x['tts_codec_device_seconds'] for x in timeline),
'scoring_seconds':sum(x['total_seconds'] for x in timeline),'graph_capture_seconds':capture_seconds,
'streaming_rule':'Winner commits immediately; top-2 emits only common acoustic-token prefixes',
'includes':'reference encoding, packing, global/local TTS, codec, GPU transfers, batched ASR/timestamps, shared VoiceCLAP, heads, CPU selection and KV cloning',
'excludes':'model loading, one-time style text-query embedding, Luna, MP3 encoding, final optional alignment/evaluation, network/playback',
'device':str(engine.device),'scorer_device':str(self.scorer.device),
'timeline':timeline,'final_search_score':scores[winner],
'hit_frame_limit':steps>=budget and not bool(finished[winner])}
return output,performance