### Support loss weighting for packing ###
loss = None
if labels is not None:
lm_logits = lm_logits.to(torch.float32)
# Shift so that tokens < n predict n
shift_logits = lm_logits[..., :-1, :].contiguous()
if isinstance(labels, tuple) or isinstance(labels, list):
labels, weights = labels
shift_labels = labels[..., 1:].contiguous()
if self.pack_loss:
shift_weights = weights[..., 1:].contiguous()
loss_fct = CrossEntropyLoss(ignore_index=-100, reduction='none')
loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
loss = (loss * shift_weights).sum()
else:
loss_fct = CrossEntropyLoss(ignore_index=-100)
loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
lm_logits = lm_logits.to(hidden_states.dtype)
loss = loss.to(hidden_states.dtype)
### -------------------------------------- ###
Voxtral Mini 在线 DPO 变体能够提供更清晰的接地气、更少的幻觉,并且通常能提供更有帮助的响应。 对于 Voxtral Small,我们发现其语音理解基准测试的响应质量得分显著提升,但在英语短句基准测试中却略有下降。
Results
语音识别:各任务的平均 WER 结果。Voxtral Small 在英语短格式和 MCV 上的表现优于所有开源和闭源模型。Voxtral Mini Transcribe 在每项任务中均胜过 GPT-4o mini Transcribe 和 Gemini 2.5 Flash。
Tables 4, 5 and 6 show the per-language breakdown of WER scores for the FLEURS, Mozilla Common Voice and Multilingual LibriSpeech benchmarks, respectively.
# Create torch group with deepspeed rank 0 and all vllm ranks# to update vllm engine's weights after each training stage.## Say we have 3 vllm engines and eache of them has 4 GPUs,# then the torch group is:# [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]# |ds rank 0 | engine-0 | engine-1 | engine-2 |## For ZeRO-1/2:# 1. Broadcast parameters from rank 0 to all vllm engines# For ZeRO-3:# 1. AllGather paramters to rank 0# 2. Broadcast parameters from rank 0 to all vllm engines
if self.vllm_engines is not None and torch.distributed.get_rank() == 0:
...
# world_size = num_of_all_vllm_ranks + 1 ds_rank0
world_size = vllm_num_engines * vllm_tensor_parallel_size + 1
...
# =====================================================================# 遍历每个vllm_engines,将其下的每个vllm_rank添加进通讯组中,这里又分成两步:# 1. engine.init_process_group.remote(...):# 首先,触发远程vllm_engine的init_process_group方法# 2. 远程vllm_engine是一个包装过的vllm实例,它的init_process_group# 方法将进一步触发这个vllm实例下的各个worker进程(见4.4图例),# 最终是在这些worker进程上执行“将每个vllm_rank"添加进ds_rank0通讯组的工作# =====================================================================
refs = [
engine.init_process_group.remote(
# ds_rank0所在node addr
master_address,
# ds_rank0所在node port
master_port,
# 该vllm_engine的第一个rank在"ds_rank0 + all_vllm_ranks“中的global_rank,# 该值将作为一个offset,以该值为起点,可以推算出该vllm_engine中其余vllm_rank的global_rank
i * vllm_tensor_parallel_size + 1,
world_size,
"openrlhf",
backend=backend,
)
for i, engine in enumerate(self.vllm_engines)
]
# =====================================================================# 将ds_rank0添加进通讯组中# =====================================================================
self._model_update_group = init_process_group(
backend=backend,
init_method=f"tcp://{master_address}:{master_port}",
world_size=world_size,
rank=0,
group_name="openrlhf",
)
# =====================================================================# 确保all_vllm_ranks都已添加进通讯组中# =====================================================================
ray.get(refs)
def _broadcast_to_vllm(self):
# avoid OOM
torch.cuda.empty_cache()
model = self.actor.model.module
count, num_params = 0, len(list(model.named_parameters()))
for name, param in model.named_parameters():
count += 1 # empty_cache at last param# Fire all vllm engines for broadcast
if torch.distributed.get_rank() == 0:
shape = param.shape if self.strategy.args.zero_stage != 3 else param.ds_shape
refs = [
# 远端vllm_engine的每个rank上,初始化一个尺寸为shape的empty weight张量,# 用于接收广播而来的权重
engine.update_weight.remote(name, dtype=param.dtype, shape=shape, empty_cache=count == num_params)
for engine in self.vllm_engines
]
# For ZeRO-3, allgather sharded parameter and broadcast to all vllm engines by rank 0# ds_rank0发出权重(视是否使用zero3决定在发出前是否要做all-gather)
with deepspeed.zero.GatheredParameters([param], enabled=self.strategy.args.zero_stage == 3):
if torch.distributed.get_rank() == 0:
torch.distributed.broadcast(param.data, 0, group=self._model_update_group)
ray.get(refs) # 确保所有vllm_ranks接收权重完毕
SoulX-DuoVoice 包含一个负责对话理解与生成的 Dialogue Model 和一个负责语音生成的 Speech Model。
新模型摒弃了传统语音交互中依赖的VAD(话音激活检测)机制与延迟控制逻辑,打破行业中普遍存在的“轮次对话”模式,赋予 AI 自主决策对话节奏的能力。AI可实现主动打破沉默、适时打断用户、边听边说、时间语义感知、并行发言讨论等。同时,模型具备多维度感知(包括时间感知、环境感知、事件感知等),口语化表达(如语气词、结巴、明显情绪起伏),音色复刻等能力,让AI更具“真人感”,支持打造更沉浸、类现实交互的语音互动新体验。
我们考虑一个源语言的语音话语,将其表示为单声道波形X∈Rfs⋅d,采样率为 fs=24kHz,时长为 d。类似地,其目标语言的翻译表示为 Y∈Rfs⋅d。我们假设对 X 进行了填充,以确保 X 和 Y 拥有相同的时长。我们的目标是建模条件概率 P[Y∣X]。此外,我们增加了一个约束:在已知 X 的情况下对 Y 的建模应具有因果性,并且相对于源语音具有最小延迟,例如与人工同声传译员在实时翻译场景中所面临的约束相同。
为了通过监督学习学习这一约束,目标语音 Y 本身必须构建为满足因果性约束。我们首先假设 Y 满足这一约束,并介绍如何对其分布进行建模。随后,我们引入一个信息论准则,用以验证 Y 相对于 X 是否具有因果性,并进一步将一个非因果的翻译转换为一个因果的翻译。
模型
以 Moshi框架为基础,对从神经音频编解码器中获得的多个离散标记序列进行联合建模。
Neural audio codec
我们使用预先训练的因果和流式 Mimi 编解码器将 X 和 Y 编码为低帧率的离散标记序列。
编码器将持续时间为 d 的输入波形转换为一个潜在向量 U∈RC×fr⋅d,其中 C是潜在空间的维度,帧率 fr=12.5 Hz。随后,U被投影到其在一个包含NA 个条目的码本中的最近邻。该投影的残差接着被投影到另一个具有相同大小的码本中,如此重复,直到完成 Q 次投影。最后一次的残差被舍弃,解码器则被训练为从这些投影张量的总和中重构原始输入波形。
Kyutai STT 是一个流式模型,这意味着它在接收音频的同时实时转录,而不是假设音频在开始处理前已全部可用。这使其非常适合诸如 Unmute [Make any LLM listen and speak using Kyutai’s speech-to-text and text-to-speech.] 之类的实时应用。
它输出格式良好的转录文本,包含标点符号,同时还提供词级时间戳。
在准确性方面,它的表现仍可与那些能够一次性访问完整音频的最先进非流式模型相媲美。
语义语音活动检测器
对于像 Unmute [Make any LLM listen and speak using Kyutai’s speech-to-text and text-to-speech.] 这样基于串联式语音对话的应用程序,需要一种方式来判断用户是否已经说完,以便生成回应。最常见的方法是使用一个独立的语音活动检测(VAD)模型,判断用户是否在说话,并在用户说完之后等待一个固定的时间。但在实际应用中,不可能找到一个适用于所有情况的等待时间。人们在说话时经常会出现长时间停顿,这会导致这种朴素方法出现误判。
在 Unmute [Make any LLM listen and speak using Kyutai’s speech-to-text and text-to-speech.]中,我们使用一种称为 “flush trick”(刷新技巧)的方法来进一步降低响应延迟。当语音活动检测器预测用户已经说完时,我们还需再等 500 毫秒(即 STT 模型的延迟),以确保不会截断转录的结尾部分。