TTS Process Topology#
StageConfig.process is the source of truth for process topology. It is plain
per-stage configuration: the model’s config class declares the default, and a
config file or a dotted CLI flag overrides it like any other stage field.
Omitting it preserves the topology declared by the selected model or YAML
config.
A config file can make vocoder isolation persistent:
stages:
vocoder:
process: vocoder
Or keep the vocoder in a shared process:
stages:
vocoder:
process: pipeline
MOSS-TTS delay ships the isolated layout as its default, with declared GPU
memory fractions 0.10 / 0.72 / 0.18 for preprocessing, tts_engine and
vocoder. config_cls: MossTTSSingleProcessPipelineConfig selects the
single-process variant, which the bounded 24 GB and 32 GB configurations and
the MPS DP2 recipe pin because their measured budgets describe that layout.
Changing Placement at Launch#
The same field is set from the command line with the dotted spelling, without editing the source config:
# put the vocoder in its own process
python -m sglang_omni.cli serve \
--model-path MODEL \
--vocoder.process vocoder
Repeating one process name colocates stages in it. The following override produces the topology already declared by the built-in Higgs-TTS config:
python -m sglang_omni.cli serve \
--model-path bosonai/higgs-tts-3-4b \
--preprocessing.process tts_frontend \
--audio_encoder.process tts_frontend
tts_frontend : preprocessing, audio_encoder
pipeline : tts_engine
vocoder : vocoder
Setting a stage to the process it already runs in is an idempotent no-op.
Writing one stage’s process twice with different values is refused as a
conflict, like any other doubly-written path.
How a Placement Is Validated#
Process topology is validated by the placement and topology planners before startup:
Every non-TP stage must declare a
process; TP stages derive one process per rank.A process group may span CPU stages and stages on at most one GPU.
When multiple process groups share one GPU, every GPU stage involved must declare
gpu_memory_fraction, and the per-GPU total must fitplacement.max_total_gpu_memory_fraction_per_gpu. Validation names the stages whose fractions are missing.TP rank process names must not collide with other process groups.
Not every handoff tolerates a process boundary: some stages exchange state
through process-local registries a second process cannot read (for example,
MOSS-TTS pipelines hand prepared requests from preprocessing to the AR engine
through a process-local queue). A model declares those edges through
PipelineConfig.process_local_edges, and splitting one is refused during
topology compilation, before any worker starts.
Qwen3-TTS keeps prepared requests in process-local module state only while
preprocessing and tts_engine share a process; placed in its own process the
preprocessing stage loads a prompt frontend and ships the prepared prompt
tensors through tensor_cpu payload fields, so that edge can cross too. The
tensor_cpu codec keeps each tensor’s own dtype and lets the relay carry it
outside the control plane, which typed_tensor would not do.
Ming-Omni-TTS carries preprocessing fields in StagePayload.data and serializes the reference encoder’s spk_emb and prompt_latent tensors with the typed_tensor wire codec. Both preprocessing -> reference_encode and reference_encode -> tts_engine can therefore cross process boundaries.
Resource and Performance Trade-offs#
Splitting a stage out creates another OS process and usually another CUDA context. It can improve throughput by overlapping vocoder scheduling and GPU work with generation, but it also changes IPC and serialization paths, can increase idle VRAM, and may duplicate process-local caches or runtime state. Grouping stages that share a cache or a local handoff keeps that cost down.
When multiple processes share one GPU, all affected GPU stages must declare
compatible gpu_memory_fraction values, and their total must fit the
placement limit. These fractions are placement-accounting declarations, not
proof of an allocator-enforced runtime limit: a factory receives
total_gpu_memory_fraction only when its signature accepts that argument,
and an engine.mem_fraction_static override can represent a different
runtime value. Keep the two consistent.
Performance depends on the model, hardware, concurrency, request shape, and streaming mode. Measure the target workload before making isolation persistent in model or YAML configuration.