Traduzione automatica · Originale inglese
API e protocolli
Tempo di esecuzione nativo di Apple · PocketTTS
PocketTTS aggiunge la sintesi vocale nativo CoreML a speech-swift. Tutte le fasi di default all'esecuzione della CPU. I flussi preimpostati di Alba inglese 1,920-sample frame a 24 kHz (80 ms per frame).
import PocketTTS
let model = try PocketTTSModel.fromBundle(URL(fileURLWithPath: "/path/to/bundle"))
try await model.generateStreaming(text: "Hello world.") { chunk in
await consume(chunk.samples)
}Il modulo AudioCommon definisce i protocolli diagnostici del modello e i tipi condivisi. Qualsiasi modello di conformazione può essere utilizzato in modo intercambiabile attraverso queste interfacce.
Panoramica del protocollo
┌─────────────────────────────────────────────────────────┐
│ AudioCommon │
│ │
│ AudioChunk SpeechGenerationModel (TTS) │
│ AlignedWord SpeechRecognitionModel (STT) │
│ SpeechSegment ForcedAlignmentModel │
│ SpeechToSpeechModel │
│ VoiceActivityDetectionModel (VAD) │
│ TurnCompletionProvider (EOT) │
│ SpeakerEmbeddingModel │
│ SpeakerDiarizationModel │
│ SpeakerExtractionCapable │
└─────────────────────────────────────────────────────────┘SpeechRecognitionModel
Protocollo per i modelli vocale-text.
public protocol SpeechRecognitionModel: AnyObject {
var inputSampleRate: Int { get }
func transcribe(audio: [Float], sampleRate: Int, language: String?) -> String
func transcribeWithLanguage(audio: [Float], sampleRate: Int, language: String?) -> TranscriptionResult
}
Tipi di conformità: Qwen3ASRModel, WhisperASRModel, ParakeetASRModel, ParakeetStreamingASRModel, OmnilingualASRModel (CoreML), OmnilingualASRMLXModel (MLX)
SpeechGenerationModel
Protocollo per i modelli di testo-speech.
public protocol SpeechGenerationModel: AnyObject {
var sampleRate: Int { get }
func generate(text: String, language: String?) async throws -> [Float]
func generateStream(text: String, language: String?) -> AsyncThrowingStream<AudioChunk, Error> // has default impl
}
generateStream() ha un'implementazione predefinita che avvolge generate() come singolo pezzo. Modelli con lo streaming vero (ad esempio Qwen3-TTS) sovrascriverlo.
Tipi di conformità: Qwen3TTSModel, CosyVoiceTTSModel, VoxCPM2TTSModel, KokoroTTSModel, IndexTTS2TTSModel
IndexTTS2TTSModel aggiunge un sovraccarico di riferimento-audio generate per la clonazione vocale a zero-shot e utilizza IndexTTS2SynthesisOptions per i controlli di conversazione e di pausa.
Qwen3-TTS carico locale
fromLocal convalida i file richiesti e la completezza del punto di controllo, quindi deriva la dimensione del modello, la quantizzazione e l'architettura da config.json. L'opzionale configuration il valore è solo un fallback per i metadati omessi; i metadai malformati o contraddittori gettano Qwen3TTSLoadingError.invalidConfiguration prima dell'assegnazione del modello.
public static func fromLocal(
modelDirectory: URL,
tokenizerDirectory: URL,
configuration: Qwen3TTSConfig? = nil,
wiredMemoryPolicy: Qwen3TTSWiredMemoryPolicy = .none,
progressHandler: ((Double, String) -> Void)? = nil
) throws -> Qwen3TTSModel
public static func fromPretrained(
modelId: String = Qwen3TTSModel.defaultModelId,
tokenizerModelId: String = "Qwen/Qwen3-TTS-Tokenizer-12Hz",
cacheDir: URL? = nil,
tokenizerCacheDir: URL? = nil,
offlineMode: Bool = false,
wiredMemoryPolicy: Qwen3TTSWiredMemoryPolicy = .pin(fraction: 0.9),
progressHandler: ((Double, String) -> Void)? = nil
) async throws -> Qwen3TTSModel
fromLocal di default a .none, quindi non cambia il limite di memoria cablato Metal a livello di processo. fromPretrained mantiene la sua .pin(fraction: 0.9) default. Entrambi i caricatori pre-trained accettano un separato tokenizerCacheDir.
Model di allineamento forzato
Protocollo per allineamento timestamp a livello di parola.
public protocol ForcedAlignmentModel: AnyObject {
func align(audio: [Float], text: String, sampleRate: Int, language: String?) -> [AlignedWord]
}
SpeechToSpeechModel
Protocollo per i modelli di dialogo vocale-speech.
public protocol SpeechToSpeechModel: AnyObject {
var sampleRate: Int { get }
func respond(userAudio: [Float]) -> [Float]
func respondStream(userAudio: [Float]) -> AsyncThrowingStream<AudioChunk, Error>
}
Tipo di contatto: PersonaPlexModel
HibikiTranslateModel
Guidatore di traduzione vocale-speech. Non conforme a SpeechToSpeechModel perché il contratto è diverso: l'ingresso e l'uscita sono lingue diverse, la lunghezza di uscita è variabile (EOS-driven, tappato a max(tSrc * 5/2, tSrc + 20) passi — approssimativamente 1.0–1.6× durata della sorgente su clip in stile FLEURS), e c'è un flusso di testo parallelo accanto all'audio. Guida completa →
public final class HibikiTranslateModel {
public let cfg: HibikiConfig
public static var defaultModelId: String { "aufklarer/Hibiki-Zero-3B-MLX-4bit" }
public static var modelId8bit: String { "aufklarer/Hibiki-Zero-3B-MLX-8bit" }
public static func fromPretrained(
modelId: String = defaultModelId,
cacheDir: URL? = nil,
offlineMode: Bool = false,
progressHandler: ((Double, String) -> Void)? = nil
) async throws -> HibikiTranslateModel
public func warmUp()
public func translate(
sourceAudio: [Float],
sourceLanguage: HibikiSourceLanguage = .fr,
verbose: Bool = false
) -> (audio: [Float], textTokens: [Int32])
public func translateStream(
sourceAudio: [Float],
sourceLanguage: HibikiSourceLanguage = .fr,
streaming: HibikiStreamingConfig = .default,
verbose: Bool = false
) -> AsyncThrowingStream<AudioChunk, Error>
}
public enum HibikiSourceLanguage: String, CaseIterable, Sendable, Codable {
case fr, es, pt, de
}
Tasso di campione: 24 kHz mono, sia input che output. Tasso di telaio: 12.5 Hz (Mimi codec). Lingue di origine: FR / ES / PT / DE. Lingua di destinazione: EN. Set HIBIKI_GREEDY=1 al lancio di processo per decodifica argmax deterministica (tappezza i canari CI).
Detezione dell'attivitá della voceModel
Protocollo per il rilevamento delle attività vocali.
public protocol VoiceActivityDetectionModel: AnyObject {
var inputSampleRate: Int { get }
func detectSpeech(audio: [Float], sampleRate: Int) -> [SpeechSegment]
}
TurnCompletionProvider
Protocollo per i classificatori di fine turno consultati da StreamingVADProcessor su ogni conferma VAD Una pausa. VAD solo sente il silenzio; un modello di turn-completion ascolta la prosodia di tutta l'enunciazione, quindi una pausa di mezza-sentenza mantiene l'agente in attesa mentre una frase finita riceve una risposta immediata. speech-coreE'... sc_turn_completion_vtable_t. Vedi il Guida Smart Turn.
public protocol TurnCompletionProvider: AnyObject {
/// Probability in [0, 1] that the turn is complete, given the audio of the
/// turn so far (Smart Turn looks at the last 8 s).
func turnCompleteProbability(audio: [Float], sampleRate: Int) throws -> Float
}
Tipi di conformità: SmartTurnModel (Smart Turn v3.2)
AltoparlanteModel incorporato
Protocollo per l'estrazione dell'incorporazione dei diffusori.
public protocol SpeakerEmbeddingModel: AnyObject {
var inputSampleRate: Int { get }
var embeddingDimension: Int { get }
func embed(audio: [Float], sampleRate: Int) -> [Float]
}
Tipo di contatto: WeSpeakerModel
SpeakerDiarizationModel
Protocollo per i modelli di diarizzazione dei diffusori che assegnano le etichette dei diffusori ai segmenti audio.
public protocol SpeakerDiarizationModel: AnyObject {
var inputSampleRate: Int { get }
func diarize(audio: [Float], sampleRate: Int) -> [DiarizedSegment]
}
Tipo di contatto: DiarizationPipeline ( Pyannote), SortformerDiarizer
AltoparlanteExtractionCapable
Protocollo di diarizzazione esteso per i motori che supportano l'estrazione di segmenti di un altoparlante di destinazione utilizzando un'integrazione di riferimento. Non tutti i motori supportano questo (Sortformer è end-to-end e non produce embedding di diffusori).
public protocol SpeakerExtractionCapable: SpeakerDiarizationModel {
func extractSpeaker(audio: [Float], sampleRate: Int, targetEmbedding: [Float]) -> [SpeechSegment]
}
Tipo di contatto: DiarizationPipeline ( Pyannote only)
Tipi condivisi
AudioChunk
public struct AudioChunk {
public let samples: [Float] // PCM samples
public let sampleRate: Int // Sample rate (e.g. 24000)
}
Segmento di discorso
public struct SpeechSegment {
public let startTime: Float // Start time in seconds
public let endTime: Float // End time in seconds
}
Ordigno allineato
public struct AlignedWord {
public let text: String // The word
public let startTime: Float // Start time in seconds
public let endTime: Float // End time in seconds
}
Segmento diarizzato
public struct DiarizedSegment {
public let startTime: Float // Start time in seconds
public let endTime: Float // End time in seconds
public let speakerId: Int // Speaker identifier (0-based)
}
DialogoSegment
Un segmento di dialogo multi-altoparlante con etichette opzionali di altoparlante e di emozione. Utilizzato con DialogueParser e DialogueSynthesizer per la sintesi di dialogo CosyVoice3.
public struct DialogueSegment: Sendable, Equatable {
public let speaker: String? // Speaker identifier ("S1", "S2"), nil for untagged
public let emotion: String? // Emotion tag ("happy", "whispers"), nil if none
public let text: String // Cleaned text to synthesize
}
DialogoParser
Parses multi-speaker dialogo testo con i tag di altoparlante in linea ([S1]) e tag di emozione ((happy)).
public enum DialogueParser {
static func parse(_ text: String) -> [DialogueSegment]
static func emotionToInstruction(_ emotion: String) -> String
}
Emozioni integrate: happy/excited, sad, angry, whispers/whispering, laughs/laughing, calm, surprised, serious. I tag sconosciuti passano attraverso come istruzioni di forma libera.
DialogoSynthesizer
Orchestra la sintesi di dialogo multi-segment con clonazione vocale per-speaker, lacune di silenzio e crossfade.
public enum DialogueSynthesizer {
static func synthesize(
segments: [DialogueSegment],
speakerEmbeddings: [String: [Float]],
model: CosyVoiceTTSModel,
language: String,
config: DialogueSynthesisConfig,
verbose: Bool
) -> [Float]
}
DialogoSinteticoConfig.
public struct DialogueSynthesisConfig: Sendable {
public var turnGapSeconds: Float // Default: 0.2
public var crossfadeSeconds: Float // Default: 0.0
public var defaultInstruction: String // Default: "You are a helpful assistant."
public var maxTokensPerSegment: Int // Default: 500
}
PipelineLL
Protocollo per l'integrazione dei modelli linguistici con le condutture vocali. LLM alla VocePipeline ASR → LLM → TTS Flusso.
public protocol PipelineLLM: AnyObject {
func chat(messages: [(role: MessageRole, content: String)],
onToken: @escaping (String, Bool) -> Void)
func cancel()
}
Adattatore di compilazione: Qwen3PipelineLLM bridges Qwen35MLXChat a questo protocollo con pulizia, cancellazione e accumulo di frase in attesa.
AudioIO
Gestore I/O audio riutilizzabile che elimina la piastra caldaia AVAudioEngine. Maniglie cattura microfono, ricampionamento, riproduzione e misurazione del livello audio.
let audio = AudioIO()
try audio.startMicrophone(targetSampleRate: 16000) { samples in
pipeline.pushAudio(samples)
}
audio.player.scheduleChunk(ttsOutput)
audio.stopMicrophone()
AudioIO include un StreamingAudioPlayer per TTS output e un AudioRingBuffer per il trasferimento audio sicuro filettato tra filetti di cattura e inferenza.
Sistema audioscopio
Cattura il mix di output del sistema — che cosa sta attualmente giocando il Mac — come il mono Float32 ad una frequenza di campionamento con il caller-chosen (Core Audio process taps, macOS 14.4+). Il rubinetto mono globale esclude il processo corrente per impostazione predefinita, quindi la riproduzione di un'app non viene mai riconquistata e viene avvolta solo in un dispositivo privato.
let tap = SystemAudioTap()
try tap.start(targetSampleRate: 16000) { samples in
pipeline.pushAudio(samples)
}
tap.stop()
Capture sopravvive agli interruttori di default-output-device — il resampler interno viene ricostruito quando il tasso di miscela cambia. NSAudioCaptureUsageDescription. Se il permesso di registrazione è negato, un rubinetto può ancora essere creato ma consegnare solo silenzio: guarda framesCaptured crescere mentre nonSilentFrames rimane 0 e fallisce chiuso.
Fonti indipendenti timestamp
Entrambe le classi di cattura espongono sovraccarichi timestamped. CapturedAudioChunk trasporta mono samples a sampleRate plus a facoltativo Mach hostTime per il primo frame di input. Poiché entrambe le fonti utilizzano lo stesso orologio host, un'applicazione può mantenere il loro PCM trascritto separato e l'ordine
try tap.startTimestamped(targetSampleRate: 16000) { systemChunk in
systemPipeline.pushAudio(systemChunk.samples)
recordSystemTime(systemChunk.hostTime)
}
let microphone = AudioIO(enableAEC: true, enablePlayback: false)
try microphone.startMicrophoneTimestamped(targetSampleRate: 16000) { micChunk in
microphonePipeline.pushAudio(micChunk.samples)
recordMicrophoneTime(micChunk.hostTime)
}
Per catturare solo l'ascolto, enableAEC: true attiva Apple Voice Processing prima che il formato del microfono sia letto, e enablePlayback: false Su macOS, la riproduzione del dispositivo viene rimossa dai campioni del microfono e il mancato avvio di AEC viene segnalato invece di rientrare in ingresso crudo.
Ingresso e riconoscimento dei file incredibili
CapturedAudioChunk porta anche un monotonico frameIndex e una isFinal Incantesimo. AudioFileLoader.stream produce questi pezzi su richiesta con memoria limitata, rivenditamples una volta con lo stato del convertitore persistente, e media tutti i canali di input per impostazione predefinita. .first o .select([indices]) quando il routing del canale è noto.
StreamingRecognitionModel e StreamingRecognitionSession fornire una cache-conservazione incrementale API attraverso Parakeet Streaming e Nemotron Core ML/MLX:
let source = AudioFileLoader.stream(
url: inputURL,
options: AudioFileStreamOptions(targetSampleRate: 16_000,
chunkDuration: 0.32,
channelSelection: .mixAll))
let session = try model.makeStreamingSession(language: "en-US")
for try await chunk in source {
for update in try session.push(chunk) {
render(update.text, final: update.isFinal)
}
}
for update in try session.finish() {
render(update.text, final: true)
}
SentencePieceModel
Lettore di protobuf condiviso per SentencePiece .model file, vive in AudioCommon. Ogni modulo che deve decodificare i pezzi SentencePiece (PersonaPlex, OmnilingualASR, futuro ASR / TTS porte) costruisce il proprio decoder sulla parte superiore di questo singolo lettore invece di ri-applicare il formato filo protobuf.
public struct SentencePieceModel: Sendable {
public struct Piece: Sendable, Equatable {
public let text: String
public let score: Float
public let type: Int32
public var pieceType: PieceType? { get }
public var isControlOrUnknown: Bool { get }
}
public enum PieceType: Int32 {
case normal = 1, unknown = 2, control = 3,
userDefined = 4, unused = 5, byte = 6
}
public let pieces: [Piece]
public var count: Int { get }
public subscript(_ id: Int) -> Piece? { get }
public init(contentsOf url: URL) throws
public init(modelPath: String) throws
public init(data: Data) throws
}
Utilizzato da: OmnilingualASR.OmnilingualVocabulary, PersonaPlex.SentencePieceDecoder Coperto da 7 test unitari in Tests/AudioCommonTests/SentencePieceModelTests
MLXCommon.SDPA
Gli helper di attenzione del dot-product scalati condivisi in ogni MLX modulo di attenzione (Qwen3-ASR / Qwen3-TTS / Qwen3-Chat / CosyVoice / PersonaPlex / OmnilingualASR). Ogni modulo mantiene le proprie proiezioni — SDPA gestisce solo il rimodellamento → attenzione → unire caldaia.
public enum SDPA {
// Flat [B, T, H*D] input: project/reshape happens inside
public static func multiHead(
q: MLXArray, k: MLXArray, v: MLXArray,
numHeads: Int, headDim: Int, scale: Float,
mask: MLXArray? = nil
) -> MLXArray
// GQA / MQA variant with separate query and KV head counts
public static func multiHead(
q: MLXArray, k: MLXArray, v: MLXArray,
numQueryHeads: Int, numKVHeads: Int, headDim: Int, scale: Float,
mask: MLXArray? = nil
) -> MLXArray
// Already-shaped [B, H, T, D] (RoPE / KV cache paths)
public static func attendAndMerge(
qHeads: MLXArray, kHeads: MLXArray, vHeads: MLXArray,
scale: Float,
mask: MLXArray? = nil
) -> MLXArray
// Same, with ScaledDotProductAttentionMaskMode enum (newer API)
public static func attendAndMerge(
qHeads: MLXArray, kHeads: MLXArray, vHeads: MLXArray,
scale: Float,
mask: MLXFast.ScaledDotProductAttentionMaskMode
) -> MLXArray
// Low-level head merge: [B, H, T, D] → [B, T, H*D]
public static func mergeHeads(_ attn: MLXArray) -> MLXArray
}
Tutte le chiamate di rimodellamento usano -1 per la dimensione del lotto in modo che gli helper compongano con MLX.compile(shapeless:) che variano il lotto a runtime (ad esempio Qwen3-TTS Decodice autoregressivo di Talker).
HTTP API Server
Il speech-server binario espone ogni modello in speech-swift come endpoint HTTP REST più un endpoint WebSocket che implementa il OpenAI in tempo reale API. I modelli sono caricati pigramente su prima richiesta; pass --preload per riscaldarli tutti all'avvio.
swift build -c release
.build/release/speech-server --port 8080
# Preload every model at startup
.build/release/speech-server --port 8080 --preload
REST Punti finali
| Punto di arrivo | Metodo | Richiesta | Risposta |
|---|---|---|---|
/transcribe | POST | audio/wav corpo fisico | JSON { text } ( Qwen3-ASR) |
/v1/audio/transcriptions | POST | Multipart { file, model, response_format?, language?, prompt?, temperature? } | JSON, testo, verbose JSON, SRT o VTT |
/v1/audio/speech | POST | JSON { model, input, voice, response_format?, speed? } | audio/wav o audio/pcm corpo fisico |
/speak | POST | JSON { text, engine?, language?, voice? } | audio/wav corpo (Qwen3-TTS, CosyVoice e Kokoro) |
/respond | POST | audio/wav corpo fisico | audio/wav corpo ( PersonaPlex) |
/enhance | POST | audio/wav corpo fisico | audio/wav corpo ( DeepFilterNet3 |
/vad | POST | audio/wav corpo fisico | Elenco dei segmenti JSON |
/diarize | POST | audio/wav corpo fisico | JSON DiarizedSegment |
/embed-speaker | POST | audio/wav corpo fisico | JSON [Float] (256-dim) |
/v1/audio/speech mappe standard TTS modello e nomi vocali a motori locali. Di default a WAV; response_format: "pcm" restituisce headerless 24 kHz mono PCM16 audio piccolo-endiano.
/v1/audio/transcriptions accetta un file WAV come dati del modulo multipart e di default a {"text":"..."}; response_format può anche richiedere testo, verbose JSON, SRT o VTT. Su Apple, modelli alias selezionare un locale ASR motore. Il confezionato speech-core server su Linux e Windows utilizza lazily caricato Parakeet, accetta PCM16/24/32 o Float32 WAV, e i tappi caricano a 25 MiB e 10 minuti.
# Transcribe a file
curl -X POST http://localhost:8080/transcribe \
--data-binary @recording.wav \
-H "Content-Type: audio/wav"
# Synthesize speech
curl -X POST http://localhost:8080/speak \
-H "Content-Type: application/json" \
-d '{"text": "Hello world", "engine": "cosyvoice"}' \
-o output.wav
# OpenAI-compatible transcription
curl http://localhost:8080/v1/audio/transcriptions \
-F "[email protected];type=audio/wav" \
-F "model=whisper-1"
# OpenAI-compatible speech synthesis
curl http://localhost:8080/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{"model":"tts-1","voice":"alloy","input":"Hello world","response_format":"wav"}' \
-o openai-output.wav
# Full speech-to-speech round trip
curl -X POST http://localhost:8080/respond \
--data-binary @question.wav \
-o response.wav
OpenAI Realtime API ( /v1/realtime)
Il punto finale WebSocket ws://host:port/v1/realtime implementa il protocollo OpenAI Realtime. Tutti i messaggi sono JSON con un type discriminatore; i payload audio sono base64-encoded PCM16 a 24 kHz mono.
Durante i carichi di modelli freddi o di lunga generazione, il server emette leggeri realtime.keepalive JSON eventi e fotogrammi di controllo del pong di websocket circa ogni 15 secondi fino a quando l'output del modello è pronto. I clienti possono ignorare questi eventi o trattarli come indicatori di attività.
Client → Eventi del server
| Evento | Oggetto |
|---|---|
session.update | Configurare il motore, la lingua, la voce, il formato audio e il server opzionale VAD |
input_audio_buffer.append | Appendere un pezzo base64 PCM16 al buffer di input |
input_audio_buffer.commit | Commettere l'audio buffered per la trascrizione |
input_audio_buffer.clear | Scardire il buffer di ingresso corrente |
response.create | Richiesta TTS sintesi per il testo/istruzioni fornite |
Server → Eventi client
| Evento | Significato |
|---|---|
session.created | Handshake completo, configurazione predefinita emessa |
session.updated | Più recente session.update riconosciuto |
input_audio_buffer.speech_started | Server VAD ha rilevato l'inizio di un turno di discorso |
input_audio_buffer.speech_stopped | Server VAD ha concluso il giro dopo il silenzio o il limite di durata |
input_audio_buffer.committed | Audio accettato e in coda per trascrizione |
conversation.item.input_audio_transcription.completed | ASR risultato con testo di trascrizione finale |
response.audio.delta | Base64 PCM16 pezzo di audio sintetizzato |
response.audio.done | Basta pezzi audio per questa risposta |
response.done | Risposta finalizzata (metadati + statistiche di latenza) |
error | Busta di errore con type e message |
Turni automatici con server VAD
Esplicito input_audio_buffer.commit Per lasciare che il server si chiuda e trascrivi automaticamente con Silero VAD, configurare turn_detection. Il server mantiene solo il pre-roll richiesto durante il periodo di inattività, emette l'avvio/arresto del discorso e commette eventi in ordine, e la forza chiude un giro continuo attivo max_turn_duration_ms.
ws.send(JSON.stringify({
type: 'session.update',
session: {
turn_detection: {
type: 'server_vad',
threshold: 0.5,
prefix_padding_ms: 300,
silence_duration_ms: 500,
max_turn_duration_ms: 120000
}
}
}));
// Disable automatic turns and return to explicit commits:
// { type: 'session.update', session: { turn_detection: null } }
const ws = new WebSocket('ws://localhost:8080/v1/realtime');
// ASR: push audio, request transcription
ws.send(JSON.stringify({ type: 'input_audio_buffer.append', audio: base64PCM16 }));
ws.send(JSON.stringify({ type: 'input_audio_buffer.commit' }));
// → conversation.item.input_audio_transcription.completed
// TTS: request synthesis and stream audio deltas
ws.send(JSON.stringify({
type: 'response.create',
response: { modalities: ['audio', 'text'], instructions: 'Hello world' }
}));
// → response.audio.delta (repeated), response.audio.done, response.done
Il server vive nel AudioServer Prodotto SPM. Un client del browser di esempio viene spedito Examples/websocket-client.html — aprirlo accanto a un server in esecuzione per guidare il viaggio completo ASR + TTS.
Download del modello
Tutti i modelli sono scaricati da HuggingFace sul primo utilizzo e memorizzati nella cache ~/Library/Caches/qwen3-speech/. AudioCommon modulo fornisce una condivisione HuggingFaceDownloader che gestisce il download, il caching e la verifica dell'integrità.
Scarica retries
Caricatori di modelli e download API accetta downloadOptions: DownloadOptions. onRetry riceve un DownloadRetryEvent prima di ogni backoff, con modelId, attempt, maxAttempts, l'originale errore retryInSeconds. nil significa che non ci sono ritorsioni; il fallback completo può ancora avere successo.
I callback sono sincroni e @Sendable, senza alcuna garanzia del vettore principale. La cancellazione non produce alcun evento di riprovazione. Set retryDelaysSeconds per personalizzare la politica ([] significa un tentativo); l'argomento di download esplicito API ha la precedenza. I conti di tentativi di default e i progressi byte sono conservati. I tentativi falliti sono anche registrati a livello di avviso attraverso AudioLog.download.
Cancellazione cooperativa
Per la trascrizione MLX, Qwen3ASRModel.transcribeCheckingCancellation(audio:sampleRate:options:) getti CancellationError quando la cancellazione dell'attività viene osservata prima dell'estrazione della caratteristica, codifica, prefill decoder, o un passo di decodifica. GPU il lavoro già presentato può finire. transcribe e transcribeBatch Le API mantengono il loro comportamento non rovesciante e ignorano la cancellazione delle attività. speech-server utilizza il punto di annullamento-consapevole per Qwen3-ASR.
MOSS-TTS-Nano
MossTTSNanoModel serializza richieste e crea fresco CoreML stato per ogni pronuncia. sintetizzare e sintetizzareStream preservare stereo; SpeechGenerationModel restituisce un mono downmix. Lingua di passaggio: nil. Streams termina con un marcatore finale vuoto.
import AudioCommon
import MossTTSNano
let model = try await MossTTSNanoModel.fromPretrained()
let reference = try await model.encodeReference(url: referenceURL)
let result = try await model.synthesize(
text: "The weather is nice today.", reference: reference,
options: MossTTSNanoOptions(maxFrames: 300, seed: 1))
try WAVWriter.writeStereo(left: result.left, right: result.right,
sampleRate: 48000, to: outputURL)Inferenza locale
Per 12B, i marcatori di chiusura adiacenti ripetuti immediatamente dopo una chiamata completa e validata vengono consumati con tale chiamata. Non creano un'altra chiamata o modificano i suoi argomenti. I marcatori standalone e gli argomenti malformati rimangono errori; E4B mantiene una gestione rigorosa.
Per mantenere le richieste di chat native fallite, impostare failureCaptureDirectory La cattura è disattivata per impostazione predefinita. La directory deve negare il gruppo e l'altro accesso; nuove directory utilizzano 0700 e l'uso di file di guasto 0600. I file includono l'esatto corpo di richiesta, ID di richiesta ed errore. Le generazioni complete di 12B mantengono anche il prompt reso, ID di token, output grezzo e ragione di arresto prima di parsing. I guasti E4B non hanno record di generazione grezza. Questi file locali contengono contenuto di conversazione e strumento; errori HTTP e richieste di successo non includono.
Gemma4UnifiedChat.generateResponse accetta un opzionale onGeneration callback con un Gemma4GenerationTrace. Il callback viene eseguito prima dell'analisi degli strumenti, incluso quando tale completamento viene respinto. La registrazione non cambia campionamento o sintassi degli strumenti accettati.
Agenti opzionali La direzione carica un Gemma 4 12B Unificata INT4 modello di testo insieme E4B. Modello d'uso: "gemma4-12b-it" per chattare con strumenti nativi, storia dei risultati e cancellazione. E4B rimane disponibile per altre richieste. Gemma4UnifiedChat utilizza il pinned MLX L'implementazione del testo e rilascia la cache KV di ogni richiesta. La parsing di risposta rimuove i delimitatori del canale dal testo restituito. I terminatori di chiamata degli strumenti orfani vengono rifiutati come risposte malformate. Gemma 4 12B, contenuto di messaggio-utensile mantiene JSON come testo, corrispondente al modello di chat di checkpoint.
Le costruzioni di origine accettano --inference-manifest modelli.json per caricare in scena Gemma 4 E4B INT4, VoxCPM2♪ Whisper Large-v3e Silero VAD. Questo servizio inizia offline e rifiuta identità di modello incompatibili.
VoxCPM2 supporta il pesoPrecisione: .float16 per un checkpoint non specificato. I suoi parametri principali usano FP16; AudioVAE e alcune computazioni rimangono FP32. INT8
Gemma4ConversationMessage, Gemma4Tool, e generareRisponsa conservare le dichiarazioni, gli argomenti e la storia dei risultati degli strumenti nativi. InferenceCancellationToken annulla la generazione in modo cooperativo. Silero
Whisper ASRModel.dai carichi direttivi in scena pieno Large-v3 CoreML i fasci. 32-layer decoder può costruire il suo prefisso senza TextDecoderContextPrefill.mlmodelc e rilascia i tempi per passo. Whisper Il modello CLI rimane Turbo.
Il servizio nativo serializza la chat, la sintesi e la trascrizione; leggero VAD ha uno stato di flusso separato. Gemma prefill utilizza batch a 256-token, viene rilasciato lo stato KV richiesta, e la cache MLX è bloccata a 128 MiB con un limite di allocatore da 20 GiB.
Discorso con flusso: restituisce veri eventi audio NDJSON a 48 kHz, seguito da fatto o errore. Annullare da requestID a /v1/inference/cancel. GET /v1/v1/inferenza/capabilità e /v1/Inference/provenance report identities, parametro dtypes, asset pins, e contatori di memoria.
speech-server --inference-manifest models.json --port 8080POST /v1/inference
{"operation":"chat","model":"gemma4-e4b-it","requestID":"chat-1","messages":[{"role":"user","content":"Hello"}],"maxTokens":128,"temperature":0}
chat: gemma4-e4b-it
speech: voxcpm2
transcribe: whisper-large-v3
vad: silero-vad
Audio: audioBase64 (mono PCM16), sampleRate
VAD: streamID, reset, endStream; 512 samples/frame at 16000 Hz
Tools: name, description, parameters
Tool call: id, name, arguments
Tool result: role="tool", toolCallID, contentProprietà del modello opzionale
SpeechRuntime fornisce il caricamento condiviso, l'ammissione FIFO cancellabile e l'esecuzione serializzata. Qwen3ASRRuntime seleziona MLX o CoreML completo attraverso descrittori espliciti; le API native e le bandiere CLI rimangono disponibili.
Il server audio contiene un permesso di esecuzione per ogni richiesta completa, inclusa l'uscita in streaming, con fino a 32 richieste di attesa. ASR guasti producono errori HTTP o WebSocket
import Qwen3ASRRuntime
let runtime = Qwen3ASRRuntime(descriptor: Qwen3ASRDescriptors.small)
let text = try await runtime.transcribe(audio: samples, sampleRate: 16000)
try await runtime.unload()Voxtral TTS
Voxtral 4B TTS 2603 funziona localmente su Apple Silicon attraverso il modulo VoxtraLTTS: 20 voci predefinite, nove lingue e 24 kHz monolingua.
import VoxtralTTS
let model = try await VoxtralTTSModel.fromPretrained()
for try await chunk in model.generateStream(text: "Hello, welcome to Voxtral.", voice: "casual_male") {
print(chunk.frameIndex, chunk.samples.count, chunk.isFinal)
}Solo le voci preimpostate; la clonazione audio di riferimento non è disponibile. I flussi di output audio attraverso il SDK, CLI, WebSocket e HTTP PCM. Fornire il testo di input completo sul fronte. I pesi del modello utilizzano CC BY-NC 4.0.
import AudioCommon
let player = StreamingAudioPlayer()
player.skipInitialSilence = false