Fixed 'dict' object has no attribute 'cpu' error when using Voice Designer
voices with TTS Text node.
## Root Cause
Voice Designer and Character Voices nodes output unified NARRATOR_VOICE format:
{
"audio": {"waveform": tensor, "sample_rate": 24000},
"audio_path": "...",
"reference_text": "...",
...
}
But TTS Text node was wrapping this AGAIN when creating voice_mapping:
{
"waveform": audio_tensor, # audio_tensor was already a dict!
"sample_rate": 24000,
"text": "..."
}
This created triple-nested structure that broke audio hashing.
## Fixes Applied
**TTS Text Node** (nodes/unified/tts_text_node.py):
- Extract actual waveform tensor from audio dict before creating voice_mapping
- Applied to all 6 places where audio_tensor is used:
- Narrator voice mapping (line 1151)
- Character ICL fallback (line 1183)
- Character x_vector fallback (line 1217)
- Character no-text fallback (line 1220)
- Character no-audio fallback (line 1237)
- Pattern: Check if audio_tensor is dict, extract waveform/sample_rate if so
**Qwen3-TTS Adapter** (engines/adapters/qwen3_tts_adapter.py):
- Extract inner audio dict before passing to hash function
- Changed from passing full voice_ref to passing voice_ref['audio']
**Audio Hash Utility** (utils/audio/audio_hash.py):
- Added nested structure handling with fallback
- Detects Character Voices format {"audio": {...}} vs direct format
- Enhanced debug logging to diagnose structure issues
## Testing
- Voice Designer → TTS Text: Works without hash error
- Character Voices → TTS Text: Works without hash error
- Audio hashing generates stable keys for caching
- Both VoiceDesign and traditional voices function correctly
Major changes:
1. Post-processor now uses Audio Editor node instead of calling engine directly
- Inherits progress bars, caching, tested code
- Much simpler and more maintainable
2. Fixed unified model interface usage across Step Audio EditX
- Adapter now loads via unified_model_interface.load_model()
- Audio Editor node now loads via unified_model_interface.load_model()
- Both use model_type="tts" for consistent cache keys
- Prevents duplicate model loading (was loading twice before)
3. Fixed pause tag + edit tag interaction
- Pause tags split FIRST, then edit tags extracted per segment
- Allows: "Hello <Laughter> [pause:1] World <Sigh>"
- Laughter applies to "Hello", Sigh applies to "World"
4. Added lesson to fails document
- All engines must use unified interface in ALL code paths
- Cache key must match across adapters/nodes/processors
Bug fixes:
- Missing Dict import in post-processor
- Import path for Audio Editor node (use importlib)
- Progress bars now work during editing
Files modified:
- utils/audio/edit_post_processor.py - refactored to use Audio Editor node
- nodes/step_audio_editx/step_audio_editx_processor.py - pause handling
- engines/adapters/step_audio_editx_adapter.py - unified interface
- nodes/step_audio_editx_special/step_audio_editx_audio_editor_node.py - unified interface
- docs/Dev reports/fails_to_avoid_TTS_Engine_Implementation.md - added lesson
Replace complex weight restoration workaround with a simple one-line config fix.
The root cause: Step-Audio-EditX model config was missing 'tie_word_embeddings'
setting, so transformers 4.54+ defaulted to True and tied lm_head to embed_tokens
even though the checkpoint has different weights for each.
Solution: Add 'tie_word_embeddings': false to config.json to prevent tying.
This is much simpler and cleaner than the previous safetensors weight restoration
approach. One line in config.json fixes the issue for all transformers versions.
- Add step_audio_editx to PARAMETER_ENGINES for seed and temperature
- Fix engine node tooltips: parameters ARE used in clone mode (not hardcoded)
- Add reference implementation paths to implementation plan
- Update fails doc with latest lessons learned
Make both engines follow ChatterBox 23-Lang modular pattern:
**Step Audio EditX:**
- Update combine_audio_segments() to use ChunkCombiner with return_info=True
- Return (audio, chunk_info) tuple when requested
- Fix single segment shape handling (ensure 2D [channels, samples])
**IndexTTS:**
- Add combine_audio_segments() method using ChunkCombiner
- Update process_text() to accept return_info parameter
- Build chunk_info from text segments and return timing data
**Unified Node:**
- Call both engines with return_info=True
- Use ChunkTimingHelper.enhance_generation_info() for detailed reports
- Clean text without tags before calculating length
**Documentation:**
- Add generation_info consistency requirement to fails document
- Document correct pattern: ChunkCombiner + ChunkTimingHelper
- Note wrong pattern: inline torch.cat without timing
Now both engines provide detailed timing reports like F5-TTS and ChatterBox.
Models should only unload when explicitly requested (Clear VRAM button) or when
switching engines - NOT automatically after generation completes.
__del__ was causing models to unload immediately after wrapper instance goes
out of scope, preventing model reuse between generations.
Pattern: F5-TTS and ChatterBox don't have __del__, only Index2 and StepAudio had it.
Removed to match correct behavior.
- Configure character parser with available characters and language defaults like IndexTTS
- Add characters from text tags to available_characters to prevent fallback to "narrator"
- Add fallback voice mapping entries for characters without voice files
- Update fails document with character switching implementation lessons
Implemented Step Audio EditX integration with the following components:
Engine Implementation:
- Bundled Step Audio EditX reference code in step_audio_editx_impl/
- Created engine wrapper (step_audio_editx.py) with clone/edit methods
- Created adapter (step_audio_editx_adapter.py) for unified interface
- Created processor (step_audio_editx_processor.py) for TTS Suite integration
Model Management:
- Created downloader (step_audio_editx_downloader.py) for:
- Main Step-Audio-EditX model (3B LLM + CosyVoice vocoder)
- Step-Audio-Tokenizer files (linguistic_tokenizer.npy, speech_tokenizer_v1.onnx)
- FunASR-Paraformer model with correct config for bundled funasr_detach
- Fixed FunASR config to use bundled class names (ParaformerStreaming, WavFrontendOnline, SpecAugLFR)
- Added auto-download for all required models on first use
- Updated unified_model_interface.py to auto-download Step-Audio-EditX components
Node Configuration:
- Created Step Audio EditX engine config node
- Registered in nodes.py and unified TTS text node
Known Issues:
- LLM generation hangs during inference despite:
- torch.no_grad() wrapper
- torch.cuda.synchronize()
- Eager attention mode (Step1ForCausalLM doesn't support SDPA)
- Reduced max_new_tokens
- Removed logits processor
- Model loads successfully, all components initialized correctly
- Issue appears to be with transformers.generate() call itself
- Requires further investigation of Step1ForCausalLM compatibility
Files Modified:
- nodes.py: Added Step Audio EditX registration
- requirements.txt: Added FunASR dependencies
- utils/models/unified_model_interface.py: Added Step Audio EditX factory
- Various Step Audio EditX implementation files
Model Downloads:
- Step-Audio-EditX: 7GB (3B LLM + vocoder)
- FunASR-Paraformer: 881MB (from custom HF repo with fixed config)
- Step-Audio-Tokenizer: Bundled with main model download