Marcos Claude commited on
Commit ·
4f31f44
1
Parent(s): b0c4347
refactor: clean project structure to essentials only
Browse files- Renamed main server to server.py (single server)
- Created simplified start.sh script
- Removed 200+ redundant files:
- 60+ duplicate test files
- Unused directories (llama-omni2-official, llama_omni2_integration)
- Old installation scripts
- Development/debug files
- Kept only essential files:
- server.py (main WebRTC server with GPU+vLLM)
- install.sh (complete installation with exact versions)
- start.sh (simple startup script)
- llama_omni2/ (core implementation)
- requirements.txt
- Model: Qwen3-0.6B with vLLM 0.8.4
- Performance: 378ms latency, 92% coherence
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This view is limited to 50 files because it contains too many changes. See raw diff
- .claude/commands/benchmark.md +61 -0
- .claude/commands/debug-pipeline.md +92 -0
- .claude/commands/optimize-model.md +48 -0
- .claude/commands/start-webrtc.md +26 -0
- .claude/commands/test-latency.md +21 -0
- ANALISE_MODIFICACOES.md +0 -128
- CLAUDE.md +0 -322
- COMMUNICATION_LATENCY_ANALYSIS_REPORT.md +0 -302
- DUAL_MODEL_USAGE.md +0 -83
- INSTALLATION_GUIDE.md +0 -133
- PACKAGES_INSTALLED.md +0 -74
- PLANO_INTEGRACAO_REALTIMETTS.md +0 -194
- PLANO_TROCA_LLM_TTS_EXTERNO.md +0 -261
- README.md +0 -394
- README_INSTALLATION.md +0 -184
- RELATORIO_VIABILIDADE_TROCA_QWEN_MULTILINGUE.md +0 -302
- SPEECH_PROJECTOR_ANALYSIS.md +0 -110
- analyze_generated_audio.py +0 -227
- benchmark_20q_gpu_final.py +0 -312
- coherence_test_results.json +0 -82
- communication_analysis_report.py +0 -388
- communication_latency_test.py +0 -370
- create_real_speech.py +0 -100
- create_test_audio.py +0 -94
- docs/A1_VOCABULARY_CONTROL_TECHNIQUES.md +0 -587
- download_llama_omni2.py +0 -25
- download_official_model.py +0 -13
- generate_test_audios.py +0 -81
- gtts_test_results.json +0 -82
- installed_packages.txt +0 -246
- llama_omni2_integration/__init__.py +0 -1
- llama_omni2_integration/constants.py +0 -9
- llama_omni2_integration/omni2_speech_arch.py +0 -201
- llama_omni2_integration/qwen2_speech_model.py +0 -155
- llama_omni2_integration/qwen2_speech_model_fixed.py +0 -294
- llama_omni2_integration/speech_encoder/__init__.py +0 -1
- llama_omni2_integration/speech_encoder/builder.py +0 -9
- llama_omni2_integration/speech_encoder/speech_encoder.py +0 -26
- llama_omni2_integration/speech_projector/__init__.py +0 -1
- llama_omni2_integration/speech_projector/builder.py +0 -9
- llama_omni2_integration/speech_projector/speech_projector.py +0 -30
- load_speech_projector.py +0 -184
- webrtc_server_gpu_vllm.py → server.py +0 -0
- simple_speech_chat_torchcompiled.py +0 -230
- start.sh +51 -246
- stop.sh +0 -76
- streaming_latency_test.py +0 -262
- system_prompt_v2.md +0 -94
- test_100_questions_final.py +0 -401
- test_100_questions_final_v1.py +0 -413
.claude/commands/benchmark.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Benchmark Performance Command
|
| 2 |
+
|
| 3 |
+
Run comprehensive performance benchmarks on the speech-to-speech system.
|
| 4 |
+
|
| 5 |
+
## Benchmark Tests:
|
| 6 |
+
|
| 7 |
+
### 1. Model Loading Time
|
| 8 |
+
```bash
|
| 9 |
+
python -c "
|
| 10 |
+
import time
|
| 11 |
+
start = time.time()
|
| 12 |
+
from transformers import AutoModelForCausalLM
|
| 13 |
+
model = AutoModelForCausalLM.from_pretrained('Qwen/Qwen3-0.6B', cache_dir='/tmp/hf_cache')
|
| 14 |
+
print(f'Load time: {time.time() - start:.2f}s')
|
| 15 |
+
"
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
### 2. Inference Speed
|
| 19 |
+
```bash
|
| 20 |
+
python test_optimized_cpu_inference.py
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
### 3. Full Pipeline
|
| 24 |
+
```bash
|
| 25 |
+
python test_optimized_speech_to_speech.py
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
### 4. WebRTC Latency
|
| 29 |
+
```bash
|
| 30 |
+
python test_webrtc_optimized.py
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
## Metrics to Track:
|
| 34 |
+
- Model load time (target: <5s)
|
| 35 |
+
- Inference latency (target: <3s)
|
| 36 |
+
- Audio generation (target: <0.5s)
|
| 37 |
+
- Total pipeline (target: <5s)
|
| 38 |
+
- Memory usage (monitor with htop)
|
| 39 |
+
|
| 40 |
+
## Generate Report:
|
| 41 |
+
```python
|
| 42 |
+
results = {
|
| 43 |
+
'model_load': load_time,
|
| 44 |
+
'inference': inference_time,
|
| 45 |
+
'audio_in': audio_in_time,
|
| 46 |
+
'audio_out': audio_out_time,
|
| 47 |
+
'total': total_time
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
print(f"""
|
| 51 |
+
Performance Report
|
| 52 |
+
==================
|
| 53 |
+
Model Load: {results['model_load']:.2f}s
|
| 54 |
+
Inference: {results['inference']:.2f}s
|
| 55 |
+
Audio Input: {results['audio_in']:.2f}s
|
| 56 |
+
Audio Output: {results['audio_out']:.2f}s
|
| 57 |
+
Total Pipeline: {results['total']:.2f}s
|
| 58 |
+
|
| 59 |
+
Status: {'✅ PASS' if results['total'] < 5 else '❌ FAIL'}
|
| 60 |
+
""")
|
| 61 |
+
```
|
.claude/commands/debug-pipeline.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Debug Pipeline Command
|
| 2 |
+
|
| 3 |
+
Debug the speech-to-speech pipeline step by step.
|
| 4 |
+
|
| 5 |
+
## Debug Steps:
|
| 6 |
+
|
| 7 |
+
### 1. Check Environment
|
| 8 |
+
```bash
|
| 9 |
+
python -c "
|
| 10 |
+
import sys
|
| 11 |
+
print('Python:', sys.version)
|
| 12 |
+
import torch
|
| 13 |
+
print('PyTorch:', torch.__version__)
|
| 14 |
+
import transformers
|
| 15 |
+
print('Transformers:', transformers.__version__)
|
| 16 |
+
"
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
### 2. Test Audio Input
|
| 20 |
+
```python
|
| 21 |
+
from gtts import gTTS
|
| 22 |
+
import tempfile
|
| 23 |
+
import soundfile as sf
|
| 24 |
+
|
| 25 |
+
text = "Teste de áudio"
|
| 26 |
+
tts = gTTS(text=text, lang='pt')
|
| 27 |
+
with tempfile.NamedTemporaryFile(suffix='.mp3') as f:
|
| 28 |
+
tts.save(f.name)
|
| 29 |
+
audio, sr = sf.read(f.name)
|
| 30 |
+
print(f"Audio shape: {audio.shape}, Sample rate: {sr}")
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
### 3. Test Model Loading
|
| 34 |
+
```python
|
| 35 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 36 |
+
|
| 37 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 38 |
+
"Qwen/Qwen3-0.6B",
|
| 39 |
+
trust_remote_code=True,
|
| 40 |
+
cache_dir="/tmp/hf_cache"
|
| 41 |
+
)
|
| 42 |
+
print("✅ Tokenizer loaded")
|
| 43 |
+
|
| 44 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 45 |
+
"Qwen/Qwen3-0.6B",
|
| 46 |
+
torch_dtype=torch.float32,
|
| 47 |
+
trust_remote_code=True,
|
| 48 |
+
cache_dir="/tmp/hf_cache"
|
| 49 |
+
)
|
| 50 |
+
print("✅ Model loaded")
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
### 4. Test Inference
|
| 54 |
+
```python
|
| 55 |
+
prompt = "P: Qual é a capital do Brasil?\nR:"
|
| 56 |
+
inputs = tokenizer(prompt, return_tensors="pt")
|
| 57 |
+
|
| 58 |
+
with torch.no_grad():
|
| 59 |
+
outputs = model.generate(
|
| 60 |
+
**inputs,
|
| 61 |
+
max_new_tokens=15,
|
| 62 |
+
do_sample=False
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 66 |
+
print(f"Response: {response}")
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
### 5. Test WebRTC
|
| 70 |
+
```python
|
| 71 |
+
import asyncio
|
| 72 |
+
from aiortc import RTCPeerConnection, RTCConfiguration, RTCIceServer
|
| 73 |
+
|
| 74 |
+
async def test_webrtc():
|
| 75 |
+
ice_servers = [RTCIceServer(urls=["stun:stun.l.google.com:19302"])]
|
| 76 |
+
config = RTCConfiguration(iceServers=ice_servers)
|
| 77 |
+
pc = RTCPeerConnection(configuration=config)
|
| 78 |
+
channel = pc.createDataChannel("test")
|
| 79 |
+
print("✅ WebRTC components working")
|
| 80 |
+
await pc.close()
|
| 81 |
+
|
| 82 |
+
asyncio.run(test_webrtc())
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
## Common Issues:
|
| 86 |
+
|
| 87 |
+
| Error | Solution |
|
| 88 |
+
|-------|----------|
|
| 89 |
+
| ModuleNotFoundError | Check virtual env activation |
|
| 90 |
+
| CUDA not available | Use CPU optimizations |
|
| 91 |
+
| High latency | Enable torch.compile() |
|
| 92 |
+
| WebRTC fails | Check port availability |
|
.claude/commands/optimize-model.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Optimize Model Command
|
| 2 |
+
|
| 3 |
+
Apply all optimizations to achieve <3s latency.
|
| 4 |
+
|
| 5 |
+
## Optimization Checklist:
|
| 6 |
+
- [ ] Enable torch.compile()
|
| 7 |
+
- [ ] Set max_new_tokens=15
|
| 8 |
+
- [ ] Use greedy decoding (do_sample=False)
|
| 9 |
+
- [ ] Enable KV cache
|
| 10 |
+
- [ ] Model in eval mode
|
| 11 |
+
- [ ] Warm-up model before first use
|
| 12 |
+
|
| 13 |
+
## Code Template:
|
| 14 |
+
```python
|
| 15 |
+
import torch
|
| 16 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 17 |
+
|
| 18 |
+
# Load model with optimizations
|
| 19 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 20 |
+
"Qwen/Qwen3-0.6B",
|
| 21 |
+
torch_dtype=torch.float32,
|
| 22 |
+
trust_remote_code=True,
|
| 23 |
+
cache_dir="/tmp/hf_cache",
|
| 24 |
+
low_cpu_mem_usage=True
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
# Apply optimizations
|
| 28 |
+
model.eval()
|
| 29 |
+
model = torch.compile(model, mode="reduce-overhead")
|
| 30 |
+
|
| 31 |
+
# Warm-up
|
| 32 |
+
with torch.no_grad():
|
| 33 |
+
warm_input = tokenizer("Test", return_tensors="pt")
|
| 34 |
+
_ = model.generate(**warm_input, max_new_tokens=5)
|
| 35 |
+
|
| 36 |
+
# Optimized generation
|
| 37 |
+
def generate_fast(text):
|
| 38 |
+
inputs = tokenizer(text, return_tensors="pt")
|
| 39 |
+
with torch.no_grad():
|
| 40 |
+
outputs = model.generate(
|
| 41 |
+
**inputs,
|
| 42 |
+
max_new_tokens=15,
|
| 43 |
+
do_sample=False,
|
| 44 |
+
num_beams=1,
|
| 45 |
+
use_cache=True
|
| 46 |
+
)
|
| 47 |
+
return tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 48 |
+
```
|
.claude/commands/start-webrtc.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Start WebRTC Server Command
|
| 2 |
+
|
| 3 |
+
Launch the WebRTC server for real-time speech communication.
|
| 4 |
+
|
| 5 |
+
## Steps:
|
| 6 |
+
1. Check if port 8081 is available
|
| 7 |
+
2. Activate virtual environment
|
| 8 |
+
3. Start the unified WebRTC server
|
| 9 |
+
4. Monitor for connection
|
| 10 |
+
|
| 11 |
+
## Command:
|
| 12 |
+
```bash
|
| 13 |
+
cd /tmp/llama-omni2-official-code
|
| 14 |
+
source /tmp/llama-omni2-vllm-env/bin/activate
|
| 15 |
+
python unified_webrtc_server.py
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
## Ports:
|
| 19 |
+
- WebRTC: 8081
|
| 20 |
+
- WebSocket: ws://localhost:8081/ws
|
| 21 |
+
- HTTP: http://localhost:8081
|
| 22 |
+
|
| 23 |
+
## Health Check:
|
| 24 |
+
```bash
|
| 25 |
+
curl http://localhost:8081/health
|
| 26 |
+
```
|
.claude/commands/test-latency.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Test Latency Command
|
| 2 |
+
|
| 3 |
+
Run the optimized speech-to-speech pipeline test to verify latency is under 3 seconds.
|
| 4 |
+
|
| 5 |
+
## Steps:
|
| 6 |
+
1. Activate the virtual environment
|
| 7 |
+
2. Run the optimized test script
|
| 8 |
+
3. Verify latency metrics
|
| 9 |
+
4. Report results
|
| 10 |
+
|
| 11 |
+
## Command:
|
| 12 |
+
```bash
|
| 13 |
+
cd /tmp/llama-omni2-official-code
|
| 14 |
+
source /tmp/llama-omni2-vllm-env/bin/activate
|
| 15 |
+
python test_optimized_speech_to_speech.py
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
## Success Criteria:
|
| 19 |
+
- Average inference time < 3s
|
| 20 |
+
- Full pipeline < 5s
|
| 21 |
+
- All test questions answered correctly
|
ANALISE_MODIFICACOES.md
DELETED
|
@@ -1,128 +0,0 @@
|
|
| 1 |
-
# 📋 Análise de Modificações - WebRTC + gRPC
|
| 2 |
-
|
| 3 |
-
## 🟢 Arquivos para MANTER (Essenciais)
|
| 4 |
-
|
| 5 |
-
### 1. **test_webrtc_latency.py** ✅
|
| 6 |
-
- **Motivo**: Teste principal da integração WebRTC
|
| 7 |
-
- **Funcionalidade**: Testa pipeline completa WebRTC → Worker → TTS
|
| 8 |
-
- **Status**: Funcionando perfeitamente
|
| 9 |
-
- **Decisão**: **MANTER E COMMITAR**
|
| 10 |
-
|
| 11 |
-
### 2. **webrtc_client_aiortc.py** ✅
|
| 12 |
-
- **Motivo**: Implementação com aiortc (framework solicitado)
|
| 13 |
-
- **Funcionalidade**: Cliente WebRTC nativo usando aiortc
|
| 14 |
-
- **Status**: Funcionando, latência de 2-6s
|
| 15 |
-
- **Decisão**: **MANTER E COMMITAR**
|
| 16 |
-
|
| 17 |
-
### 3. **test_webrtc_benchmark.py** ✅
|
| 18 |
-
- **Motivo**: Benchmark de performance (5 requisições)
|
| 19 |
-
- **Funcionalidade**: Análise estatística de latência e consistência
|
| 20 |
-
- **Resultados**: 100% sucesso, média 4.5s, CV 33%
|
| 21 |
-
- **Decisão**: **MANTER E COMMITAR**
|
| 22 |
-
|
| 23 |
-
### 4. **llama_omni2/serve/webrtc_server.py** ⚠️
|
| 24 |
-
- **Motivo**: Servidor WebRTC integrado com Worker gRPC
|
| 25 |
-
- **Modificações principais**:
|
| 26 |
-
- ✅ Integração com Worker gRPC real
|
| 27 |
-
- ✅ Import base64 adicionado
|
| 28 |
-
- ✅ WebRTCAudioProcessor conecta ao Worker
|
| 29 |
-
- ✅ handle_audio_data implementado
|
| 30 |
-
- ✅ WebSocket tracking adicionado
|
| 31 |
-
- ✅ Path estático corrigido
|
| 32 |
-
- **Decisão**: **MANTER MODIFICAÇÕES**
|
| 33 |
-
|
| 34 |
-
## 🔴 Arquivos para REMOVER/REVERTER
|
| 35 |
-
|
| 36 |
-
### 1. **response_webrtc.wav**
|
| 37 |
-
- **Motivo**: Arquivo de teste temporário
|
| 38 |
-
- **Decisão**: **REMOVER** (se existir)
|
| 39 |
-
|
| 40 |
-
### 2. **response_aiortc.wav**
|
| 41 |
-
- **Motivo**: Arquivo de teste temporário
|
| 42 |
-
- **Decisão**: **REMOVER** (se existir)
|
| 43 |
-
|
| 44 |
-
## 📊 Resumo das Modificações no webrtc_server.py
|
| 45 |
-
|
| 46 |
-
### Mudanças ESSENCIAIS (manter):
|
| 47 |
-
```python
|
| 48 |
-
# 1. Import base64 (linha 13)
|
| 49 |
-
import base64
|
| 50 |
-
|
| 51 |
-
# 2. Import gRPC Worker (linhas 24-27)
|
| 52 |
-
import worker_service_pb2
|
| 53 |
-
import worker_service_pb2_grpc
|
| 54 |
-
|
| 55 |
-
# 3. Conexão real com Worker (linhas 37-44)
|
| 56 |
-
self.worker_channel = grpc.insecure_channel(f'{worker_host}:{worker_port}')
|
| 57 |
-
self.worker_stub = worker_service_pb2_grpc.ModelWorkerStub(self.worker_channel)
|
| 58 |
-
|
| 59 |
-
# 4. handle_audio_data implementado (linhas 256-292)
|
| 60 |
-
async def handle_audio_data(self, data, connection_id):
|
| 61 |
-
# Processa áudio via WebSocket
|
| 62 |
-
# Envia para Worker gRPC
|
| 63 |
-
# Retorna resposta
|
| 64 |
-
|
| 65 |
-
# 5. WebSocket tracking (linhas 148-149)
|
| 66 |
-
self.pending_responses = {}
|
| 67 |
-
self.websockets = {}
|
| 68 |
-
|
| 69 |
-
# 6. Path estático corrigido (linha 369-370)
|
| 70 |
-
static_path = os.path.join(os.path.dirname(__file__), "static")
|
| 71 |
-
```
|
| 72 |
-
|
| 73 |
-
### Mudanças OPCIONAIS (avaliar):
|
| 74 |
-
- Remoção do grpc_adapter (substituído por conexão direta)
|
| 75 |
-
- gTTS como fallback (até integrar CosyVoice real)
|
| 76 |
-
|
| 77 |
-
## 🎯 Recomendações
|
| 78 |
-
|
| 79 |
-
### Para Commitar:
|
| 80 |
-
1. ✅ test_webrtc_latency.py
|
| 81 |
-
2. ✅ webrtc_client_aiortc.py
|
| 82 |
-
3. ✅ test_webrtc_benchmark.py
|
| 83 |
-
4. ✅ llama_omni2/serve/webrtc_server.py (modificado)
|
| 84 |
-
|
| 85 |
-
### Comando sugerido:
|
| 86 |
-
```bash
|
| 87 |
-
# Adicionar arquivos essenciais
|
| 88 |
-
git add test_webrtc_latency.py
|
| 89 |
-
git add webrtc_client_aiortc.py
|
| 90 |
-
git add test_webrtc_benchmark.py
|
| 91 |
-
git add llama_omni2/serve/webrtc_server.py
|
| 92 |
-
|
| 93 |
-
# Commit com mensagem descritiva
|
| 94 |
-
git commit -m "feat: Implementação completa WebRTC com aiortc
|
| 95 |
-
|
| 96 |
-
- Integração WebRTC Server com Worker gRPC
|
| 97 |
-
- Cliente aiortc para WebRTC nativo
|
| 98 |
-
- Testes de latência e benchmark (5 requisições)
|
| 99 |
-
- Pipeline completa: Audio → WebRTC → Worker → TTS → Audio
|
| 100 |
-
- Latência média: 4.5s (100% sucesso)
|
| 101 |
-
|
| 102 |
-
🤖 Generated with Claude Code
|
| 103 |
-
|
| 104 |
-
Co-Authored-By: Claude <noreply@anthropic.com>"
|
| 105 |
-
```
|
| 106 |
-
|
| 107 |
-
## 📈 Métricas de Performance
|
| 108 |
-
|
| 109 |
-
| Componente | Latência | Status |
|
| 110 |
-
|------------|----------|--------|
|
| 111 |
-
| WebRTC Handshake | ~50ms | ✅ |
|
| 112 |
-
| Worker gRPC | 2.7-6.3s | ⚠️ |
|
| 113 |
-
| TTS (gTTS) | ~500ms | ✅ |
|
| 114 |
-
| **Total Pipeline** | 3.1-6.7s | ⚠️ |
|
| 115 |
-
|
| 116 |
-
## 🔍 Problemas Conhecidos
|
| 117 |
-
|
| 118 |
-
1. **Latência variável no Worker**: 2.7s a 6.3s
|
| 119 |
-
2. **Warmup perde efeito**: Performance degrada após várias requisições
|
| 120 |
-
3. **CosyVoice não integrado**: Usando gTTS como fallback
|
| 121 |
-
|
| 122 |
-
## ✅ Conquistas
|
| 123 |
-
|
| 124 |
-
1. ✅ WebRTC com aiortc funcionando
|
| 125 |
-
2. ✅ Integração completa com Worker gRPC
|
| 126 |
-
3. ✅ Pipeline speech-to-speech funcional
|
| 127 |
-
4. ✅ 100% taxa de sucesso
|
| 128 |
-
5. ✅ Testes e benchmarks implementados
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CLAUDE.md
DELETED
|
@@ -1,322 +0,0 @@
|
|
| 1 |
-
# LLaMA-Omni2 Speech-to-Speech System - Claude Code Configuration
|
| 2 |
-
|
| 3 |
-
## 🎯 Project Overview
|
| 4 |
-
End-to-end speech-to-speech conversation system implementing the official LLaMA-Omni2 architecture with **Qwen3-0.6B** for Portuguese language support. Achieves **<500ms inference latency** with GPU + vLLM optimization.
|
| 5 |
-
|
| 6 |
-
## 🏗️ Architecture Pipeline
|
| 7 |
-
```
|
| 8 |
-
🎤 Audio → Whisper(GPU) → Speech Projector → Qwen3-0.6B(vLLM) → gTTS → 🔊 Audio
|
| 9 |
-
↓ ↓ ↓
|
| 10 |
-
(embeddings) (feature mapping) (generation)
|
| 11 |
-
```
|
| 12 |
-
|
| 13 |
-
### Key Components
|
| 14 |
-
- **Whisper Encoder**: GPU-accelerated, extracts embeddings (no transcription)
|
| 15 |
-
- **Speech Projector**: EncoderProjectorConcat with k=5 downsampling
|
| 16 |
-
- **LLM**: Qwen3-0.6B with vLLM 0.8.4+ (378ms latency achieved!)
|
| 17 |
-
- **TTS**: gTTS for Portuguese audio
|
| 18 |
-
- **WebRTC**: Real-time communication on port 8888
|
| 19 |
-
|
| 20 |
-
## 🚀 Performance Metrics
|
| 21 |
-
|
| 22 |
-
### Latest Benchmarks (GPU + vLLM)
|
| 23 |
-
| Configuration | Latency | Improvement |
|
| 24 |
-
|--------------|---------|-------------|
|
| 25 |
-
| CPU Baseline | 3610ms | - |
|
| 26 |
-
| GPU PyTorch | 459ms | 87% faster |
|
| 27 |
-
| **GPU + vLLM** | **378ms** | **89.5% faster** |
|
| 28 |
-
| WebRTC End-to-End | 532ms | Still < target |
|
| 29 |
-
| Target | <500ms | ✅ ACHIEVED |
|
| 30 |
-
|
| 31 |
-
### Quality Test Results (50 Questions)
|
| 32 |
-
- **Overall Coherence**: 92% (46/50 correct)
|
| 33 |
-
- **Latency per question**: 65ms average
|
| 34 |
-
- **Categories**:
|
| 35 |
-
- Saudações: 100% (10/10)
|
| 36 |
-
- Conhecimento: 90% (9/10)
|
| 37 |
-
- Matemática: 70% (7/10)
|
| 38 |
-
- Conversação: 100% (10/10)
|
| 39 |
-
- Tempo/Data: 100% (5/5)
|
| 40 |
-
- Despedidas: 100% (5/5)
|
| 41 |
-
|
| 42 |
-
## 📦 Tech Stack
|
| 43 |
-
|
| 44 |
-
### Core Dependencies
|
| 45 |
-
```yaml
|
| 46 |
-
Python: 3.10-3.12
|
| 47 |
-
PyTorch: 2.6.0 (CUDA 12.1)
|
| 48 |
-
vLLM: 0.8.4+ (REQUIRED for Qwen3)
|
| 49 |
-
Transformers: 4.55.4
|
| 50 |
-
CUDA: 12.1+
|
| 51 |
-
GPU: NVIDIA RTX 3060 (12GB VRAM)
|
| 52 |
-
```
|
| 53 |
-
|
| 54 |
-
### Model Requirements
|
| 55 |
-
```yaml
|
| 56 |
-
Model: Qwen/Qwen3-0.6B (NOT Qwen2!)
|
| 57 |
-
vLLM: >= 0.8.4 (Qwen3ForCausalLM support)
|
| 58 |
-
Memory: ~1.2GB model weights
|
| 59 |
-
Dtype: float16 for GPU
|
| 60 |
-
```
|
| 61 |
-
|
| 62 |
-
## 📁 Project Structure
|
| 63 |
-
```
|
| 64 |
-
/tmp/llama-omni2-official-code/
|
| 65 |
-
├── llama_omni2/
|
| 66 |
-
│ ├── model/
|
| 67 |
-
│ │ ├── language_model/
|
| 68 |
-
│ │ │ └── omni2_speech_qwen2.py # Core implementation
|
| 69 |
-
│ │ └── speech_projector/
|
| 70 |
-
│ │ └── speech_projector.py # Feature projection
|
| 71 |
-
│ └── serve/
|
| 72 |
-
│ └── tts/ # TTS services
|
| 73 |
-
├── vLLM_GPU_configs/
|
| 74 |
-
│ ├── webrtc_server_gpu_vllm.py # Production server ⭐
|
| 75 |
-
│ ├── test_gpu_vllm.py # GPU benchmark
|
| 76 |
-
│ ├── test_gpu_alternatives.py # PyTorch vs vLLM
|
| 77 |
-
│ └── benchmark_20q_gpu_final.py # Full benchmark
|
| 78 |
-
├── quality_tests/
|
| 79 |
-
│ ├── test_qwen3_vllm_fixed.py # Qwen3 validation ⭐
|
| 80 |
-
│ ├── test_qwen3_quality_50.py # 50 questions test
|
| 81 |
-
│ └── test_webrtc_quality_50questions.py
|
| 82 |
-
├── scripts/
|
| 83 |
-
│ ├── setup_gpu_vllm.sh # GPU setup script
|
| 84 |
-
│ └── install_vllm_direct.sh # CPU fallback
|
| 85 |
-
└── reports/
|
| 86 |
-
├── qwen3_quality_report.json # 92% coherence
|
| 87 |
-
└── webrtc_quality_report.json # WebRTC metrics
|
| 88 |
-
```
|
| 89 |
-
|
| 90 |
-
## 🚀 Commands & Operations
|
| 91 |
-
|
| 92 |
-
### Environment Setup (GPU + vLLM)
|
| 93 |
-
```bash
|
| 94 |
-
# Create vLLM environment
|
| 95 |
-
python3 -m venv /tmp/llama-omni2-vllm-env
|
| 96 |
-
source /tmp/llama-omni2-vllm-env/bin/activate
|
| 97 |
-
|
| 98 |
-
# Install vLLM 0.8.4+ (REQUIRED for Qwen3)
|
| 99 |
-
pip install vllm==0.8.4 --index-url https://download.pytorch.org/whl/cu121
|
| 100 |
-
|
| 101 |
-
# Verify GPU
|
| 102 |
-
nvidia-smi # Should show RTX 3060
|
| 103 |
-
python -c "import torch; print(torch.cuda.is_available())"
|
| 104 |
-
```
|
| 105 |
-
|
| 106 |
-
### Server Operations
|
| 107 |
-
```bash
|
| 108 |
-
# Start WebRTC server with GPU + vLLM (Production) ⭐
|
| 109 |
-
cd /tmp/llama-omni2-official-code
|
| 110 |
-
python webrtc_server_gpu_vllm.py # Port 8888
|
| 111 |
-
|
| 112 |
-
# Test endpoint
|
| 113 |
-
curl http://localhost:8888/test?text="Olá"
|
| 114 |
-
|
| 115 |
-
# Check stats
|
| 116 |
-
curl http://localhost:8888/stats
|
| 117 |
-
```
|
| 118 |
-
|
| 119 |
-
### Testing & Benchmarks
|
| 120 |
-
```bash
|
| 121 |
-
# Test Qwen3 with vLLM ⭐
|
| 122 |
-
python test_qwen3_vllm_fixed.py
|
| 123 |
-
|
| 124 |
-
# Quality test (50 questions)
|
| 125 |
-
python test_qwen3_quality_50.py
|
| 126 |
-
|
| 127 |
-
# WebRTC quality test
|
| 128 |
-
python test_webrtc_quality_50questions.py
|
| 129 |
-
|
| 130 |
-
# GPU vs CPU benchmark
|
| 131 |
-
python benchmark_20q_gpu_final.py
|
| 132 |
-
```
|
| 133 |
-
|
| 134 |
-
## 💻 Code Patterns & Best Practices
|
| 135 |
-
|
| 136 |
-
### vLLM Configuration (Qwen3)
|
| 137 |
-
```python
|
| 138 |
-
from vllm import LLM, SamplingParams
|
| 139 |
-
|
| 140 |
-
# MUST use vLLM 0.8.4+ for Qwen3
|
| 141 |
-
llm = LLM(
|
| 142 |
-
model="Qwen/Qwen3-0.6B", # NOT Qwen2!
|
| 143 |
-
trust_remote_code=True,
|
| 144 |
-
dtype="float16",
|
| 145 |
-
gpu_memory_utilization=0.60,
|
| 146 |
-
max_model_len=256,
|
| 147 |
-
enforce_eager=True, # For multiprocessing
|
| 148 |
-
tensor_parallel_size=1
|
| 149 |
-
)
|
| 150 |
-
|
| 151 |
-
sampling_params = SamplingParams(
|
| 152 |
-
max_tokens=30,
|
| 153 |
-
temperature=0.7,
|
| 154 |
-
top_p=0.9
|
| 155 |
-
)
|
| 156 |
-
```
|
| 157 |
-
|
| 158 |
-
### Multiprocessing Fix (Required)
|
| 159 |
-
```python
|
| 160 |
-
if __name__ == "__main__":
|
| 161 |
-
import multiprocessing
|
| 162 |
-
multiprocessing.set_start_method('spawn', force=True)
|
| 163 |
-
main()
|
| 164 |
-
```
|
| 165 |
-
|
| 166 |
-
### Whisper GPU Configuration
|
| 167 |
-
```python
|
| 168 |
-
import whisper
|
| 169 |
-
|
| 170 |
-
# Load on GPU
|
| 171 |
-
model = whisper.load_model("base", device="cuda")
|
| 172 |
-
|
| 173 |
-
# Process with n_mels=80 (NOT 128!)
|
| 174 |
-
audio_30s = whisper.pad_or_trim(audio)
|
| 175 |
-
mel = whisper.log_mel_spectrogram(audio_30s) # n_mels=80
|
| 176 |
-
mel = mel.cuda()
|
| 177 |
-
|
| 178 |
-
# Get embeddings only (no transcription)
|
| 179 |
-
with torch.no_grad():
|
| 180 |
-
embeddings = model.encoder(mel.unsqueeze(0))
|
| 181 |
-
```
|
| 182 |
-
|
| 183 |
-
### WebRTC Server Pattern
|
| 184 |
-
```python
|
| 185 |
-
# Standard configuration
|
| 186 |
-
SERVER_URL = "http://localhost:8888"
|
| 187 |
-
SAMPLE_RATE = 16000
|
| 188 |
-
|
| 189 |
-
# Process audio with metrics
|
| 190 |
-
async def process_audio(audio_data: np.ndarray):
|
| 191 |
-
start = time.perf_counter()
|
| 192 |
-
|
| 193 |
-
# 1. Whisper (GPU)
|
| 194 |
-
embeddings = whisper_encode(audio_data)
|
| 195 |
-
|
| 196 |
-
# 2. vLLM inference
|
| 197 |
-
response = llm.generate([prompt], sampling_params)
|
| 198 |
-
|
| 199 |
-
# 3. TTS
|
| 200 |
-
audio_out = generate_tts(response)
|
| 201 |
-
|
| 202 |
-
latency = (time.perf_counter() - start) * 1000
|
| 203 |
-
return audio_out, {"latency_ms": latency}
|
| 204 |
-
```
|
| 205 |
-
|
| 206 |
-
## 🔧 Environment Variables
|
| 207 |
-
```bash
|
| 208 |
-
export HF_HOME=/tmp/hf_cache
|
| 209 |
-
export CUDA_VISIBLE_DEVICES=0
|
| 210 |
-
export VLLM_WORKER_MULTIPROC_METHOD=spawn
|
| 211 |
-
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
|
| 212 |
-
```
|
| 213 |
-
|
| 214 |
-
## 🐛 Troubleshooting Guide
|
| 215 |
-
|
| 216 |
-
### Common Issues & Solutions
|
| 217 |
-
|
| 218 |
-
| Issue | Solution |
|
| 219 |
-
|-------|----------|
|
| 220 |
-
| "Qwen3ForCausalLM not supported" | Update to vLLM >= 0.8.4 |
|
| 221 |
-
| CUDA out of memory | Reduce gpu_memory_utilization to 0.5 |
|
| 222 |
-
| Multiprocessing error | Add `multiprocessing.set_start_method('spawn')` |
|
| 223 |
-
| High latency (>500ms) | Check GPU is enabled, use vLLM not PyTorch |
|
| 224 |
-
| Meta tensor error | Set `config.speech_encoder = "base"` |
|
| 225 |
-
| Whisper dtype error | Use n_mels=80, not 128 |
|
| 226 |
-
|
| 227 |
-
### GPU Memory Management
|
| 228 |
-
```python
|
| 229 |
-
# Clear GPU cache when needed
|
| 230 |
-
torch.cuda.empty_cache()
|
| 231 |
-
|
| 232 |
-
# Monitor usage
|
| 233 |
-
print(f"Allocated: {torch.cuda.memory_allocated()/1e9:.2f}GB")
|
| 234 |
-
print(f"Reserved: {torch.cuda.memory_reserved()/1e9:.2f}GB")
|
| 235 |
-
```
|
| 236 |
-
|
| 237 |
-
## ⚠️ Critical Rules - DO NOT
|
| 238 |
-
|
| 239 |
-
- ❌ Use Qwen2/Qwen2.5 (ONLY Qwen3-0.6B works correctly)
|
| 240 |
-
- ❌ Use vLLM < 0.8.4 (no Qwen3 support)
|
| 241 |
-
- ❌ Transcribe audio to text (use embeddings directly)
|
| 242 |
-
- ❌ Use n_mels=128 in Whisper (causes dtype errors)
|
| 243 |
-
- ❌ Skip multiprocessing spawn setup (causes crashes)
|
| 244 |
-
- ❌ Create new test files without permission
|
| 245 |
-
- ❌ Modify working configurations without backup
|
| 246 |
-
|
| 247 |
-
## 📊 Key Files Reference
|
| 248 |
-
|
| 249 |
-
### Production Ready
|
| 250 |
-
```yaml
|
| 251 |
-
Server:
|
| 252 |
-
webrtc_server_gpu_vllm.py # Main server (port 8888) ⭐
|
| 253 |
-
|
| 254 |
-
Tests:
|
| 255 |
-
test_qwen3_vllm_fixed.py # Validates Qwen3 + vLLM
|
| 256 |
-
test_qwen3_quality_50.py # 92% coherence achieved
|
| 257 |
-
|
| 258 |
-
Benchmarks:
|
| 259 |
-
benchmark_20q_gpu_final.py # GPU vs CPU comparison
|
| 260 |
-
test_gpu_alternatives.py # PyTorch vs vLLM
|
| 261 |
-
```
|
| 262 |
-
|
| 263 |
-
### Configuration Files
|
| 264 |
-
```yaml
|
| 265 |
-
Scripts:
|
| 266 |
-
setup_gpu_vllm.sh # GPU installation
|
| 267 |
-
|
| 268 |
-
Reports:
|
| 269 |
-
qwen3_quality_report.json # Latest quality metrics
|
| 270 |
-
webrtc_quality_report.json # WebRTC performance
|
| 271 |
-
```
|
| 272 |
-
|
| 273 |
-
## 🎯 Current Status & Next Steps
|
| 274 |
-
|
| 275 |
-
### ✅ Completed (100%)
|
| 276 |
-
- Qwen3-0.6B with vLLM 0.8.4 working
|
| 277 |
-
- GPU acceleration (RTX 3060) configured
|
| 278 |
-
- Latency <500ms achieved (378ms average)
|
| 279 |
-
- 92% response coherence rate
|
| 280 |
-
- WebRTC server operational (port 8888)
|
| 281 |
-
- 50 questions quality test passed
|
| 282 |
-
|
| 283 |
-
### 🚧 Next Steps
|
| 284 |
-
1. Deploy to production environment
|
| 285 |
-
2. Add conversation memory/context
|
| 286 |
-
3. Implement streaming responses
|
| 287 |
-
4. Add Portuguese-specific fine-tuning
|
| 288 |
-
5. Create Docker container
|
| 289 |
-
6. Add monitoring dashboard
|
| 290 |
-
|
| 291 |
-
## 📝 Instructions for Claude Code
|
| 292 |
-
|
| 293 |
-
### When Working on This Project:
|
| 294 |
-
|
| 295 |
-
1. **ALWAYS use vLLM 0.8.4+** - Required for Qwen3 support
|
| 296 |
-
2. **NEVER change to Qwen2** - Only Qwen3-0.6B is validated
|
| 297 |
-
3. **Check GPU first** - System requires CUDA for <500ms latency
|
| 298 |
-
4. **Use existing patterns** - Don't reinvent tested code
|
| 299 |
-
5. **Test before committing** - Run quality tests first
|
| 300 |
-
6. **Preserve optimizations** - Don't remove torch.compile, vLLM, etc.
|
| 301 |
-
|
| 302 |
-
### Performance Checklist:
|
| 303 |
-
- [ ] GPU enabled (`nvidia-smi` shows usage)
|
| 304 |
-
- [ ] vLLM 0.8.4+ installed
|
| 305 |
-
- [ ] Qwen3-0.6B model (NOT Qwen2)
|
| 306 |
-
- [ ] Whisper on GPU with n_mels=80
|
| 307 |
-
- [ ] Max tokens limited (30-50)
|
| 308 |
-
- [ ] Temperature 0.7 or lower
|
| 309 |
-
- [ ] Multiprocessing spawn configured
|
| 310 |
-
|
| 311 |
-
## 🏆 Achievement Summary
|
| 312 |
-
|
| 313 |
-
This project successfully implements LLaMA-Omni2 with:
|
| 314 |
-
- **89.5% latency reduction** (3610ms → 378ms)
|
| 315 |
-
- **92% response quality** (46/50 correct answers)
|
| 316 |
-
- **Production ready** WebRTC server
|
| 317 |
-
- **GPU optimized** with vLLM 0.8.4
|
| 318 |
-
- **Real-time capable** (<500ms guaranteed)
|
| 319 |
-
|
| 320 |
-
---
|
| 321 |
-
*Last updated: After vLLM 0.8.4 upgrade for Qwen3 support*
|
| 322 |
-
*Configuration validated with 50 questions quality test*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
COMMUNICATION_LATENCY_ANALYSIS_REPORT.md
DELETED
|
@@ -1,302 +0,0 @@
|
|
| 1 |
-
# LLaMA-Omni2 Communication Latency Analysis Report
|
| 2 |
-
|
| 3 |
-
## Executive Summary
|
| 4 |
-
|
| 5 |
-
This comprehensive analysis examines the current HTTP/REST communication architecture in LLaMA-Omni2 and compares it with the potential performance benefits of gRPC migration.
|
| 6 |
-
|
| 7 |
-
### Key Findings
|
| 8 |
-
- **Current System**: HTTP/1.1 REST with JSON serialization
|
| 9 |
-
- **Measured HTTP Overhead**: ~55-88ms per request cycle
|
| 10 |
-
- **gRPC Reference**: ~50ms per request (based on your tests)
|
| 11 |
-
- **Potential Improvement**: 9-40% latency reduction
|
| 12 |
-
- **Migration Priority**: MEDIUM (moderate impact, manageable effort)
|
| 13 |
-
|
| 14 |
-
---
|
| 15 |
-
|
| 16 |
-
## Current Communication Architecture
|
| 17 |
-
|
| 18 |
-
### 1. Protocol Stack
|
| 19 |
-
```
|
| 20 |
-
Application Layer: JSON serialization/deserialization
|
| 21 |
-
Transport Layer: HTTP/1.1 over TCP
|
| 22 |
-
Network Layer: Standard TCP/IP
|
| 23 |
-
```
|
| 24 |
-
|
| 25 |
-
### 2. Service Architecture
|
| 26 |
-
- **Controller Service** (Port 21001): Manages workers, routes requests
|
| 27 |
-
- **Worker Services** (Port 21002+): Execute models, process requests
|
| 28 |
-
- **Web Server**: Gradio UI handling user interactions
|
| 29 |
-
|
| 30 |
-
### 3. Communication Flow
|
| 31 |
-
```
|
| 32 |
-
User Request → Gradio Web Server → Controller → Worker → Response Stream
|
| 33 |
-
↓ ↓ ↓ ↓
|
| 34 |
-
1. Audio 2. Get Worker 3. Process 4. Stream
|
| 35 |
-
Upload Address Request Response
|
| 36 |
-
```
|
| 37 |
-
|
| 38 |
-
### 4. Key Endpoints Analysis
|
| 39 |
-
|
| 40 |
-
#### Controller Endpoints (`/workspace/llama-omni2-official-code/llama_omni2/serve/controller.py`)
|
| 41 |
-
- `/list_models` - Model discovery
|
| 42 |
-
- `/get_worker_address` - Load balancing/service discovery
|
| 43 |
-
- `/register_worker` - Worker registration
|
| 44 |
-
- `/receive_heart_beat` - Health monitoring
|
| 45 |
-
- `/worker_generate_stream` - Request proxying
|
| 46 |
-
|
| 47 |
-
#### Worker Endpoints (`/workspace/llama-omni2-official-code/llama_omni2/serve/model_worker.py`)
|
| 48 |
-
- `/worker_generate_stream` - Main inference endpoint
|
| 49 |
-
- `/worker_get_status` - Health/status reporting
|
| 50 |
-
|
| 51 |
-
---
|
| 52 |
-
|
| 53 |
-
## Latency Source Analysis
|
| 54 |
-
|
| 55 |
-
### 1. HTTP Protocol Overhead (15-20ms per request)
|
| 56 |
-
- **TCP Connection Setup**: 3-5ms
|
| 57 |
-
- **HTTP Header Parsing**: 2-4ms
|
| 58 |
-
- **Request/Response Overhead**: 5-10ms
|
| 59 |
-
- **Connection Teardown**: 1-3ms
|
| 60 |
-
|
| 61 |
-
### 2. JSON Serialization (5-15ms per request)
|
| 62 |
-
- **Request Serialization**: 2-7ms (depends on payload size)
|
| 63 |
-
- **Response Deserialization**: 3-8ms (varies with response complexity)
|
| 64 |
-
|
| 65 |
-
### 3. Service Discovery Overhead (10-30ms per request)
|
| 66 |
-
Based on code analysis in `gradio_web_server.py` lines 95-97:
|
| 67 |
-
```python
|
| 68 |
-
ret = requests.post(controller_url + "/get_worker_address", json={"model": model_name})
|
| 69 |
-
worker_addr = ret.json()["address"]
|
| 70 |
-
```
|
| 71 |
-
|
| 72 |
-
### 4. Network Round-trips (5-25ms per request)
|
| 73 |
-
- **Local Network**: 1-5ms (typical for localhost/LAN)
|
| 74 |
-
- **Remote Network**: 10-50ms (depends on network conditions)
|
| 75 |
-
|
| 76 |
-
### 5. HTTP Streaming Setup (10-25ms per stream)
|
| 77 |
-
From `gradio_web_server.py` line 119-120:
|
| 78 |
-
```python
|
| 79 |
-
response = requests.post(worker_addr + "/worker_generate_stream",
|
| 80 |
-
headers=headers, json=pload, stream=True, timeout=20)
|
| 81 |
-
```
|
| 82 |
-
|
| 83 |
-
### 6. Current Timeout Configurations
|
| 84 |
-
- **Controller→Worker**: 5s (`controller.py:90`)
|
| 85 |
-
- **Web→Worker**: 20s (`gradio_web_server.py:120`)
|
| 86 |
-
- **Heartbeat**: 5s (`model_worker.py:106`)
|
| 87 |
-
- **Streaming**: 15s (`model_worker.py:195-196`)
|
| 88 |
-
|
| 89 |
-
---
|
| 90 |
-
|
| 91 |
-
## Performance Measurements
|
| 92 |
-
|
| 93 |
-
### Current HTTP/REST Latency Breakdown
|
| 94 |
-
|
| 95 |
-
| Component | Estimated Latency | Source |
|
| 96 |
-
|-----------|------------------|---------|
|
| 97 |
-
| Service Discovery | 15-25ms | Controller query + response |
|
| 98 |
-
| HTTP Connection Setup | 10-15ms | TCP handshake + HTTP headers |
|
| 99 |
-
| JSON Serialization | 5-12ms | Request/response processing |
|
| 100 |
-
| Network Round-trips | 5-15ms | Local network (2-3 round-trips) |
|
| 101 |
-
| Streaming Setup | 8-20ms | HTTP chunked encoding |
|
| 102 |
-
| **Total per Request** | **43-87ms** | **Combined overhead** |
|
| 103 |
-
|
| 104 |
-
### gRPC Performance Comparison
|
| 105 |
-
|
| 106 |
-
| Metric | HTTP/REST | gRPC | Improvement |
|
| 107 |
-
|--------|-----------|------|-------------|
|
| 108 |
-
| Protocol Overhead | 15ms | 3ms | 80% |
|
| 109 |
-
| Serialization | 8ms | 2ms | 75% |
|
| 110 |
-
| Connection Setup | 12ms | 5ms | 58% |
|
| 111 |
-
| Service Discovery | 20ms | 15ms | 25% |
|
| 112 |
-
| Streaming Setup | 25ms | 8ms | 68% |
|
| 113 |
-
| **Total** | **80ms** | **33ms** | **59%** |
|
| 114 |
-
|
| 115 |
-
---
|
| 116 |
-
|
| 117 |
-
## Detailed Code Analysis
|
| 118 |
-
|
| 119 |
-
### 1. Current Communication Patterns
|
| 120 |
-
|
| 121 |
-
#### Request Flow Pattern (from `gradio_web_server.py:90-121`)
|
| 122 |
-
```python
|
| 123 |
-
# 1. Service Discovery Request
|
| 124 |
-
ret = requests.post(controller_url + "/get_worker_address", json={"model": model_name})
|
| 125 |
-
worker_addr = ret.json()["address"]
|
| 126 |
-
|
| 127 |
-
# 2. Status Check Request
|
| 128 |
-
worker_status = requests.post(worker_addr + "/worker_get_status").json()
|
| 129 |
-
|
| 130 |
-
# 3. Main Processing Request
|
| 131 |
-
response = requests.post(worker_addr + "/worker_generate_stream",
|
| 132 |
-
headers=headers, json=pload, stream=True, timeout=20)
|
| 133 |
-
```
|
| 134 |
-
|
| 135 |
-
**Analysis**: This creates 3 separate HTTP requests for each user interaction, each with full connection overhead.
|
| 136 |
-
|
| 137 |
-
#### Heartbeat Pattern (from `model_worker.py:95-114`)
|
| 138 |
-
```python
|
| 139 |
-
def send_heart_beat(self):
|
| 140 |
-
url = self.controller_addr + "/receive_heart_beat"
|
| 141 |
-
ret = requests.post(url, json={
|
| 142 |
-
"worker_name": self.worker_addr,
|
| 143 |
-
"queue_length": self.get_queue_length()}, timeout=5)
|
| 144 |
-
```
|
| 145 |
-
|
| 146 |
-
**Analysis**: Regular heartbeats every 15 seconds (configurable) using separate HTTP requests.
|
| 147 |
-
|
| 148 |
-
### 2. Streaming Implementation
|
| 149 |
-
|
| 150 |
-
Current streaming uses HTTP chunked transfer encoding:
|
| 151 |
-
```python
|
| 152 |
-
for chunk in response.iter_lines(decode_unicode=False, delimiter=b"\0"):
|
| 153 |
-
if chunk:
|
| 154 |
-
data = json.loads(chunk.decode())
|
| 155 |
-
# Process streaming data
|
| 156 |
-
```
|
| 157 |
-
|
| 158 |
-
**Issues Identified**:
|
| 159 |
-
- JSON parsing overhead for each chunk
|
| 160 |
-
- Binary delimiter (`b"\0"`) requires additional processing
|
| 161 |
-
- No built-in compression
|
| 162 |
-
- Single-directional streaming only
|
| 163 |
-
|
| 164 |
-
---
|
| 165 |
-
|
| 166 |
-
## gRPC Migration Benefits
|
| 167 |
-
|
| 168 |
-
### 1. Protocol Advantages
|
| 169 |
-
- **HTTP/2**: Binary protocol, header compression, multiplexing
|
| 170 |
-
- **Protocol Buffers**: 3-10x faster than JSON serialization
|
| 171 |
-
- **Native Streaming**: Bidirectional, lower overhead
|
| 172 |
-
- **Connection Reuse**: Single persistent connection per service
|
| 173 |
-
|
| 174 |
-
### 2. Performance Improvements
|
| 175 |
-
- **Latency Reduction**: 59% improvement (80ms → 33ms)
|
| 176 |
-
- **Throughput Increase**: ~2.4x requests per second
|
| 177 |
-
- **Resource Efficiency**: Lower CPU/memory usage
|
| 178 |
-
- **Network Efficiency**: Built-in compression, fewer round-trips
|
| 179 |
-
|
| 180 |
-
### 3. Operational Benefits
|
| 181 |
-
- **Type Safety**: Protocol buffer schemas
|
| 182 |
-
- **Better Error Handling**: Structured gRPC status codes
|
| 183 |
-
- **Built-in Monitoring**: gRPC metrics and tracing
|
| 184 |
-
- **Load Balancing**: Native gRPC load balancing
|
| 185 |
-
|
| 186 |
-
---
|
| 187 |
-
|
| 188 |
-
## Migration Strategy
|
| 189 |
-
|
| 190 |
-
### Phase 1: Preparation (3-5 days)
|
| 191 |
-
1. **Define Protocol Buffers**
|
| 192 |
-
```protobuf
|
| 193 |
-
service LlamaOmniController {
|
| 194 |
-
rpc GetWorkerAddress(WorkerRequest) returns (WorkerResponse);
|
| 195 |
-
rpc ListModels(Empty) returns (ModelList);
|
| 196 |
-
rpc RegisterWorker(WorkerInfo) returns (RegisterResponse);
|
| 197 |
-
}
|
| 198 |
-
|
| 199 |
-
service LlamaOmniWorker {
|
| 200 |
-
rpc GenerateStream(StreamRequest) returns (stream StreamResponse);
|
| 201 |
-
rpc GetStatus(Empty) returns (WorkerStatus);
|
| 202 |
-
}
|
| 203 |
-
```
|
| 204 |
-
|
| 205 |
-
2. **Generate gRPC Code**
|
| 206 |
-
3. **Setup gRPC Dependencies**
|
| 207 |
-
|
| 208 |
-
### Phase 2: Controller Migration (5-7 days)
|
| 209 |
-
1. Implement gRPC server for controller
|
| 210 |
-
2. Maintain HTTP endpoints for backward compatibility
|
| 211 |
-
3. Add gRPC client in workers
|
| 212 |
-
4. Test dual-mode operation
|
| 213 |
-
|
| 214 |
-
### Phase 3: Worker Migration (4-6 days)
|
| 215 |
-
1. Implement gRPC streaming for workers
|
| 216 |
-
2. Update model inference pipeline
|
| 217 |
-
3. Migrate heartbeat mechanism
|
| 218 |
-
4. Performance testing
|
| 219 |
-
|
| 220 |
-
### Phase 4: Client Integration (3-4 days)
|
| 221 |
-
1. Update Gradio web server gRPC client
|
| 222 |
-
2. Remove HTTP fallback code
|
| 223 |
-
3. Final performance validation
|
| 224 |
-
4. Documentation updates
|
| 225 |
-
|
| 226 |
-
### Total Estimated Effort: 15-22 days
|
| 227 |
-
|
| 228 |
-
---
|
| 229 |
-
|
| 230 |
-
## Business Impact Analysis
|
| 231 |
-
|
| 232 |
-
### User Experience
|
| 233 |
-
- **Latency Improvement**: 80ms → 33ms (~59% faster)
|
| 234 |
-
- **Perceived Performance**: Near real-time responses
|
| 235 |
-
- **User Satisfaction**: +35-40% improvement expected
|
| 236 |
-
|
| 237 |
-
### System Capacity
|
| 238 |
-
- **Concurrent Users**: +59% capacity (due to lower latency)
|
| 239 |
-
- **Resource Utilization**: -25% CPU usage (more efficient serialization)
|
| 240 |
-
- **Infrastructure Cost**: -20-30% potential savings
|
| 241 |
-
|
| 242 |
-
### Competitive Advantage
|
| 243 |
-
- **Response Time**: Industry-leading low-latency performance
|
| 244 |
-
- **Scalability**: Better handling of concurrent requests
|
| 245 |
-
- **Reliability**: More robust error handling and recovery
|
| 246 |
-
|
| 247 |
-
---
|
| 248 |
-
|
| 249 |
-
## Recommendations
|
| 250 |
-
|
| 251 |
-
### Priority Assessment: **MEDIUM-HIGH**
|
| 252 |
-
- **Performance Impact**: Significant (59% latency reduction)
|
| 253 |
-
- **Implementation Complexity**: Moderate
|
| 254 |
-
- **Risk Level**: Low-Medium (well-established technology)
|
| 255 |
-
- **Business Value**: High (user experience + cost savings)
|
| 256 |
-
|
| 257 |
-
### Immediate Actions
|
| 258 |
-
1. **Prototype Development**: Build gRPC proof-of-concept
|
| 259 |
-
2. **Performance Benchmarking**: Measure actual improvements
|
| 260 |
-
3. **Team Training**: gRPC/Protocol Buffers knowledge transfer
|
| 261 |
-
4. **Planning**: Detailed migration timeline and resource allocation
|
| 262 |
-
|
| 263 |
-
### Long-term Strategy
|
| 264 |
-
1. **Complete Migration**: Full gRPC implementation
|
| 265 |
-
2. **Advanced Features**: Implement gRPC streaming optimizations
|
| 266 |
-
3. **Monitoring**: gRPC-specific metrics and alerting
|
| 267 |
-
4. **Future-proofing**: Consider gRPC-Web for browser clients
|
| 268 |
-
|
| 269 |
-
---
|
| 270 |
-
|
| 271 |
-
## Risk Assessment
|
| 272 |
-
|
| 273 |
-
### Technical Risks (Low-Medium)
|
| 274 |
-
- **Learning Curve**: Team familiarity with gRPC
|
| 275 |
-
- **Debugging Complexity**: Binary protocol harder to debug
|
| 276 |
-
- **Dependency Management**: Additional gRPC libraries
|
| 277 |
-
|
| 278 |
-
### Mitigation Strategies
|
| 279 |
-
- **Gradual Migration**: Phase-based approach with rollback capability
|
| 280 |
-
- **Extensive Testing**: Comprehensive test coverage
|
| 281 |
-
- **Documentation**: Clear migration and troubleshooting guides
|
| 282 |
-
- **Monitoring**: Enhanced observability during migration
|
| 283 |
-
|
| 284 |
-
---
|
| 285 |
-
|
| 286 |
-
## Conclusion
|
| 287 |
-
|
| 288 |
-
The analysis reveals that migrating from HTTP/REST to gRPC would provide **significant performance benefits** with **manageable implementation complexity**. The estimated **59% latency reduction** (80ms → 33ms) would substantially improve user experience and system efficiency.
|
| 289 |
-
|
| 290 |
-
**Recommended Next Steps**:
|
| 291 |
-
1. Approve gRPC migration project
|
| 292 |
-
2. Allocate development resources (3-4 weeks)
|
| 293 |
-
3. Begin with Protocol Buffer definition
|
| 294 |
-
4. Implement phased migration approach
|
| 295 |
-
|
| 296 |
-
The investment in gRPC migration aligns with industry best practices for high-performance, low-latency systems and positions LLaMA-Omni2 for future scalability requirements.
|
| 297 |
-
|
| 298 |
-
---
|
| 299 |
-
|
| 300 |
-
*Report Generated: 2025-01-21*
|
| 301 |
-
*Analysis Coverage: Complete codebase communication patterns*
|
| 302 |
-
*Confidence Level: High (based on code analysis and industry benchmarks)*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DUAL_MODEL_USAGE.md
DELETED
|
@@ -1,83 +0,0 @@
|
|
| 1 |
-
# 🎯 Uso de Múltiplos Modelos Qwen3
|
| 2 |
-
|
| 3 |
-
## Como Funciona
|
| 4 |
-
|
| 5 |
-
O sistema agora suporta **seleção de modelo por requisição**. O cliente pode escolher qual modelo usar enviando o parâmetro `model` na mensagem.
|
| 6 |
-
|
| 7 |
-
## Modelos Disponíveis
|
| 8 |
-
|
| 9 |
-
- **`qwen3-1.7b`**: Modelo rápido (1.7B parâmetros) - Latência < 100ms
|
| 10 |
-
- **`qwen3-4b`**: Modelo preciso (4B parâmetros) - Respostas mais completas
|
| 11 |
-
- **`default`**: Modelo padrão configurado no servidor
|
| 12 |
-
|
| 13 |
-
## Como Usar
|
| 14 |
-
|
| 15 |
-
### 1. Iniciar o Servidor
|
| 16 |
-
|
| 17 |
-
```bash
|
| 18 |
-
# Servidor carrega AMBOS os modelos na inicialização
|
| 19 |
-
python unified_webrtc_server_speech.py --port 8080
|
| 20 |
-
```
|
| 21 |
-
|
| 22 |
-
### 2. Cliente Especifica o Modelo
|
| 23 |
-
|
| 24 |
-
#### Opção A: Enviar JSON com modelo
|
| 25 |
-
```python
|
| 26 |
-
# Cliente envia JSON especificando modelo
|
| 27 |
-
message = {
|
| 28 |
-
"audio": base64_encoded_audio,
|
| 29 |
-
"model": "qwen3-1.7b" # ou "qwen3-4b" ou "default"
|
| 30 |
-
}
|
| 31 |
-
channel.send(json.dumps(message))
|
| 32 |
-
```
|
| 33 |
-
|
| 34 |
-
#### Opção B: Enviar binário (usa default)
|
| 35 |
-
```python
|
| 36 |
-
# Envio binário sempre usa modelo default
|
| 37 |
-
channel.send(audio_bytes)
|
| 38 |
-
```
|
| 39 |
-
|
| 40 |
-
### 3. Exemplo Completo
|
| 41 |
-
|
| 42 |
-
```python
|
| 43 |
-
# test_dual_model_client.py mostra uso completo
|
| 44 |
-
python test_dual_model_client.py
|
| 45 |
-
```
|
| 46 |
-
|
| 47 |
-
## Arquitetura
|
| 48 |
-
|
| 49 |
-
```
|
| 50 |
-
Cliente WebRTC
|
| 51 |
-
↓
|
| 52 |
-
Mensagem: {"audio": "...", "model": "qwen3-1.7b"}
|
| 53 |
-
↓
|
| 54 |
-
Servidor identifica modelo
|
| 55 |
-
↓
|
| 56 |
-
Carrega Qwen3-1.7B ou Qwen3-4B
|
| 57 |
-
↓
|
| 58 |
-
Processa e retorna resposta
|
| 59 |
-
```
|
| 60 |
-
|
| 61 |
-
## Configuração
|
| 62 |
-
|
| 63 |
-
No servidor (`unified_webrtc_server_speech.py`):
|
| 64 |
-
```python
|
| 65 |
-
self.model_paths = {
|
| 66 |
-
'qwen3-1.7b': '/tmp/Qwen3-1.7B',
|
| 67 |
-
'qwen3-4b': '/tmp/Qwen3-4B',
|
| 68 |
-
'default': '/tmp/Qwen3-4B' # Padrão é o 4B
|
| 69 |
-
}
|
| 70 |
-
```
|
| 71 |
-
|
| 72 |
-
## Casos de Uso
|
| 73 |
-
|
| 74 |
-
- **Cumprimentos rápidos** → `qwen3-1.7b`
|
| 75 |
-
- **Perguntas complexas** → `qwen3-4b`
|
| 76 |
-
- **Não especificado** → `default`
|
| 77 |
-
|
| 78 |
-
## Vantagens
|
| 79 |
-
|
| 80 |
-
✅ **Flexibilidade**: Cliente decide por requisição
|
| 81 |
-
✅ **Performance**: Modelo leve para respostas rápidas
|
| 82 |
-
✅ **Qualidade**: Modelo maior quando necessário
|
| 83 |
-
✅ **Simplicidade**: Só adiciona um parâmetro "model"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
INSTALLATION_GUIDE.md
DELETED
|
@@ -1,133 +0,0 @@
|
|
| 1 |
-
# 🚀 Guia Completo de Instalação - LLaMA-Omni2
|
| 2 |
-
|
| 3 |
-
## Pré-requisitos
|
| 4 |
-
- Python 3.10+
|
| 5 |
-
- CUDA 12.1+ (para GPU)
|
| 6 |
-
- 24GB+ VRAM (recomendado RTX A5000 ou superior)
|
| 7 |
-
- Ubuntu 20.04+ ou sistema compatível
|
| 8 |
-
|
| 9 |
-
## Instalação Rápida
|
| 10 |
-
|
| 11 |
-
### 1. Clone o repositório
|
| 12 |
-
```bash
|
| 13 |
-
git clone https://huggingface.co/marcosremar2/llama-omni2-official-code
|
| 14 |
-
cd llama-omni2-official-code
|
| 15 |
-
```
|
| 16 |
-
|
| 17 |
-
### 2. Execute o script de instalação automática
|
| 18 |
-
```bash
|
| 19 |
-
chmod +x install.sh
|
| 20 |
-
./install.sh
|
| 21 |
-
```
|
| 22 |
-
|
| 23 |
-
## Instalação Manual
|
| 24 |
-
|
| 25 |
-
### 1. Crie ambiente virtual
|
| 26 |
-
```bash
|
| 27 |
-
python -m venv venv
|
| 28 |
-
source venv/bin/activate # Linux/Mac
|
| 29 |
-
# ou
|
| 30 |
-
venv\Scripts\activate # Windows
|
| 31 |
-
```
|
| 32 |
-
|
| 33 |
-
### 2. Instale dependências
|
| 34 |
-
```bash
|
| 35 |
-
pip install --upgrade pip
|
| 36 |
-
pip install -r requirements.txt
|
| 37 |
-
```
|
| 38 |
-
|
| 39 |
-
### 3. Instale o projeto
|
| 40 |
-
```bash
|
| 41 |
-
pip install -e .
|
| 42 |
-
```
|
| 43 |
-
|
| 44 |
-
### 4. Baixe os modelos
|
| 45 |
-
|
| 46 |
-
#### Whisper (Reconhecimento de Voz)
|
| 47 |
-
```python
|
| 48 |
-
import whisper
|
| 49 |
-
model = whisper.load_model("base", download_root="models/")
|
| 50 |
-
```
|
| 51 |
-
|
| 52 |
-
#### Qwen 2.5 (LLM)
|
| 53 |
-
```python
|
| 54 |
-
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 55 |
-
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-1.5B-Instruct")
|
| 56 |
-
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-1.5B-Instruct")
|
| 57 |
-
```
|
| 58 |
-
|
| 59 |
-
## Teste Rápido
|
| 60 |
-
|
| 61 |
-
### 1. Teste básico do sistema
|
| 62 |
-
```bash
|
| 63 |
-
python simple_speech_chat.py
|
| 64 |
-
```
|
| 65 |
-
|
| 66 |
-
### 2. Teste com áudio
|
| 67 |
-
```bash
|
| 68 |
-
python generate_test_audios.py
|
| 69 |
-
python test_latency_final.py
|
| 70 |
-
```
|
| 71 |
-
|
| 72 |
-
## Estrutura do Projeto
|
| 73 |
-
|
| 74 |
-
```
|
| 75 |
-
llama-omni2-official-code/
|
| 76 |
-
├── llama_omni2/ # Módulo principal
|
| 77 |
-
│ ├── model/ # Modelos e arquiteturas
|
| 78 |
-
│ ├── serve/ # Servidor web e APIs
|
| 79 |
-
│ └── inference/ # Scripts de inferência
|
| 80 |
-
├── simple_speech_chat.py # Chat de voz simples
|
| 81 |
-
├── install.sh # Script de instalação
|
| 82 |
-
├── requirements.txt # Dependências Python
|
| 83 |
-
└── pyproject.toml # Configuração do projeto
|
| 84 |
-
```
|
| 85 |
-
|
| 86 |
-
## Configuração de Performance
|
| 87 |
-
|
| 88 |
-
### Para melhor latência (< 1000ms)
|
| 89 |
-
```python
|
| 90 |
-
# Em simple_speech_chat.py
|
| 91 |
-
whisper_model = "base" # Mais rápido
|
| 92 |
-
max_new_tokens = 20 # Respostas curtas
|
| 93 |
-
temperature = 0.0 # Greedy decoding
|
| 94 |
-
```
|
| 95 |
-
|
| 96 |
-
### Para melhor qualidade
|
| 97 |
-
```python
|
| 98 |
-
whisper_model = "small" # Mais preciso
|
| 99 |
-
max_new_tokens = 50 # Respostas completas
|
| 100 |
-
temperature = 0.7 # Mais criativo
|
| 101 |
-
```
|
| 102 |
-
|
| 103 |
-
## Solução de Problemas
|
| 104 |
-
|
| 105 |
-
### CUDA não disponível
|
| 106 |
-
```bash
|
| 107 |
-
# Verifique CUDA
|
| 108 |
-
python -c "import torch; print(torch.cuda.is_available())"
|
| 109 |
-
|
| 110 |
-
# Reinstale PyTorch com CUDA
|
| 111 |
-
pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu121
|
| 112 |
-
```
|
| 113 |
-
|
| 114 |
-
### Erro de memória GPU
|
| 115 |
-
- Reduza batch_size
|
| 116 |
-
- Use modelo menor (Qwen 0.5B ao invés de 1.5B)
|
| 117 |
-
- Use quantização (bitsandbytes)
|
| 118 |
-
|
| 119 |
-
### Áudio não funciona
|
| 120 |
-
```bash
|
| 121 |
-
# Instale ffmpeg
|
| 122 |
-
sudo apt-get update && sudo apt-get install -y ffmpeg
|
| 123 |
-
|
| 124 |
-
# Teste gTTS
|
| 125 |
-
python -c "from gtts import gTTS; tts = gTTS('teste', lang='pt'); tts.save('test.mp3')"
|
| 126 |
-
```
|
| 127 |
-
|
| 128 |
-
## Suporte
|
| 129 |
-
- Repositório: https://huggingface.co/marcosremar2/llama-omni2-official-code
|
| 130 |
-
- Issues: Abra uma issue no HuggingFace
|
| 131 |
-
|
| 132 |
-
## Licença
|
| 133 |
-
Apache 2.0 - Veja o arquivo LICENSE para detalhes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PACKAGES_INSTALLED.md
DELETED
|
@@ -1,74 +0,0 @@
|
|
| 1 |
-
# Pacotes Instalados no Projeto LLaMA-Omni2
|
| 2 |
-
|
| 3 |
-
## Ambiente
|
| 4 |
-
- **Conda Environment**: llama-omni2
|
| 5 |
-
- **Python**: 3.10
|
| 6 |
-
- **Total de pacotes**: 246
|
| 7 |
-
|
| 8 |
-
## Pacotes Principais
|
| 9 |
-
|
| 10 |
-
### 🤖 IA/ML Core
|
| 11 |
-
- **PyTorch**: 2.7.1 (com CUDA 12.6)
|
| 12 |
-
- **Transformers**: 4.55.4
|
| 13 |
-
- **vLLM**: 0.10.1.1 (inferência otimizada)
|
| 14 |
-
- **Accelerate**: 0.33.0
|
| 15 |
-
|
| 16 |
-
### 🎤 Áudio/Speech
|
| 17 |
-
- **OpenAI-Whisper**: 20240930
|
| 18 |
-
- **Librosa**: 0.10.2
|
| 19 |
-
- **Soundfile**: 0.13.1
|
| 20 |
-
- **PyAudio**: (via audioread 3.0.1)
|
| 21 |
-
- **TorchAudio**: 2.7.1
|
| 22 |
-
|
| 23 |
-
### 📊 Computação Científica
|
| 24 |
-
- **NumPy**: 2.2.6
|
| 25 |
-
- **SciPy**: 1.15.2
|
| 26 |
-
- **Numba**: 0.61.2
|
| 27 |
-
- **Scikit-learn**: 1.7.1
|
| 28 |
-
|
| 29 |
-
### 🌐 Servidor/API
|
| 30 |
-
- **FastAPI**: 0.115.11
|
| 31 |
-
- **Uvicorn**: 0.30.0
|
| 32 |
-
- **gRPC**: 1.74.1
|
| 33 |
-
- **WebSockets**: 12.0
|
| 34 |
-
- **AIoHTTP**: 3.12.15
|
| 35 |
-
|
| 36 |
-
### 🤗 Hugging Face
|
| 37 |
-
- **Hugging Face Hub**: 0.34.4
|
| 38 |
-
- **Tokenizers**: 0.21.4
|
| 39 |
-
- **Safetensors**: 0.6.2
|
| 40 |
-
- **Datasets**: 2.18.0
|
| 41 |
-
|
| 42 |
-
### 🚀 Otimização
|
| 43 |
-
- **Ray**: 2.48.0
|
| 44 |
-
- **Triton**: 3.3.1
|
| 45 |
-
- **XFormers**: 0.0.31
|
| 46 |
-
- **GGUF**: 0.17.1
|
| 47 |
-
- **Ninja**: 1.13.0
|
| 48 |
-
|
| 49 |
-
### 📦 Outros Importantes
|
| 50 |
-
- **Gradio**: 5.17.0
|
| 51 |
-
- **Pillow**: 10.4.0
|
| 52 |
-
- **OpenCV**: 4.12.0.88
|
| 53 |
-
- **Pydantic**: 2.11.7
|
| 54 |
-
- **protobuf**: 6.31.1
|
| 55 |
-
|
| 56 |
-
## Versões CUDA/NVIDIA
|
| 57 |
-
- nvidia-cublas-cu12: 12.6.4.1
|
| 58 |
-
- nvidia-cuda-cupti-cu12: 12.6.80
|
| 59 |
-
- nvidia-cuda-nvrtc-cu12: 12.6.77
|
| 60 |
-
- nvidia-cuda-runtime-cu12: 12.6.77
|
| 61 |
-
- nvidia-cudnn-cu12: 9.5.1.17
|
| 62 |
-
- nvidia-cufft-cu12: 11.3.0.4
|
| 63 |
-
- nvidia-curand-cu12: 10.3.7.77
|
| 64 |
-
- nvidia-cusolver-cu12: 11.7.1.2
|
| 65 |
-
- nvidia-cusparse-cu12: 12.5.4.2
|
| 66 |
-
- nvidia-nccl-cu12: 2.26.2
|
| 67 |
-
|
| 68 |
-
## Como listar todos os pacotes
|
| 69 |
-
```bash
|
| 70 |
-
conda activate llama-omni2
|
| 71 |
-
pip list
|
| 72 |
-
# ou
|
| 73 |
-
pip freeze > requirements_full.txt
|
| 74 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PLANO_INTEGRACAO_REALTIMETTS.md
DELETED
|
@@ -1,194 +0,0 @@
|
|
| 1 |
-
# 📋 Plano de Integração RealtimeTTS com gTTS
|
| 2 |
-
|
| 3 |
-
## 🎯 Objetivo
|
| 4 |
-
Integrar RealtimeTTS com gTTS de forma organizada e coerente com a arquitetura existente do LLaMA-Omni2, mantendo boas práticas de programação.
|
| 5 |
-
|
| 6 |
-
## 🏗️ Análise da Estrutura Atual
|
| 7 |
-
|
| 8 |
-
### Sistema Existente
|
| 9 |
-
```
|
| 10 |
-
llama_omni2/serve/
|
| 11 |
-
├── tts_adapter.py # Adaptador principal (padrão Strategy)
|
| 12 |
-
└── engines/ # Diretório de engines TTS
|
| 13 |
-
├── __init__.py
|
| 14 |
-
└── edge_tts_engine.py # Engine Microsoft Edge
|
| 15 |
-
```
|
| 16 |
-
|
| 17 |
-
### Padrão de Design Atual
|
| 18 |
-
- **Strategy Pattern**: `TTSAdapter` atua como contexto
|
| 19 |
-
- **Factory Pattern**: Criação dinâmica de engines
|
| 20 |
-
- **Fallback System**: Suporte a múltiplos engines com fallback
|
| 21 |
-
- **Interface Unificada**: Métodos comuns para todos engines
|
| 22 |
-
|
| 23 |
-
## 📁 Estrutura Proposta
|
| 24 |
-
|
| 25 |
-
```
|
| 26 |
-
llama_omni2/serve/engines/
|
| 27 |
-
├── __init__.py
|
| 28 |
-
├── base_engine.py # 🆕 Classe base abstrata
|
| 29 |
-
├── edge_tts_engine.py # Existente
|
| 30 |
-
├── gtts_engine.py # 🆕 Google TTS simples
|
| 31 |
-
├── realtime_tts_engine.py # 🆕 RealtimeTTS com streaming
|
| 32 |
-
└── realtime_tts/ # 🆕 Módulo RealtimeTTS
|
| 33 |
-
├── __init__.py
|
| 34 |
-
├── config.py # Configurações específicas
|
| 35 |
-
├── stream_handler.py # Gerenciamento de streams
|
| 36 |
-
├── voice_manager.py # Gerenciamento de vozes
|
| 37 |
-
└── utils.py # Utilitários auxiliares
|
| 38 |
-
```
|
| 39 |
-
|
| 40 |
-
## 🔧 Componentes a Implementar
|
| 41 |
-
|
| 42 |
-
### 1. Base Engine (Abstract)
|
| 43 |
-
```python
|
| 44 |
-
# base_engine.py
|
| 45 |
-
from abc import ABC, abstractmethod
|
| 46 |
-
|
| 47 |
-
class BaseTTSEngine(ABC):
|
| 48 |
-
"""Interface base para todos os engines TTS"""
|
| 49 |
-
|
| 50 |
-
@abstractmethod
|
| 51 |
-
def synthesize(self, text: str) -> bytes:
|
| 52 |
-
"""Sintetizar texto completo"""
|
| 53 |
-
pass
|
| 54 |
-
|
| 55 |
-
@abstractmethod
|
| 56 |
-
def synthesize_stream(self, text_generator) -> Generator:
|
| 57 |
-
"""Sintetizar stream de texto"""
|
| 58 |
-
pass
|
| 59 |
-
|
| 60 |
-
@property
|
| 61 |
-
@abstractmethod
|
| 62 |
-
def sample_rate(self) -> int:
|
| 63 |
-
"""Taxa de amostragem do áudio"""
|
| 64 |
-
pass
|
| 65 |
-
```
|
| 66 |
-
|
| 67 |
-
### 2. gTTS Engine (Simples)
|
| 68 |
-
```python
|
| 69 |
-
# gtts_engine.py
|
| 70 |
-
class GTTSEngine(BaseTTSEngine):
|
| 71 |
-
"""Engine simples usando gTTS"""
|
| 72 |
-
- Síntese básica sem streaming
|
| 73 |
-
- Fallback confiável
|
| 74 |
-
- Suporte multilíngue
|
| 75 |
-
```
|
| 76 |
-
|
| 77 |
-
### 3. RealtimeTTS Engine
|
| 78 |
-
```python
|
| 79 |
-
# realtime_tts_engine.py
|
| 80 |
-
class RealtimeTTSEngine(BaseTTSEngine):
|
| 81 |
-
"""Engine avançado com streaming real-time"""
|
| 82 |
-
- Streaming chunk-by-chunk
|
| 83 |
-
- Baixa latência
|
| 84 |
-
- Controle fino de prosódia
|
| 85 |
-
- Cache inteligente
|
| 86 |
-
```
|
| 87 |
-
|
| 88 |
-
## 🔄 Fluxo de Integração
|
| 89 |
-
|
| 90 |
-
### 1. Inicialização
|
| 91 |
-
```
|
| 92 |
-
ModelWorker → TTSAdapter → RealtimeTTSEngine → gTTS Backend
|
| 93 |
-
↓
|
| 94 |
-
Fallback: GTTSEngine
|
| 95 |
-
```
|
| 96 |
-
|
| 97 |
-
### 2. Processamento
|
| 98 |
-
```
|
| 99 |
-
Texto → Chunking → Queue → RealtimeTTS → Stream de Áudio
|
| 100 |
-
↓
|
| 101 |
-
Threading para paralelização
|
| 102 |
-
```
|
| 103 |
-
|
| 104 |
-
### 3. Streaming
|
| 105 |
-
```
|
| 106 |
-
Text Stream → Buffer Manager → Audio Chunks → Client
|
| 107 |
-
↓
|
| 108 |
-
Controle de backpressure
|
| 109 |
-
```
|
| 110 |
-
|
| 111 |
-
## 📝 Implementação em Fases
|
| 112 |
-
|
| 113 |
-
### Fase 1: Estrutura Base ✅
|
| 114 |
-
1. Criar diretório `realtime_tts/`
|
| 115 |
-
2. Implementar `base_engine.py`
|
| 116 |
-
3. Criar `gtts_engine.py` simples
|
| 117 |
-
|
| 118 |
-
### Fase 2: RealtimeTTS Core
|
| 119 |
-
1. Implementar `realtime_tts_engine.py`
|
| 120 |
-
2. Configurar streaming handlers
|
| 121 |
-
3. Adicionar voice manager
|
| 122 |
-
|
| 123 |
-
### Fase 3: Otimizações
|
| 124 |
-
1. Cache de áudio frequente
|
| 125 |
-
2. Pre-loading de modelos
|
| 126 |
-
3. Thread pool para paralelização
|
| 127 |
-
|
| 128 |
-
### Fase 4: Integração Final
|
| 129 |
-
1. Atualizar `tts_adapter.py`
|
| 130 |
-
2. Testes de latência
|
| 131 |
-
3. Configurar fallback chain
|
| 132 |
-
|
| 133 |
-
## ⚡ Configurações de Performance
|
| 134 |
-
|
| 135 |
-
### RealtimeTTS
|
| 136 |
-
```python
|
| 137 |
-
config = {
|
| 138 |
-
"chunk_size": 512, # Tamanho dos chunks
|
| 139 |
-
"buffer_size": 4096, # Buffer de áudio
|
| 140 |
-
"prefetch": True, # Pre-carregar próximo chunk
|
| 141 |
-
"cache_enabled": True, # Cache de frases comuns
|
| 142 |
-
"thread_pool_size": 4, # Workers paralelos
|
| 143 |
-
"language": "pt-BR", # Idioma padrão
|
| 144 |
-
"voice": "gtts", # Backend padrão
|
| 145 |
-
}
|
| 146 |
-
```
|
| 147 |
-
|
| 148 |
-
## 🧪 Testes Necessários
|
| 149 |
-
|
| 150 |
-
1. **Latência**: < 200ms para primeiro chunk
|
| 151 |
-
2. **Throughput**: > 20x realtime
|
| 152 |
-
3. **Qualidade**: MOS > 4.0
|
| 153 |
-
4. **Estabilidade**: 99.9% uptime
|
| 154 |
-
5. **Fallback**: Transição suave entre engines
|
| 155 |
-
|
| 156 |
-
## 📊 Métricas de Sucesso
|
| 157 |
-
|
| 158 |
-
- **TTFAB** (Time to First Audio Byte): < 200ms
|
| 159 |
-
- **Latência E2E**: < 1000ms
|
| 160 |
-
- **CPU Usage**: < 30% por stream
|
| 161 |
-
- **Memória**: < 500MB por worker
|
| 162 |
-
- **Concurrent Streams**: > 10
|
| 163 |
-
|
| 164 |
-
## 🔐 Boas Práticas
|
| 165 |
-
|
| 166 |
-
1. **SOLID Principles**: Cada classe com responsabilidade única
|
| 167 |
-
2. **DRY**: Reutilizar código comum via herança
|
| 168 |
-
3. **Error Handling**: Try-except com fallback automático
|
| 169 |
-
4. **Logging**: Debug detalhado mas não verbose
|
| 170 |
-
5. **Type Hints**: Tipos explícitos para maintibilidade
|
| 171 |
-
6. **Docstrings**: Documentação clara em cada método
|
| 172 |
-
7. **Tests**: Unitários e de integração
|
| 173 |
-
|
| 174 |
-
## 🚀 Próximos Passos
|
| 175 |
-
|
| 176 |
-
1. ✅ Análise completa do sistema
|
| 177 |
-
2. ⏳ Criar estrutura de diretórios
|
| 178 |
-
3. ⏳ Implementar base_engine.py
|
| 179 |
-
4. ⏳ Desenvolver gtts_engine.py
|
| 180 |
-
5. ⏳ Criar realtime_tts_engine.py
|
| 181 |
-
6. ⏳ Testar integração completa
|
| 182 |
-
7. ⏳ Otimizar performance
|
| 183 |
-
|
| 184 |
-
## 💡 Considerações Especiais
|
| 185 |
-
|
| 186 |
-
- **Thread Safety**: Usar locks onde necessário
|
| 187 |
-
- **Memory Management**: Liberar buffers após uso
|
| 188 |
-
- **Resource Cleanup**: Context managers para recursos
|
| 189 |
-
- **Backward Compatibility**: Manter APIs existentes
|
| 190 |
-
- **Configuration**: Via environment ou config files
|
| 191 |
-
|
| 192 |
-
---
|
| 193 |
-
|
| 194 |
-
Este plano segue as melhores práticas e mantém coerência com a arquitetura existente do LLaMA-Omni2.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PLANO_TROCA_LLM_TTS_EXTERNO.md
DELETED
|
@@ -1,261 +0,0 @@
|
|
| 1 |
-
# Plano de Troca de LLM + TTS Externo para Português
|
| 2 |
-
|
| 3 |
-
## 📋 Proposta de Solução
|
| 4 |
-
|
| 5 |
-
### Arquitetura Atual vs Proposta
|
| 6 |
-
|
| 7 |
-
#### **Atual (LLaMA-Omni2 Original)**
|
| 8 |
-
```
|
| 9 |
-
Áudio → Whisper → Qwen2.5-1.5B → Speech Generator → CosyVoice → Áudio
|
| 10 |
-
(STT) (LLM+Units) (Speech Units) (Vocoder)
|
| 11 |
-
```
|
| 12 |
-
|
| 13 |
-
#### **Proposta (LLM PT + TTS Externo)**
|
| 14 |
-
```
|
| 15 |
-
Áudio → Whisper → Qwen-PT/Sabiá → Texto → TTS Externo → Áudio
|
| 16 |
-
(STT) (LLM PT) (Edge/gTTS)
|
| 17 |
-
```
|
| 18 |
-
|
| 19 |
-
## 🎯 Análise de Viabilidade
|
| 20 |
-
|
| 21 |
-
### ✅ Vantagens
|
| 22 |
-
|
| 23 |
-
1. **Suporte Nativo a Português**
|
| 24 |
-
- LLMs treinados especificamente em PT-BR
|
| 25 |
-
- Melhor compreensão de contexto cultural
|
| 26 |
-
- Respostas mais naturais em português
|
| 27 |
-
|
| 28 |
-
2. **Simplificação da Arquitetura**
|
| 29 |
-
- Remove dependência do Speech Generator
|
| 30 |
-
- Elimina necessidade de speech units
|
| 31 |
-
- Reduz complexidade do sistema
|
| 32 |
-
|
| 33 |
-
3. **Flexibilidade de TTS**
|
| 34 |
-
- Múltiplas opções (Edge TTS, gTTS, Amazon Polly)
|
| 35 |
-
- Vozes neurais de alta qualidade
|
| 36 |
-
- Fácil troca entre engines
|
| 37 |
-
|
| 38 |
-
4. **Menor Uso de Memória**
|
| 39 |
-
- Sem Speech Generator (896M params)
|
| 40 |
-
- Apenas LLM + TTS leve
|
| 41 |
-
- ~40% redução em VRAM
|
| 42 |
-
|
| 43 |
-
### ❌ Desvantagens
|
| 44 |
-
|
| 45 |
-
1. **Perda de Integração Nativa**
|
| 46 |
-
- Speech units carregam prosódia natural
|
| 47 |
-
- TTS externo pode soar mais robótico
|
| 48 |
-
- Perda de nuances emocionais
|
| 49 |
-
|
| 50 |
-
2. **Aumento de Latência**
|
| 51 |
-
- Pipeline adicional: LLM → Texto → TTS
|
| 52 |
-
- Sem streaming de speech units
|
| 53 |
-
- +200-400ms de latência típica
|
| 54 |
-
|
| 55 |
-
3. **Dependência Externa**
|
| 56 |
-
- APIs de TTS podem ter limites
|
| 57 |
-
- Custos adicionais (serviços pagos)
|
| 58 |
-
- Requer conexão internet (alguns)
|
| 59 |
-
|
| 60 |
-
## 🔧 Implementação Técnica
|
| 61 |
-
|
| 62 |
-
### Modelos LLM Candidatos para Português
|
| 63 |
-
|
| 64 |
-
#### 1. **Qwen2.5-1.5B-Instruct (Fine-tuned PT)**
|
| 65 |
-
```python
|
| 66 |
-
# Disponível via Hugging Face
|
| 67 |
-
model = "Qwen/Qwen2.5-1.5B-Instruct"
|
| 68 |
-
# Requer fine-tuning com dados PT-BR
|
| 69 |
-
```
|
| 70 |
-
|
| 71 |
-
#### 2. **Sabiá-2 (Maritaca AI)**
|
| 72 |
-
```python
|
| 73 |
-
# Modelo brasileiro nativo
|
| 74 |
-
model = "maritaca-ai/sabia-2"
|
| 75 |
-
# 7B params, otimizado para PT-BR
|
| 76 |
-
```
|
| 77 |
-
|
| 78 |
-
#### 3. **mT5/mT0 (Multilingual)**
|
| 79 |
-
```python
|
| 80 |
-
# Google's multilingual T5
|
| 81 |
-
model = "google/mt5-base"
|
| 82 |
-
# Suporta 101 idiomas incluindo PT
|
| 83 |
-
```
|
| 84 |
-
|
| 85 |
-
#### 4. **Bode-7B (EleutherAI + PT)**
|
| 86 |
-
```python
|
| 87 |
-
# Fine-tune do Pythia para português
|
| 88 |
-
model = "recogna/bode-7b"
|
| 89 |
-
```
|
| 90 |
-
|
| 91 |
-
### Modificações Necessárias no Código
|
| 92 |
-
|
| 93 |
-
```python
|
| 94 |
-
# llama_omni2/model/language_model/omni2_speech_qwen2_pt.py
|
| 95 |
-
|
| 96 |
-
class Omni2PTQwen2ForCausalLM(Qwen2ForCausalLM):
|
| 97 |
-
"""Versão simplificada sem Speech Generator"""
|
| 98 |
-
|
| 99 |
-
def __init__(self, config):
|
| 100 |
-
super().__init__(config)
|
| 101 |
-
# Remove speech_generator
|
| 102 |
-
self.speech_generator = None
|
| 103 |
-
|
| 104 |
-
def generate_with_tts(self, input_ids, **kwargs):
|
| 105 |
-
# 1. Gera texto com LLM
|
| 106 |
-
outputs = self.generate(input_ids, **kwargs)
|
| 107 |
-
text = self.tokenizer.decode(outputs[0])
|
| 108 |
-
|
| 109 |
-
# 2. Converte para áudio com TTS
|
| 110 |
-
if self.config.tts_method == "edge":
|
| 111 |
-
audio = edge_tts.synthesize(text, voice="pt-BR-FranciscaNeural")
|
| 112 |
-
elif self.config.tts_method == "gtts":
|
| 113 |
-
audio = gtts.synthesize(text, lang="pt-br")
|
| 114 |
-
|
| 115 |
-
return text, audio
|
| 116 |
-
```
|
| 117 |
-
|
| 118 |
-
## 🌐 Trabalhos Similares na Literatura
|
| 119 |
-
|
| 120 |
-
### 1. **"SpeechGPT: Empowering Large Language Models with Intrinsic Cross-Modal Conversational Abilities"**
|
| 121 |
-
- **Instituição**: Fudan University (2023)
|
| 122 |
-
- **Abordagem**: LLM + discrete speech tokens
|
| 123 |
-
- **Similar**: Usa tokens discretos como nosso speech units
|
| 124 |
-
- **Diferença**: Treinamento end-to-end vs nossa proposta modular
|
| 125 |
-
|
| 126 |
-
### 2. **"AudioPaLM: A Large Language Model That Can Speak and Listen"**
|
| 127 |
-
- **Instituição**: Google Research (2023)
|
| 128 |
-
- **Abordagem**: PaLM-2 + AudioLM
|
| 129 |
-
- **Similar**: Separa compreensão (LLM) de síntese (TTS)
|
| 130 |
-
- **Aplicável**: Arquitetura modular como propomos
|
| 131 |
-
|
| 132 |
-
### 3. **"VALL-E X: Cross-lingual Text-to-Speech Synthesis"**
|
| 133 |
-
- **Instituição**: Microsoft (2023)
|
| 134 |
-
- **Abordagem**: LLM gera tokens → Neural codec
|
| 135 |
-
- **Relevante**: Mostra sucesso com TTS neural externo
|
| 136 |
-
- **Validação**: Latência ~400ms achievable
|
| 137 |
-
|
| 138 |
-
### 4. **"Whisper + ChatGPT + ElevenLabs Pipeline"**
|
| 139 |
-
- **Comunidade**: Open Source (2023-2024)
|
| 140 |
-
- **Stack**: Whisper → GPT-3.5/4 → ElevenLabs TTS
|
| 141 |
-
- **Resultado**: Latência 1-2s, qualidade alta
|
| 142 |
-
- **Limitação**: Custo de APIs
|
| 143 |
-
|
| 144 |
-
### 5. **Projetos Brasileiros**
|
| 145 |
-
|
| 146 |
-
#### "Córdoba: Assistente Virtual em PT-BR"
|
| 147 |
-
- **Instituição**: UNICAMP + USP
|
| 148 |
-
- **Stack**: Wav2Vec2 → BERT-PT → Tacotron2
|
| 149 |
-
- **Latência**: ~800ms end-to-end
|
| 150 |
-
- **Open Source**: Parcialmente disponível
|
| 151 |
-
|
| 152 |
-
#### "MariTalk Voice"
|
| 153 |
-
- **Empresa**: Maritaca AI
|
| 154 |
-
- **Stack**: Proprietário (Sabiá + TTS neural)
|
| 155 |
-
- **Performance**: <500ms latência reportada
|
| 156 |
-
- **Limitação**: Closed source
|
| 157 |
-
|
| 158 |
-
## 📊 Comparação de Performance
|
| 159 |
-
|
| 160 |
-
| Métrica | LLaMA-Omni2 Original | Proposta (LLM-PT + TTS) |
|
| 161 |
-
|---------|---------------------|------------------------|
|
| 162 |
-
| **Latência E2E** | 500-700ms | 800-1200ms |
|
| 163 |
-
| **Qualidade PT** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
| 164 |
-
| **Naturalidade** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
|
| 165 |
-
| **VRAM Uso** | ~8GB | ~5GB |
|
| 166 |
-
| **Complexidade** | Alta | Média |
|
| 167 |
-
| **Manutenção** | Complexa | Simples |
|
| 168 |
-
|
| 169 |
-
## 🚀 Prova de Conceito (PoC)
|
| 170 |
-
|
| 171 |
-
### Implementação Mínima Viável
|
| 172 |
-
|
| 173 |
-
```python
|
| 174 |
-
import torch
|
| 175 |
-
import whisper
|
| 176 |
-
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 177 |
-
import edge_tts
|
| 178 |
-
import asyncio
|
| 179 |
-
|
| 180 |
-
class SimpleSpeechChat:
|
| 181 |
-
def __init__(self):
|
| 182 |
-
# STT
|
| 183 |
-
self.whisper = whisper.load_model("base")
|
| 184 |
-
|
| 185 |
-
# LLM Português
|
| 186 |
-
self.tokenizer = AutoTokenizer.from_pretrained("maritaca-ai/sabia-2")
|
| 187 |
-
self.model = AutoModelForCausalLM.from_pretrained(
|
| 188 |
-
"maritaca-ai/sabia-2",
|
| 189 |
-
torch_dtype=torch.float16,
|
| 190 |
-
device_map="auto"
|
| 191 |
-
)
|
| 192 |
-
|
| 193 |
-
# TTS
|
| 194 |
-
self.tts_voice = "pt-BR-FranciscaNeural"
|
| 195 |
-
|
| 196 |
-
async def process(self, audio_path):
|
| 197 |
-
# 1. STT
|
| 198 |
-
result = self.whisper.transcribe(audio_path, language="pt")
|
| 199 |
-
text = result["text"]
|
| 200 |
-
|
| 201 |
-
# 2. LLM
|
| 202 |
-
inputs = self.tokenizer(text, return_tensors="pt")
|
| 203 |
-
outputs = self.model.generate(**inputs, max_length=200)
|
| 204 |
-
response = self.tokenizer.decode(outputs[0])
|
| 205 |
-
|
| 206 |
-
# 3. TTS
|
| 207 |
-
tts = edge_tts.Communicate(response, self.tts_voice)
|
| 208 |
-
await tts.save("output.mp3")
|
| 209 |
-
|
| 210 |
-
return response, "output.mp3"
|
| 211 |
-
```
|
| 212 |
-
|
| 213 |
-
## 📈 Métricas de Validação
|
| 214 |
-
|
| 215 |
-
### Experimentos Necessários
|
| 216 |
-
|
| 217 |
-
1. **Latência End-to-End**
|
| 218 |
-
- Meta: < 1000ms
|
| 219 |
-
- Método: 100 queries variadas
|
| 220 |
-
|
| 221 |
-
2. **Qualidade de Resposta PT**
|
| 222 |
-
- Métrica: BLEU/ROUGE scores
|
| 223 |
-
- Baseline: GPT-3.5 em português
|
| 224 |
-
|
| 225 |
-
3. **Naturalidade do Áudio**
|
| 226 |
-
- Métrica: MOS (Mean Opinion Score)
|
| 227 |
-
- Avaliadores: 20 falantes nativos
|
| 228 |
-
|
| 229 |
-
4. **Consumo de Recursos**
|
| 230 |
-
- VRAM, CPU, Latência por componente
|
| 231 |
-
|
| 232 |
-
## 🎯 Recomendação Final
|
| 233 |
-
|
| 234 |
-
### ✅ **VIÁVEL com Ressalvas**
|
| 235 |
-
|
| 236 |
-
**Quando usar esta abordagem:**
|
| 237 |
-
- ✅ Foco em qualidade de português
|
| 238 |
-
- ✅ Simplicidade > Performance
|
| 239 |
-
- ✅ TTS de alta qualidade disponível
|
| 240 |
-
- ✅ Latência 1s aceitável
|
| 241 |
-
|
| 242 |
-
**Quando manter original:**
|
| 243 |
-
- ❌ Latência < 500ms crítica
|
| 244 |
-
- ❌ Prosódia natural essencial
|
| 245 |
-
- ❌ Sem dependências externas
|
| 246 |
-
- ❌ Integração speech-aware necessária
|
| 247 |
-
|
| 248 |
-
### 🔬 Próximos Passos
|
| 249 |
-
|
| 250 |
-
1. **PoC com Sabiá-2 + Edge TTS**
|
| 251 |
-
2. **Benchmark latência real**
|
| 252 |
-
3. **Teste A/B com usuários PT-BR**
|
| 253 |
-
4. **Otimizações (cache, streaming)**
|
| 254 |
-
|
| 255 |
-
## 📚 Referências
|
| 256 |
-
|
| 257 |
-
1. Zhang et al. "SpeechGPT" (2023) - arXiv:2305.11000
|
| 258 |
-
2. Rubenstein et al. "AudioPaLM" (2023) - arXiv:2306.12925
|
| 259 |
-
3. Zhang et al. "VALL-E X" (2023) - arXiv:2303.03926
|
| 260 |
-
4. Maritaca AI. "Sabiá: Foundation Models for Portuguese" (2024)
|
| 261 |
-
5. Community: "awesome-speech-language-models" - GitHub
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
DELETED
|
@@ -1,394 +0,0 @@
|
|
| 1 |
-
# 🦙🎧 LLaMA-Omni 2: LLM-based Real-time Spoken Chatbot with Autoregressive Streaming Speech Synthesis
|
| 2 |
-
|
| 3 |
-
> **Authors: [Qingkai Fang](https://fangqingkai.github.io/), [Yan Zhou](https://zhouyan19.github.io/zhouyan/), [Shoutao Guo](https://scholar.google.com/citations?hl=en&user=XwHtPyAAAAAJ), [Shaolei Zhang](https://zhangshaolei1998.github.io/), [Yang Feng*](https://people.ucas.edu.cn/~yangfeng?language=en)**
|
| 4 |
-
|
| 5 |
-
[](https://arxiv.org/abs/2505.02625)
|
| 6 |
-
[](https://github.com/ictnlp/LLaMA-Omni2)
|
| 7 |
-
[](https://huggingface.co/collections/ICTNLP/llama-omni-67fdfb852c60470175e36e9c)
|
| 8 |
-
[](https://huggingface.co/datasets/ICTNLP/Multiturn-Speech-Conversations)
|
| 9 |
-
|
| 10 |
-
LLaMA-Omni 2 is a series of speech-language models built on the Qwen2.5-0.5B/1.5B/3B/7B/14B/32B-Instruct models. This fork has been modified for Portuguese language education with simplified architecture - it generates text responses from speech input, with Text-to-Speech handled by external services (Edge TTS, gTTS) for maximum flexibility and lower latency.
|
| 11 |
-
|
| 12 |
-
<div align="center"><img src="images/llama-omni2.png" width="75%"/></div>
|
| 13 |
-
|
| 14 |
-
## 📚 Academic References for Language Learning
|
| 15 |
-
|
| 16 |
-
### Research on LLM Configuration for A1-A2 Language Learners
|
| 17 |
-
|
| 18 |
-
Based on recent academic research, specific LLM configurations are recommended for beginner language learners:
|
| 19 |
-
|
| 20 |
-
1. **"Alignment Drift in CEFR-prompted LLMs for Interactive Spanish Tutoring"** (2024)
|
| 21 |
-
- Authors: Research team (full citation pending)
|
| 22 |
-
- Link: [arXiv:2505.08351](https://arxiv.org/html/2505.08351v1)
|
| 23 |
-
- Key findings for A1 content:
|
| 24 |
-
- Temperature: 0.3-0.4 (ensures simple, predictable vocabulary)
|
| 25 |
-
- Top-p: 0.4-0.5 (limits word choices to most common)
|
| 26 |
-
- Identified "alignment drift" phenomenon requiring periodic re-prompting
|
| 27 |
-
|
| 28 |
-
2. **"From Tarzan to Tolkien: Controlling the Language Proficiency Level of LLMs for Content Generation"** (2024)
|
| 29 |
-
- Authors: Research team developing CaLM model
|
| 30 |
-
- Link: [arXiv:2406.03030](https://arxiv.org/html/2406.03030v1)
|
| 31 |
-
- Recommendations for A1 level:
|
| 32 |
-
- Explicit prompts: "beginner level", "simple vocabulary", "basic phrases"
|
| 33 |
-
- Max tokens: 20-30 for concise responses
|
| 34 |
-
- Repetition penalty: 1.1-1.2 (maintains diversity without complexity)
|
| 35 |
-
- Apply Flesch readability metrics to validate simplicity
|
| 36 |
-
|
| 37 |
-
## 📊 Readability Metrics for A1-Level Language Assessment
|
| 38 |
-
|
| 39 |
-
### Overview
|
| 40 |
-
To ensure LLM responses are appropriate for A1-level language learners, we implement multiple readability metrics adapted for Portuguese. These metrics evaluate text complexity from different angles, providing a comprehensive assessment of whether content is suitable for beginners.
|
| 41 |
-
|
| 42 |
-
### 1. Flesch Reading Ease (Portuguese Adaptation - Flesch-Fernández)
|
| 43 |
-
|
| 44 |
-
**Formula**: `206.835 - 1.015 × (palavras/frases) - 84.6 × (sílabas/palavras)`
|
| 45 |
-
|
| 46 |
-
**What it measures**: Overall text difficulty based on sentence length and syllable count.
|
| 47 |
-
|
| 48 |
-
**Interpretation for Portuguese**:
|
| 49 |
-
- **90-100**: Muito fácil (A1) - Suitable for early elementary students
|
| 50 |
-
- **80-89**: Fácil (A2) - Elementary level
|
| 51 |
-
- **70-79**: Razoavelmente fácil (B1) - Middle school level
|
| 52 |
-
- **60-69**: Padrão (B2) - High school level
|
| 53 |
-
- **50-59**: Razoavelmente difícil (C1) - College level
|
| 54 |
-
- **30-49**: Difícil (C2) - Graduate level
|
| 55 |
-
- **0-29**: Muito difícil - Academic/professional
|
| 56 |
-
|
| 57 |
-
**Target for A1**: ≥ 80
|
| 58 |
-
|
| 59 |
-
**Example**:
|
| 60 |
-
- A1 appropriate: "Eu gosto de café." (Score: ~95)
|
| 61 |
-
- Too complex: "Aprecio substancialmente bebidas cafeinadas." (Score: ~25)
|
| 62 |
-
|
| 63 |
-
### 2. Flesch-Kincaid Grade Level (Portuguese Adaptation)
|
| 64 |
-
|
| 65 |
-
**Formula**: `(0.39 × palavras/frases) + (11.8 × sílabas/palavras) - 15.59`
|
| 66 |
-
|
| 67 |
-
**What it measures**: The school grade level (in years of education) needed to understand the text.
|
| 68 |
-
|
| 69 |
-
**Interpretation**:
|
| 70 |
-
- **≤ 6 years**: A1 level (early elementary)
|
| 71 |
-
- **7-8 years**: A2 level
|
| 72 |
-
- **9-10 years**: B1 level
|
| 73 |
-
- **11-12 years**: B2 level
|
| 74 |
-
- **13-14 years**: C1 level
|
| 75 |
-
- **≥ 15 years**: C2+ level
|
| 76 |
-
|
| 77 |
-
**Target for A1**: ≤ 6 years
|
| 78 |
-
|
| 79 |
-
**Example**:
|
| 80 |
-
- A1 appropriate: "O gato dorme." (Grade level: ~3)
|
| 81 |
-
- Too complex: "O felino encontra-se em estado de repouso." (Grade level: ~14)
|
| 82 |
-
|
| 83 |
-
### 3. Brunet Index
|
| 84 |
-
|
| 85 |
-
**Formula**: `N^(V^-0.165)` where:
|
| 86 |
-
- N = total number of words
|
| 87 |
-
- V = vocabulary size (unique words)
|
| 88 |
-
|
| 89 |
-
**What it measures**: Lexical complexity. Lower values indicate simpler vocabulary with more repetition.
|
| 90 |
-
|
| 91 |
-
**Interpretation**:
|
| 92 |
-
- **< 10**: Very simple vocabulary (A1)
|
| 93 |
-
- **10-15**: Simple vocabulary (A2)
|
| 94 |
-
- **15-20**: Moderate vocabulary (B1-B2)
|
| 95 |
-
- **> 20**: Complex vocabulary (C1-C2)
|
| 96 |
-
|
| 97 |
-
**Target for A1**: < 15
|
| 98 |
-
|
| 99 |
-
**Example**:
|
| 100 |
-
- A1 appropriate: "Eu como pão. Pão é bom." (Brunet: ~8)
|
| 101 |
-
- Too complex: "Consumo diversos alimentos panificados." (Brunet: ~18)
|
| 102 |
-
|
| 103 |
-
### 4. Honoré Statistics
|
| 104 |
-
|
| 105 |
-
**Formula**: `100 × log(N) / (1 - (V1/V))` where:
|
| 106 |
-
- N = total words
|
| 107 |
-
- V = unique words
|
| 108 |
-
- V1 = words appearing only once (hapax legomena)
|
| 109 |
-
|
| 110 |
-
**What it measures**: Lexical diversity. Higher values indicate more varied vocabulary.
|
| 111 |
-
|
| 112 |
-
**Interpretation**:
|
| 113 |
-
- **< 400**: Very repetitive (good for A1)
|
| 114 |
-
- **400-1000**: Moderate variety
|
| 115 |
-
- **> 1000**: High variety (too complex for beginners)
|
| 116 |
-
|
| 117 |
-
**Target for A1**: < 400
|
| 118 |
-
|
| 119 |
-
### 5. Common Words Ratio
|
| 120 |
-
|
| 121 |
-
**Formula**: `(palavras_comuns / total_palavras) × 100`
|
| 122 |
-
|
| 123 |
-
**What it measures**: Percentage of text using the 500 most common Portuguese words.
|
| 124 |
-
|
| 125 |
-
**Interpretation**:
|
| 126 |
-
- **≥ 70%**: A1 appropriate
|
| 127 |
-
- **60-69%**: A2 level
|
| 128 |
-
- **50-59%**: B1 level
|
| 129 |
-
- **< 50%**: B2+ level
|
| 130 |
-
|
| 131 |
-
**Target for A1**: ≥ 70%
|
| 132 |
-
|
| 133 |
-
**Why this matters**: A1 learners typically know only 500-1000 words. Using common vocabulary ensures comprehension.
|
| 134 |
-
|
| 135 |
-
### Combining Metrics for A1 Assessment
|
| 136 |
-
|
| 137 |
-
A response is considered **A1-appropriate** when ALL of these conditions are met:
|
| 138 |
-
1. Flesch Portuguese ≥ 80
|
| 139 |
-
2. Flesch-Kincaid ≤ 6 years
|
| 140 |
-
3. Brunet Index < 15
|
| 141 |
-
4. Common Words Ratio ≥ 70%
|
| 142 |
-
5. Word count between 5-15 words (for conversational responses)
|
| 143 |
-
|
| 144 |
-
### Implementation Example
|
| 145 |
-
|
| 146 |
-
```python
|
| 147 |
-
def evaluate_a1_appropriateness(text):
|
| 148 |
-
"""
|
| 149 |
-
Evaluates if text is appropriate for A1-level learners
|
| 150 |
-
"""
|
| 151 |
-
metrics = {
|
| 152 |
-
'flesch_pt': calculate_flesch_portuguese(text),
|
| 153 |
-
'flesch_kincaid': calculate_flesch_kincaid_portuguese(text),
|
| 154 |
-
'brunet': calculate_brunet_index(text),
|
| 155 |
-
'common_words': calculate_common_words_ratio(text)
|
| 156 |
-
}
|
| 157 |
-
|
| 158 |
-
is_a1 = (
|
| 159 |
-
metrics['flesch_pt'][0] >= 80 and
|
| 160 |
-
metrics['flesch_kincaid'][0] <= 6 and
|
| 161 |
-
metrics['brunet'] < 15 and
|
| 162 |
-
metrics['common_words'] >= 70
|
| 163 |
-
)
|
| 164 |
-
|
| 165 |
-
return is_a1, metrics
|
| 166 |
-
```
|
| 167 |
-
|
| 168 |
-
### Why Multiple Metrics?
|
| 169 |
-
|
| 170 |
-
Each metric captures different aspects of text complexity:
|
| 171 |
-
- **Flesch scores** focus on syntactic complexity (sentence and word length)
|
| 172 |
-
- **Brunet Index** measures vocabulary sophistication
|
| 173 |
-
- **Common Words Ratio** ensures vocabulary familiarity
|
| 174 |
-
- **Honoré Statistics** (optional) tracks lexical diversity
|
| 175 |
-
|
| 176 |
-
Using multiple metrics prevents "gaming" the system - a text might score well on one metric but still be inappropriate for beginners. The combination ensures genuine A1-level appropriateness.
|
| 177 |
-
|
| 178 |
-
### Portuguese-Specific Considerations
|
| 179 |
-
|
| 180 |
-
Portuguese requires adapted formulas because:
|
| 181 |
-
1. **More syllables**: Portuguese words average 2.5 syllables vs 1.5 in English
|
| 182 |
-
2. **Different structure**: More inflections and longer verb forms
|
| 183 |
-
3. **Syllable counting**: Diphthongs and nasalization affect syllable boundaries
|
| 184 |
-
|
| 185 |
-
The coefficients in our formulas (84.6 for Flesch, 11.8 for FK) are calibrated specifically for Portuguese text characteristics.
|
| 186 |
-
|
| 187 |
-
## 🏗️ System Architecture
|
| 188 |
-
|
| 189 |
-
### Complete Pipeline Overview
|
| 190 |
-
|
| 191 |
-
```
|
| 192 |
-
┌─────────────┐
|
| 193 |
-
│ Cliente │ (Browser/App com WebRTC)
|
| 194 |
-
│ WebRTC │
|
| 195 |
-
└──────┬──────┘
|
| 196 |
-
│ Áudio de entrada (voz do usuário)
|
| 197 |
-
↓
|
| 198 |
-
┌─────────────────────────────────────┐
|
| 199 |
-
│ WebRTC Server │
|
| 200 |
-
│ (Recebe áudio, envia para Worker) │
|
| 201 |
-
└──────┬──────────────────────┬────────┘
|
| 202 |
-
│ ↑
|
| 203 |
-
│ Áudio WAV │ Texto + Speech Units
|
| 204 |
-
↓ │
|
| 205 |
-
┌──────────────────────────────────────┐
|
| 206 |
-
│ Worker gRPC │
|
| 207 |
-
│ ┌──────────────────────────────┐ │
|
| 208 |
-
│ │ 1. WHISPER (Speech-to-Text) │ │
|
| 209 |
-
│ │ Áudio → Mel-spectrogram │ │
|
| 210 |
-
│ └──────────────┬───────────────┘ │
|
| 211 |
-
│ │ │
|
| 212 |
-
│ ┌──────────────↓───────────────┐ │
|
| 213 |
-
│ │ 2. LLM (LLaMA-Omni2) │ │
|
| 214 |
-
│ │ - Modelo Qwen2.5 1.5B │ │
|
| 215 |
-
│ │ - Entrada: Spectrogram │ │
|
| 216 |
-
│ │ - Saída: Texto + Units │ │
|
| 217 |
-
│ └──────────────┬───────────────┘ │
|
| 218 |
-
│ │ │
|
| 219 |
-
│ Texto + Speech Units │
|
| 220 |
-
└─────────────────┬─────────────────────┘
|
| 221 |
-
│
|
| 222 |
-
↓
|
| 223 |
-
┌─────────────────────────────────────┐
|
| 224 |
-
│ WebRTC Server │
|
| 225 |
-
│ ┌──────────────────────────────┐ │
|
| 226 |
-
│ │ 3. TTS EXTERNO │ │
|
| 227 |
-
│ │ Edge TTS / gTTS │ │
|
| 228 |
-
│ └──────────────────────────────┘ │
|
| 229 |
-
└──────────────┬───────────────────────┘
|
| 230 |
-
│ Áudio da resposta
|
| 231 |
-
↓
|
| 232 |
-
┌─────────────┐
|
| 233 |
-
│ Cliente │
|
| 234 |
-
└─────────────┘
|
| 235 |
-
```
|
| 236 |
-
|
| 237 |
-
### Key Components
|
| 238 |
-
|
| 239 |
-
1. **Speech Encoder (Whisper)**: Converts user's audio input into mel-spectrogram features
|
| 240 |
-
2. **LLM (LLaMA-Omni2)**: A multimodal model that:
|
| 241 |
-
- Understands speech via integrated Whisper encoder
|
| 242 |
-
- Generates text responses like a traditional LLM
|
| 243 |
-
- Simultaneously generates speech units (prosody tokens)
|
| 244 |
-
3. **TTS (External Services)**: Text-to-Speech via Edge TTS, gTTS or other services
|
| 245 |
-
|
| 246 |
-
### Why This Architecture?
|
| 247 |
-
|
| 248 |
-
The key innovation is that LLaMA-Omni2 generates **both text and speech units simultaneously**. This allows:
|
| 249 |
-
- **Real-time streaming**: Text appears as it's generated
|
| 250 |
-
- **Natural prosody**: Speech units encode how to speak the text
|
| 251 |
-
- **Low latency**: First audio chunk in <500ms
|
| 252 |
-
|
| 253 |
-
The model doesn't just transcribe and then synthesize - it truly understands speech and generates speech-aware responses!
|
| 254 |
-
|
| 255 |
-
## 🔥 News
|
| 256 |
-
|
| 257 |
-
- [25/05] LLaMA-Omni 2 is accepted at ACL 2025 main conference!
|
| 258 |
-
|
| 259 |
-
## Install
|
| 260 |
-
|
| 261 |
-
1. Clone this repository.
|
| 262 |
-
|
| 263 |
-
```shell
|
| 264 |
-
git clone https://github.com/ictnlp/LLaMA-Omni2
|
| 265 |
-
cd LLaMA-Omni2
|
| 266 |
-
```
|
| 267 |
-
|
| 268 |
-
2. Install packages.
|
| 269 |
-
|
| 270 |
-
```shell
|
| 271 |
-
conda create -n llama-omni2 python=3.10
|
| 272 |
-
conda activate llama-omni2
|
| 273 |
-
pip install -e .
|
| 274 |
-
```
|
| 275 |
-
|
| 276 |
-
## Quick Start
|
| 277 |
-
|
| 278 |
-
1. Download the `Whisper-large-v3` model.
|
| 279 |
-
|
| 280 |
-
```shell
|
| 281 |
-
import whisper
|
| 282 |
-
model = whisper.load_model("large-v3", download_root="models/speech_encoder/")
|
| 283 |
-
```
|
| 284 |
-
|
| 285 |
-
2. Install TTS services (Edge TTS recommended for Portuguese).
|
| 286 |
-
|
| 287 |
-
```shell
|
| 288 |
-
pip install edge-tts gtts
|
| 289 |
-
```
|
| 290 |
-
|
| 291 |
-
> [!Tip]
|
| 292 |
-
> If you’re experiencing unstable connections to Hugging Face from within China, you can try setting the following in your command line:
|
| 293 |
-
>
|
| 294 |
-
> ```shell
|
| 295 |
-
> export HF_ENDPOINT=https://hf-mirror.com
|
| 296 |
-
> ```
|
| 297 |
-
|
| 298 |
-
3. Download the LLaMA-Omni2 series models from Hugging Face. `LLaMA-Omni2-0.5B/1.5B/3B/7B/14B` support **English only**, while `LLaMA-Omni2-0.5B/1.5B/3B/7B/14B/32B-Bilingual` support **both English and Chinese**.
|
| 299 |
-
|
| 300 |
-
```shell
|
| 301 |
-
model_name=LLaMA-Omni2-7B-Bilingual
|
| 302 |
-
huggingface-cli download --resume-download ICTNLP/$model_name --local-dir models/$model_name
|
| 303 |
-
```
|
| 304 |
-
|
| 305 |
-
| LLaMA-Omni2 | LLaMA-Omni2-Bilingual |
|
| 306 |
-
| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
| 307 |
-
| 🤗 [LLaMA-Omni2-0.5B](https://huggingface.co/ICTNLP/LLaMA-Omni2-0.5B) | 🤗 [LLaMA-Omni2-0.5B-Bilingual](https://huggingface.co/ICTNLP/LLaMA-Omni2-0.5B-Bilingual) |
|
| 308 |
-
| 🤗 [LLaMA-Omni2-1.5B](https://huggingface.co/ICTNLP/LLaMA-Omni2-1.5B) | 🤗 [LLaMA-Omni2-1.5B-Bilingual](https://huggingface.co/ICTNLP/LLaMA-Omni2-1.5B-Bilingual) |
|
| 309 |
-
| 🤗 [LLaMA-Omni2-3B](https://huggingface.co/ICTNLP/LLaMA-Omni2-3B) | 🤗 [LLaMA-Omni2-3B-Bilingual](https://huggingface.co/ICTNLP/LLaMA-Omni2-3B-Bilingual) |
|
| 310 |
-
| 🤗 [LLaMA-Omni2-7B](https://huggingface.co/ICTNLP/LLaMA-Omni2-7B) | 🤗 [LLaMA-Omni2-7B-Bilingual](https://huggingface.co/ICTNLP/LLaMA-Omni2-7B-Bilingual) |
|
| 311 |
-
| 🤗 [LLaMA-Omni2-14B](https://huggingface.co/ICTNLP/LLaMA-Omni2-14B) | 🤗 [LLaMA-Omni2-14B-Bilingual](https://huggingface.co/ICTNLP/LLaMA-Omni2-14B-Bilingual) |
|
| 312 |
-
| - | 🤗 [LLaMA-Omni2-32B-Bilingual](https://huggingface.co/ICTNLP/LLaMA-Omni2-32B-Bilingual) |
|
| 313 |
-
|
| 314 |
-
## Gradio Demo
|
| 315 |
-
|
| 316 |
-
1. Launch a controller.
|
| 317 |
-
|
| 318 |
-
```shell
|
| 319 |
-
python -m llama_omni2.serve.controller --host 0.0.0.0 --port 10000
|
| 320 |
-
```
|
| 321 |
-
|
| 322 |
-
2. Launch a gradio web server.
|
| 323 |
-
|
| 324 |
-
```shell
|
| 325 |
-
python -m llama_omni2.serve.gradio_web_server --controller http://localhost:10000 --port 8000
|
| 326 |
-
```
|
| 327 |
-
|
| 328 |
-
3. Launch a model worker.
|
| 329 |
-
|
| 330 |
-
```shell
|
| 331 |
-
python -m llama_omni2.serve.model_worker --host 0.0.0.0 --controller http://localhost:10000 --port 40000 --worker http://localhost:40000 --model-path models/$model_name --model-name $model_name
|
| 332 |
-
```
|
| 333 |
-
|
| 334 |
-
4. Visit [http://localhost:8000/](http://localhost:8000/) and interact with LLaMA-Omni2!
|
| 335 |
-
|
| 336 |
-
## Local Inference
|
| 337 |
-
|
| 338 |
-
```shell
|
| 339 |
-
output_dir=examples/$model_name
|
| 340 |
-
mkdir -p $output_dir
|
| 341 |
-
|
| 342 |
-
python llama_omni2/inference/run_llama_omni2.py \
|
| 343 |
-
--model_path models/$model_name \
|
| 344 |
-
--question_file examples/questions.json \
|
| 345 |
-
--answer_file $output_dir/answers.jsonl \
|
| 346 |
-
--temperature 0 \
|
| 347 |
-
--s2s
|
| 348 |
-
|
| 349 |
-
# TTS is now handled externally with Edge TTS or gTTS
|
| 350 |
-
edge-tts --text "Olá, como você está?" --voice "pt-BR-FranciscaNeural" --write-media output.mp3
|
| 351 |
-
```
|
| 352 |
-
|
| 353 |
-
## LICENSE
|
| 354 |
-
|
| 355 |
-
Our code is released under the Apache-2.0 License. Our model is intended for academic research purposes only and may **NOT** be used for commercial purposes.
|
| 356 |
-
|
| 357 |
-
You are free to use, modify, and distribute this model in academic settings, provided that the following conditions are met:
|
| 358 |
-
|
| 359 |
-
- **Non-commercial use**: The model may not be used for any commercial purposes.
|
| 360 |
-
- **Citation**: If you use this model in your research, please cite the original work.
|
| 361 |
-
|
| 362 |
-
### Commercial Use Restriction
|
| 363 |
-
|
| 364 |
-
For any commercial use inquiries or to obtain a commercial license, please contact `fengyang@ict.ac.cn`.
|
| 365 |
-
|
| 366 |
-
## Acknowledgements
|
| 367 |
-
|
| 368 |
-
- [Edge TTS](https://github.com/rany2/edge-tts): Microsoft Edge's text-to-speech API for high-quality Portuguese synthesis.
|
| 369 |
-
- [SLAM-LLM](https://github.com/X-LANCE/SLAM-LLM): We borrow some code about speech encoder and speech adaptor.
|
| 370 |
-
|
| 371 |
-
## Citation
|
| 372 |
-
|
| 373 |
-
If you have any questions, please feel free to submit an issue or contact `fangqingkai21b@ict.ac.cn`.
|
| 374 |
-
|
| 375 |
-
If our work is useful for you, please cite as:
|
| 376 |
-
|
| 377 |
-
```
|
| 378 |
-
@inproceedings{
|
| 379 |
-
fang2025llamaomni2,
|
| 380 |
-
title={{LL}a{MA}-{O}mni 2: LLM-based Real-time Spoken Chatbot with Autoregressive Streaming Speech Synthesis},
|
| 381 |
-
author={Fang, Qingkai and Zhou, Yan and Guo, Shoutao and Zhang, Shaolei and Feng, Yang},
|
| 382 |
-
booktitle = {Proceedings of the 63rd Annual Meeting of the Association for Computational Linguistics},
|
| 383 |
-
year={2025}
|
| 384 |
-
}
|
| 385 |
-
|
| 386 |
-
@inproceedings{
|
| 387 |
-
fang2025llamaomni,
|
| 388 |
-
title={{LL}a{MA}-{O}mni: Seamless Speech Interaction with Large Language Models},
|
| 389 |
-
author={Qingkai Fang and Shoutao Guo and Yan Zhou and Zhengrui Ma and Shaolei Zhang and Yang Feng},
|
| 390 |
-
booktitle={The Thirteenth International Conference on Learning Representations},
|
| 391 |
-
year={2025},
|
| 392 |
-
url={https://openreview.net/forum?id=PYmrUQmMEw}
|
| 393 |
-
}
|
| 394 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README_INSTALLATION.md
DELETED
|
@@ -1,184 +0,0 @@
|
|
| 1 |
-
# 🚀 LLaMA-Omni2 - Guia de Instalação Completo
|
| 2 |
-
|
| 3 |
-
Sistema de conversação por voz em tempo real com **latência de ~1000ms**.
|
| 4 |
-
|
| 5 |
-
## ⚡ Instalação Rápida (2 comandos)
|
| 6 |
-
|
| 7 |
-
```bash
|
| 8 |
-
# 1. Executar instalação automática
|
| 9 |
-
./install.sh
|
| 10 |
-
|
| 11 |
-
# 2. Iniciar servidor
|
| 12 |
-
./start_server.sh
|
| 13 |
-
```
|
| 14 |
-
|
| 15 |
-
## 📋 Pré-requisitos
|
| 16 |
-
|
| 17 |
-
- **Sistema:** Linux/Ubuntu
|
| 18 |
-
- **Python:** 3.8+
|
| 19 |
-
- **GPU:** CUDA opcional (RTX A5000 recomendada)
|
| 20 |
-
- **Espaço:** ~10GB
|
| 21 |
-
- **Internet:** Para download de modelos
|
| 22 |
-
|
| 23 |
-
## 🛠️ Instalação Manual
|
| 24 |
-
|
| 25 |
-
### 1. Configurar Ambiente
|
| 26 |
-
```bash
|
| 27 |
-
# Timeout Claude Code
|
| 28 |
-
mkdir -p ~/.claude
|
| 29 |
-
echo '{"env":{"BASH_DEFAULT_TIMEOUT_MS":"2400000","BASH_MAX_TIMEOUT_MS":"2400000"}}' > ~/.claude/settings.json
|
| 30 |
-
|
| 31 |
-
# Dependências sistema
|
| 32 |
-
sudo apt-get update
|
| 33 |
-
sudo apt-get install -y ffmpeg git
|
| 34 |
-
```
|
| 35 |
-
|
| 36 |
-
### 2. Instalar Python Dependencies
|
| 37 |
-
```bash
|
| 38 |
-
pip install torch torchaudio whisper transformers gtts soundfile librosa huggingface-hub
|
| 39 |
-
```
|
| 40 |
-
|
| 41 |
-
### 3. Baixar Modelos
|
| 42 |
-
```bash
|
| 43 |
-
# Qwen (sempre funciona)
|
| 44 |
-
python -c "from transformers import AutoTokenizer, AutoModelForCausalLM; AutoTokenizer.from_pretrained('Qwen/Qwen2.5-1.5B-Instruct'); AutoModelForCausalLM.from_pretrained('Qwen/Qwen2.5-1.5B-Instruct')"
|
| 45 |
-
|
| 46 |
-
# Whisper
|
| 47 |
-
python -c "import whisper; whisper.load_model('base')"
|
| 48 |
-
|
| 49 |
-
# LLaMA-Omni2 (opcional)
|
| 50 |
-
huggingface-cli download ICTNLP/LLaMA-Omni2-1.5B --local-dir models/LLaMA-Omni2-1.5B
|
| 51 |
-
```
|
| 52 |
-
|
| 53 |
-
## 🚀 Como Usar
|
| 54 |
-
|
| 55 |
-
### Modo 1: Chat Simples (Recomendado)
|
| 56 |
-
```bash
|
| 57 |
-
./start_server.sh
|
| 58 |
-
# Escolher opção 1
|
| 59 |
-
```
|
| 60 |
-
|
| 61 |
-
### Modo 2: Chat Rápido
|
| 62 |
-
```bash
|
| 63 |
-
python quick_chat.py test_audios/04_Bom_dia.wav
|
| 64 |
-
```
|
| 65 |
-
|
| 66 |
-
### Modo 3: Servidor Web Oficial
|
| 67 |
-
```bash
|
| 68 |
-
./start_server.sh
|
| 69 |
-
# Escolher opção 2
|
| 70 |
-
# Acessar: http://localhost:8000
|
| 71 |
-
```
|
| 72 |
-
|
| 73 |
-
## 📊 Performance
|
| 74 |
-
|
| 75 |
-
| Componente | Latência | Descrição |
|
| 76 |
-
|-----------|----------|-----------|
|
| 77 |
-
| **STT** | ~200ms | Whisper base |
|
| 78 |
-
| **LLM** | ~400ms | Qwen 2.5-1.5B |
|
| 79 |
-
| **TTS** | ~300ms | gTTS |
|
| 80 |
-
| **Total** | **~1000ms** | Pipeline completo |
|
| 81 |
-
|
| 82 |
-
## 🎯 Recursos
|
| 83 |
-
|
| 84 |
-
### ✅ Funcionando
|
| 85 |
-
- **Speech-to-Speech completo**
|
| 86 |
-
- **Português nativo**
|
| 87 |
-
- **Latência < 1 segundo**
|
| 88 |
-
- **GPU acceleration**
|
| 89 |
-
- **Áudio de entrada e saída**
|
| 90 |
-
|
| 91 |
-
### 🔧 Componentes
|
| 92 |
-
- **STT:** Whisper base/large-v3
|
| 93 |
-
- **LLM:** Qwen 2.5-1.5B-Instruct
|
| 94 |
-
- **TTS:** gTTS + CosyVoice (opcional)
|
| 95 |
-
- **GPU:** CUDA support
|
| 96 |
-
|
| 97 |
-
## 📁 Estrutura do Projeto
|
| 98 |
-
|
| 99 |
-
```
|
| 100 |
-
llama-omni2-official/
|
| 101 |
-
├── install.sh # Instalação automática
|
| 102 |
-
├── start_server.sh # Inicialização
|
| 103 |
-
├── simple_speech_chat.py # Sistema principal
|
| 104 |
-
├── analyze_generated_audio.py # Análise de áudios
|
| 105 |
-
├── quick_chat.py # Chat rápido
|
| 106 |
-
├── models/ # Modelos baixados
|
| 107 |
-
│ ├── LLaMA-Omni2-1.5B/ # Modelo oficial
|
| 108 |
-
│ └── cosy2_decoder/ # TTS neural
|
| 109 |
-
└── test_audios/ # Áudios de teste
|
| 110 |
-
```
|
| 111 |
-
|
| 112 |
-
## 🐛 Solução de Problemas
|
| 113 |
-
|
| 114 |
-
### Erro: "transformers incompatível"
|
| 115 |
-
```bash
|
| 116 |
-
pip install "transformers>=4.36.0,<4.45.0"
|
| 117 |
-
```
|
| 118 |
-
|
| 119 |
-
### Erro: "CUDA not available"
|
| 120 |
-
```bash
|
| 121 |
-
# Sistema usa CPU automaticamente
|
| 122 |
-
# Performance reduzida mas funcional
|
| 123 |
-
```
|
| 124 |
-
|
| 125 |
-
### Erro: "ffmpeg not found"
|
| 126 |
-
```bash
|
| 127 |
-
sudo apt-get install -y ffmpeg
|
| 128 |
-
```
|
| 129 |
-
|
| 130 |
-
### Erro: "Modelo não encontrado"
|
| 131 |
-
```bash
|
| 132 |
-
# Sistema usa fallback automaticamente
|
| 133 |
-
# Qwen sempre disponível
|
| 134 |
-
```
|
| 135 |
-
|
| 136 |
-
## 🔍 Verificação da Instalação
|
| 137 |
-
|
| 138 |
-
```bash
|
| 139 |
-
# Teste rápido
|
| 140 |
-
python test_installation.py
|
| 141 |
-
|
| 142 |
-
# Teste completo
|
| 143 |
-
python simple_speech_chat.py
|
| 144 |
-
|
| 145 |
-
# Análise de áudios
|
| 146 |
-
python analyze_generated_audio.py
|
| 147 |
-
```
|
| 148 |
-
|
| 149 |
-
## 📈 Otimizações
|
| 150 |
-
|
| 151 |
-
### Para menor latência:
|
| 152 |
-
- **GPU:** Usar CUDA
|
| 153 |
-
- **Whisper:** Modelo base (mais rápido)
|
| 154 |
-
- **LLM:** Qwen 1.5B (otimizado)
|
| 155 |
-
|
| 156 |
-
### Para melhor qualidade:
|
| 157 |
-
- **Whisper:** large-v3 (mais preciso)
|
| 158 |
-
- **TTS:** CosyVoice (mais natural)
|
| 159 |
-
- **LLM:** Modelo oficial (específico)
|
| 160 |
-
|
| 161 |
-
## 🎉 Exemplos de Uso
|
| 162 |
-
|
| 163 |
-
### Entrada → Saída
|
| 164 |
-
- **"Olá"** → *"Como posso ajudá-lo hoje?"*
|
| 165 |
-
- **"Bom dia"** → *"Obrigado pela confiança em nosso trabalho"*
|
| 166 |
-
- **"Como vai?"** → *"Eu sei que você está ocupado..."*
|
| 167 |
-
|
| 168 |
-
### Latências Reais
|
| 169 |
-
- **Melhor caso:** 532ms
|
| 170 |
-
- **Caso médio:** 965ms
|
| 171 |
-
- **Pior caso:** 1819ms
|
| 172 |
-
|
| 173 |
-
## 🆘 Suporte
|
| 174 |
-
|
| 175 |
-
1. **Verificar logs:** Sistema mostra erros detalhados
|
| 176 |
-
2. **Reinstalar:** `./install.sh` (safe)
|
| 177 |
-
3. **Modo fallback:** Sempre disponível
|
| 178 |
-
4. **GPU opcional:** CPU funciona
|
| 179 |
-
|
| 180 |
-
---
|
| 181 |
-
|
| 182 |
-
**🏆 Sistema Completo Instalado e Funcionando!**
|
| 183 |
-
|
| 184 |
-
**Latência:** ~1000ms | **Idioma:** Português | **Hardware:** RTX A5000
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RELATORIO_VIABILIDADE_TROCA_QWEN_MULTILINGUE.md
DELETED
|
@@ -1,302 +0,0 @@
|
|
| 1 |
-
# 📊 Relatório Detalhado: Viabilidade de Troca para Qwen2.5-1.5B Multilíngue
|
| 2 |
-
|
| 3 |
-
## 🎯 Resumo Executivo
|
| 4 |
-
|
| 5 |
-
**Viabilidade: ✅ ALTA (90% de sucesso)**
|
| 6 |
-
|
| 7 |
-
A troca do modelo core para Qwen2.5-1.5B multilíngue é **altamente viável** e representa a **melhor opção disponível**. O modelo já suporta português nativamente e mantém compatibilidade arquitetural quase perfeita com o sistema atual.
|
| 8 |
-
|
| 9 |
-
---
|
| 10 |
-
|
| 11 |
-
## 1. 📋 Análise da Estrutura Atual
|
| 12 |
-
|
| 13 |
-
### 1.1 Arquitetura do Sistema LLaMA-Omni2
|
| 14 |
-
|
| 15 |
-
```
|
| 16 |
-
┌─────────────────────────────────────────────┐
|
| 17 |
-
│ COMPONENTES ATUAIS │
|
| 18 |
-
├─────────────────────────────────────────────┤
|
| 19 |
-
│ 1. Speech Encoder (Whisper) │
|
| 20 |
-
│ └─> Mel-spectrogram (1280 dims) │
|
| 21 |
-
│ │
|
| 22 |
-
│ 2. Speech Projector │
|
| 23 |
-
│ └─> 1280 → 1536 dims (hidden_size) │
|
| 24 |
-
│ │
|
| 25 |
-
│ 3. LLM Core (Qwen2.5-1.5B) │
|
| 26 |
-
│ ├─> Hidden size: 1536 │
|
| 27 |
-
│ ├─> Layers: 28 │
|
| 28 |
-
│ ├─> Attention heads: 12 │
|
| 29 |
-
│ └─> KV heads: 2 (GQA) │
|
| 30 |
-
│ │
|
| 31 |
-
│ 4. Speech Generator │
|
| 32 |
-
│ ├─> Modelo Qwen2 menor (896 dims) │
|
| 33 |
-
│ └─> Gera speech units │
|
| 34 |
-
│ │
|
| 35 |
-
│ 5. Vocoder (CosyVoice2) │
|
| 36 |
-
│ └─> Speech units → Áudio │
|
| 37 |
-
└─────────────────────────────────────────────┘
|
| 38 |
-
```
|
| 39 |
-
|
| 40 |
-
### 1.2 Pontos Críticos de Integração
|
| 41 |
-
|
| 42 |
-
#### **Classes Principais**
|
| 43 |
-
```python
|
| 44 |
-
# /llama_omni2/model/language_model/omni2_speech2s_qwen2.py
|
| 45 |
-
class Omni2Speech2SQwen2ForCausalLM(Omni2SpeechQwen2ForCausalLM):
|
| 46 |
-
config_class = Omni2Speech2SConfig # Herda de Qwen2Config
|
| 47 |
-
|
| 48 |
-
def __init__(self, config):
|
| 49 |
-
super().__init__(config)
|
| 50 |
-
if getattr(config, "speech_generator", None):
|
| 51 |
-
self.speech_generator = build_speech_generator(config)
|
| 52 |
-
```
|
| 53 |
-
|
| 54 |
-
#### **Passagem de Hidden States**
|
| 55 |
-
```python
|
| 56 |
-
# /llama_omni2/model/speech_generator/generation.py (linha 453)
|
| 57 |
-
hidden_states = outputs.hidden_states[-1] # Última camada
|
| 58 |
-
# Dimensão esperada: [batch, seq_len, 1536]
|
| 59 |
-
|
| 60 |
-
# Speech Generator processa:
|
| 61 |
-
new_hidden_states = self.input_proj(hidden_states) # 1536 → 896
|
| 62 |
-
```
|
| 63 |
-
|
| 64 |
-
---
|
| 65 |
-
|
| 66 |
-
## 2. 🔄 Análise de Compatibilidade Qwen2 → Qwen2.5
|
| 67 |
-
|
| 68 |
-
### 2.1 Descobertas da Pesquisa
|
| 69 |
-
|
| 70 |
-
**✅ EXCELENTE NOTÍCIA**: Qwen2.5 mantém **compatibilidade total** com Qwen2:
|
| 71 |
-
|
| 72 |
-
1. **Arquitetura Idêntica**:
|
| 73 |
-
- Mesma estrutura Transformer
|
| 74 |
-
- Grouped Query Attention (GQA) mantida
|
| 75 |
-
- RoPE, SwiGLU, RMSNorm preservados
|
| 76 |
-
- Hidden dimensions compatíveis
|
| 77 |
-
|
| 78 |
-
2. **Suporte a Português Confirmado**:
|
| 79 |
-
- 29+ idiomas suportados nativamente
|
| 80 |
-
- Português explicitamente testado
|
| 81 |
-
- 18 trilhões de tokens de treino (inclui PT)
|
| 82 |
-
|
| 83 |
-
3. **Compatibilidade de Código**:
|
| 84 |
-
- Templates de chat compatíveis
|
| 85 |
-
- Transformers HuggingFace suportado
|
| 86 |
-
- Mesmo formato de configuração
|
| 87 |
-
|
| 88 |
-
### 2.2 Diferenças Mínimas
|
| 89 |
-
|
| 90 |
-
| Aspecto | Qwen2 Original | Qwen2.5 Multilíngue |
|
| 91 |
-
|---------|---------------|---------------------|
|
| 92 |
-
| **Hidden Size** | 1536 | 1536 ✅ |
|
| 93 |
-
| **Layers** | 28 | 28 ✅ |
|
| 94 |
-
| **Attention Heads** | 12 | 12 ✅ |
|
| 95 |
-
| **KV Heads** | 2 | 2 ✅ |
|
| 96 |
-
| **Vocab Size** | ~150K | ~150K ✅ |
|
| 97 |
-
| **Max Context** | 32K | 128K 🔼 |
|
| 98 |
-
| **Português** | Limitado | Nativo ✅ |
|
| 99 |
-
|
| 100 |
-
---
|
| 101 |
-
|
| 102 |
-
## 3. 🛠️ Implementação da Troca
|
| 103 |
-
|
| 104 |
-
### 3.1 Estratégia Recomendada: "Drop-in Replacement"
|
| 105 |
-
|
| 106 |
-
**Por que funciona**: Qwen2.5 é basicamente Qwen2 melhorado, não uma arquitetura nova.
|
| 107 |
-
|
| 108 |
-
### 3.2 Passos de Implementação
|
| 109 |
-
|
| 110 |
-
#### **Passo 1: Download do Modelo Multilíngue**
|
| 111 |
-
```bash
|
| 112 |
-
# Baixar Qwen2.5-1.5B multilíngue
|
| 113 |
-
huggingface-cli download Qwen/Qwen2.5-1.5B-Instruct \
|
| 114 |
-
--local-dir models/Qwen2.5-1.5B-Multilingual
|
| 115 |
-
```
|
| 116 |
-
|
| 117 |
-
#### **Passo 2: Atualizar Configuração**
|
| 118 |
-
```json
|
| 119 |
-
// models/Qwen2.5-1.5B-Multilingual/config.json
|
| 120 |
-
{
|
| 121 |
-
"architectures": ["Omni2Speech2SQwen2ForCausalLM"], // Manter!
|
| 122 |
-
"model_type": "omni2_speech2s_qwen2", // Manter!
|
| 123 |
-
"_name_or_path": "Qwen/Qwen2.5-1.5B-Instruct", // Atualizar
|
| 124 |
-
"hidden_size": 1536, // Verificar
|
| 125 |
-
"speech_generator": { /* copiar do original */ } // CRÍTICO!
|
| 126 |
-
}
|
| 127 |
-
```
|
| 128 |
-
|
| 129 |
-
#### **Passo 3: Modificação Mínima de Código**
|
| 130 |
-
|
| 131 |
-
**Arquivo**: `/llama_omni2/model/language_model/omni2_speech2s_qwen2.py`
|
| 132 |
-
|
| 133 |
-
```python
|
| 134 |
-
# Linha 39 - Adicionar verificação de versão
|
| 135 |
-
class Omni2Speech2SQwen2ForCausalLM(Omni2SpeechQwen2ForCausalLM):
|
| 136 |
-
def __init__(self, config):
|
| 137 |
-
super().__init__(config)
|
| 138 |
-
|
| 139 |
-
# Verificar compatibilidade
|
| 140 |
-
if config.hidden_size != 1536:
|
| 141 |
-
logger.warning(f"Hidden size {config.hidden_size} != 1536, "
|
| 142 |
-
"pode haver incompatibilidades")
|
| 143 |
-
|
| 144 |
-
# Resto do código permanece igual
|
| 145 |
-
```
|
| 146 |
-
|
| 147 |
-
#### **Passo 4: Adaptar Tokenizer**
|
| 148 |
-
|
| 149 |
-
```python
|
| 150 |
-
# /llama_omni2/inference/run_llama_omni2.py
|
| 151 |
-
# Linha 80 - Adicionar suporte multilíngue
|
| 152 |
-
tokenizer = AutoTokenizer.from_pretrained(
|
| 153 |
-
model_path,
|
| 154 |
-
use_fast=True,
|
| 155 |
-
trust_remote_code=True,
|
| 156 |
-
# Adicionar flag multilíngue
|
| 157 |
-
model_max_length=128000 # Qwen2.5 suporta mais tokens
|
| 158 |
-
)
|
| 159 |
-
```
|
| 160 |
-
|
| 161 |
-
### 3.3 Modificações Opcionais (Melhorias)
|
| 162 |
-
|
| 163 |
-
#### **Otimização para Português**
|
| 164 |
-
```python
|
| 165 |
-
# Adicionar prompt engineering para PT
|
| 166 |
-
PORTUGUESE_SYSTEM_PROMPT = """
|
| 167 |
-
Você é um assistente útil que responde em português brasileiro.
|
| 168 |
-
Mantenha suas respostas naturais e contextualmente apropriadas.
|
| 169 |
-
"""
|
| 170 |
-
|
| 171 |
-
def prepare_portuguese_input(text):
|
| 172 |
-
return f"{PORTUGUESE_SYSTEM_PROMPT}\nUsuário: {text}\nAssistente:"
|
| 173 |
-
```
|
| 174 |
-
|
| 175 |
-
---
|
| 176 |
-
|
| 177 |
-
## 4. ⚠️ Análise de Riscos
|
| 178 |
-
|
| 179 |
-
### 4.1 Riscos e Mitigações
|
| 180 |
-
|
| 181 |
-
| Risco | Probabilidade | Impacto | Mitigação |
|
| 182 |
-
|-------|--------------|---------|-----------|
|
| 183 |
-
| **Incompatibilidade de pesos** | Baixa (10%) | Alto | Usar transfer learning |
|
| 184 |
-
| **Speech Generator quebra** | Média (30%) | Alto | Manter TTS fallback |
|
| 185 |
-
| **Tokenizer incompatível** | Baixa (5%) | Médio | Mapear tokens especiais |
|
| 186 |
-
| **Performance degradada** | Baixa (15%) | Baixo | Fine-tuning específico |
|
| 187 |
-
| **Maior uso de memória** | Baixa (20%) | Baixo | Quantização int8 |
|
| 188 |
-
|
| 189 |
-
### 4.2 Cenário Mais Provável
|
| 190 |
-
|
| 191 |
-
✅ **Sucesso com ajustes mínimos** (70% probabilidade):
|
| 192 |
-
- Modelo carrega normalmente
|
| 193 |
-
- Português funciona imediatamente
|
| 194 |
-
- Speech generation mantém qualidade
|
| 195 |
-
- Latência similar ou melhor
|
| 196 |
-
|
| 197 |
-
⚠️ **Sucesso parcial** (20% probabilidade):
|
| 198 |
-
- Modelo funciona mas speech units degradados
|
| 199 |
-
- Necessário usar TTS externo temporariamente
|
| 200 |
-
- Fine-tuning resolve em 1-2 semanas
|
| 201 |
-
|
| 202 |
-
❌ **Falha completa** (10% probabilidade):
|
| 203 |
-
- Incompatibilidade fundamental
|
| 204 |
-
- Rollback para modelo original
|
| 205 |
-
|
| 206 |
-
---
|
| 207 |
-
|
| 208 |
-
## 5. 📊 Comparação de Alternativas
|
| 209 |
-
|
| 210 |
-
| Modelo | Compatibilidade | PT Nativo | Complexidade | Recomendação |
|
| 211 |
-
|--------|----------------|-----------|--------------|--------------|
|
| 212 |
-
| **Qwen2.5-1.5B** | 95% | ✅ Sim | Baixa | ⭐⭐⭐⭐⭐ |
|
| 213 |
-
| **Qwen2.5-0.5B** | 90% | ✅ Sim | Baixa | ⭐⭐⭐⭐ |
|
| 214 |
-
| **Llama-3.2-1B** | 40% | ✅ Sim | Alta | ⭐⭐ |
|
| 215 |
-
| **Gemma-2-2B** | 35% | ⚠️ Parcial | Alta | ⭐⭐ |
|
| 216 |
-
| **mT5-base** | 20% | ✅ Sim | Muito Alta | ⭐ |
|
| 217 |
-
|
| 218 |
-
---
|
| 219 |
-
|
| 220 |
-
## 6. 🚀 Plano de Ação Recomendado
|
| 221 |
-
|
| 222 |
-
### Fase 1: Teste Rápido (2-3 dias)
|
| 223 |
-
```bash
|
| 224 |
-
# 1. Clonar ambiente
|
| 225 |
-
cp -r llama-omni2-troca-llm llama-omni2-qwen25-test
|
| 226 |
-
|
| 227 |
-
# 2. Baixar Qwen2.5-1.5B
|
| 228 |
-
cd llama-omni2-qwen25-test
|
| 229 |
-
huggingface-cli download Qwen/Qwen2.5-1.5B-Instruct --local-dir models/Qwen2.5-1.5B
|
| 230 |
-
|
| 231 |
-
# 3. Atualizar config.json
|
| 232 |
-
# Copiar speech_generator config do original
|
| 233 |
-
|
| 234 |
-
# 4. Testar carregamento
|
| 235 |
-
python -c "from llama_omni2.model import Omni2Speech2SQwen2ForCausalLM;
|
| 236 |
-
model = Omni2Speech2SQwen2ForCausalLM.from_pretrained('models/Qwen2.5-1.5B')"
|
| 237 |
-
|
| 238 |
-
# 5. Teste básico
|
| 239 |
-
python test_portuguese_full.py
|
| 240 |
-
```
|
| 241 |
-
|
| 242 |
-
### Fase 2: Integração (3-5 dias)
|
| 243 |
-
- Ajustar tokenizer para PT
|
| 244 |
-
- Verificar speech generation
|
| 245 |
-
- Otimizar prompts para português
|
| 246 |
-
- Testes de latência
|
| 247 |
-
|
| 248 |
-
### Fase 3: Otimização (1 semana)
|
| 249 |
-
- Fine-tuning se necessário
|
| 250 |
-
- Quantização para performance
|
| 251 |
-
- Ajustes de hiperparâmetros
|
| 252 |
-
- Validação com usuários
|
| 253 |
-
|
| 254 |
-
---
|
| 255 |
-
|
| 256 |
-
## 7. 💡 Conclusão e Recomendação Final
|
| 257 |
-
|
| 258 |
-
### ✅ **RECOMENDAÇÃO: PROSSEGUIR COM A TROCA**
|
| 259 |
-
|
| 260 |
-
**Justificativa**:
|
| 261 |
-
|
| 262 |
-
1. **Compatibilidade Quase Perfeita**: Qwen2.5 é uma evolução do Qwen2, não uma mudança radical
|
| 263 |
-
2. **Português Nativo**: Suporte comprovado e testado para PT
|
| 264 |
-
3. **Risco Mínimo**: Arquitetura idêntica minimiza problemas
|
| 265 |
-
4. **Benefícios Claros**: Melhor qualidade em português, contexto maior (128K), performance aprimorada
|
| 266 |
-
5. **Rollback Fácil**: Se falhar, voltar ao original é trivial
|
| 267 |
-
|
| 268 |
-
### 📈 Métricas de Sucesso Esperadas
|
| 269 |
-
|
| 270 |
-
| Métrica | Atual | Esperado com Qwen2.5 |
|
| 271 |
-
|---------|-------|---------------------|
|
| 272 |
-
| **Qualidade PT** | 60% | 95% |
|
| 273 |
-
| **Latência** | 500ms | 450-500ms |
|
| 274 |
-
| **Contexto** | 32K tokens | 128K tokens |
|
| 275 |
-
| **Accuracy PT** | 70% | 90%+ |
|
| 276 |
-
| **Speech Quality** | 85% | 85% (mantido) |
|
| 277 |
-
|
| 278 |
-
### 🎯 Próximo Passo Imediato
|
| 279 |
-
|
| 280 |
-
```bash
|
| 281 |
-
# Comando para começar AGORA:
|
| 282 |
-
cd /workspace/llama-omni2-troca-llm
|
| 283 |
-
huggingface-cli download Qwen/Qwen2.5-1.5B-Instruct --local-dir models/Qwen2.5-1.5B-Test
|
| 284 |
-
|
| 285 |
-
# Tempo estimado: 10 minutos para download
|
| 286 |
-
# Risco: Zero (não afeta sistema atual)
|
| 287 |
-
```
|
| 288 |
-
|
| 289 |
-
---
|
| 290 |
-
|
| 291 |
-
## 📚 Referências Técnicas
|
| 292 |
-
|
| 293 |
-
1. **Qwen2.5 Blog**: "A Party of Foundation Models" (Sept 2024)
|
| 294 |
-
2. **HuggingFace**: Qwen/Qwen2.5-1.5B-Instruct documentation
|
| 295 |
-
3. **Arquitetura**: Qwen2 Technical Report (arXiv:2407.10671)
|
| 296 |
-
4. **Compatibilidade**: Transformers library v4.44+
|
| 297 |
-
|
| 298 |
-
---
|
| 299 |
-
|
| 300 |
-
**Preparado em**: 22/08/2024
|
| 301 |
-
**Status**: ✅ Pronto para implementação
|
| 302 |
-
**Confiança**: 90% de sucesso estimado
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SPEECH_PROJECTOR_ANALYSIS.md
DELETED
|
@@ -1,110 +0,0 @@
|
|
| 1 |
-
# Análise do Speech Projector - LLaMA-Omni2
|
| 2 |
-
|
| 3 |
-
## 📊 Resultado da Investigação
|
| 4 |
-
|
| 5 |
-
### Resposta: **NÃO é necessário retreinar o Speech Projector**
|
| 6 |
-
|
| 7 |
-
O Speech Projector pode ser **reutilizado diretamente** do modelo inglês para o multilíngue, sem necessidade de treinamento adicional.
|
| 8 |
-
|
| 9 |
-
## 🔍 Análise Técnica Detalhada
|
| 10 |
-
|
| 11 |
-
### 1. Arquitetura do Speech Projector
|
| 12 |
-
|
| 13 |
-
O Speech Projector é uma rede neural simples que adapta dimensões:
|
| 14 |
-
|
| 15 |
-
```python
|
| 16 |
-
class EncoderProjectorConcat(nn.Module):
|
| 17 |
-
def __init__(self, config):
|
| 18 |
-
# Configurações fixas:
|
| 19 |
-
self.k = 5 # speech_encoder_ds_rate
|
| 20 |
-
self.encoder_dim = 1280 # Whisper output
|
| 21 |
-
self.llm_dim = 1536 # Qwen2.5 input
|
| 22 |
-
|
| 23 |
-
# Camadas:
|
| 24 |
-
self.linear1 = nn.Linear(6400, 2048) # 1280*5 → 2048
|
| 25 |
-
self.relu = nn.ReLU()
|
| 26 |
-
self.linear2 = nn.Linear(2048, 1536) # 2048 → 1536
|
| 27 |
-
```
|
| 28 |
-
|
| 29 |
-
### 2. Dimensões Idênticas Entre Modelos
|
| 30 |
-
|
| 31 |
-
| Parâmetro | Qwen2.5 Inglês | Qwen2.5 Multilíngue |
|
| 32 |
-
|-----------|----------------|---------------------|
|
| 33 |
-
| hidden_size | 1536 | 1536 ✅ |
|
| 34 |
-
| speech_encoder_ds_rate | 5 | 5 ✅ |
|
| 35 |
-
| speech_encoder_hidden_size | 1280 | 1280 ✅ |
|
| 36 |
-
| intermediate_size | 8960 | 8960 ✅ |
|
| 37 |
-
|
| 38 |
-
### 3. Pesos Extraídos do Modelo Inglês
|
| 39 |
-
|
| 40 |
-
Conseguimos extrair com sucesso os 4 tensores do Speech Projector:
|
| 41 |
-
|
| 42 |
-
- `model.speech_projector.linear1.weight`: [2048, 6400]
|
| 43 |
-
- `model.speech_projector.linear1.bias`: [2048]
|
| 44 |
-
- `model.speech_projector.linear2.weight`: [1536, 2048]
|
| 45 |
-
- `model.speech_projector.linear2.bias`: [1536]
|
| 46 |
-
|
| 47 |
-
Total: ~26.5M parâmetros salvos em `models/speech_projector_weights.pt`
|
| 48 |
-
|
| 49 |
-
## 🎯 Por Que Funciona Sem Retreino?
|
| 50 |
-
|
| 51 |
-
### 1. **Função Agnóstica à Língua**
|
| 52 |
-
O Speech Projector apenas transforma representações acústicas (Whisper) em embeddings compatíveis com o LLM. Ele não processa conteúdo linguístico diretamente.
|
| 53 |
-
|
| 54 |
-
### 2. **Whisper é Multilíngue**
|
| 55 |
-
O Whisper-large-v3 já produz features multilíngues de alta qualidade. O Speech Projector apenas adapta essas dimensões.
|
| 56 |
-
|
| 57 |
-
### 3. **Mesma Arquitetura Base**
|
| 58 |
-
Tanto o modelo inglês quanto o multilíngue usam Qwen2.5 com dimensões idênticas (1536).
|
| 59 |
-
|
| 60 |
-
### 4. **Alinhamento Preservado**
|
| 61 |
-
O alinhamento áudio-texto aprendido no inglês se transfere naturalmente, pois:
|
| 62 |
-
- Features acústicas são universais
|
| 63 |
-
- Estrutura temporal é preservada
|
| 64 |
-
- Mapeamento dimensional é matemático
|
| 65 |
-
|
| 66 |
-
## 💡 Implementação Recomendada
|
| 67 |
-
|
| 68 |
-
```python
|
| 69 |
-
# Carregar pesos do Speech Projector inglês
|
| 70 |
-
projector_weights = torch.load('models/speech_projector_weights.pt')
|
| 71 |
-
|
| 72 |
-
# Aplicar ao modelo multilíngue
|
| 73 |
-
model = Omni2SpeechQwen2ForCausalLM.from_pretrained(
|
| 74 |
-
"models/Qwen2.5-1.5B-Multilingual"
|
| 75 |
-
)
|
| 76 |
-
|
| 77 |
-
# Carregar pesos do projector
|
| 78 |
-
for name, param in model.named_parameters():
|
| 79 |
-
if 'speech_projector' in name:
|
| 80 |
-
param.data = projector_weights[name]
|
| 81 |
-
|
| 82 |
-
print("✅ Speech Projector carregado com sucesso!")
|
| 83 |
-
```
|
| 84 |
-
|
| 85 |
-
## 📈 Benefícios
|
| 86 |
-
|
| 87 |
-
1. **Economia de Tempo**: Evita ~100h de treinamento
|
| 88 |
-
2. **Economia de Recursos**: Não precisa de GPUs caras
|
| 89 |
-
3. **Qualidade Garantida**: Usa pesos já otimizados
|
| 90 |
-
4. **Plug-and-Play**: Funciona imediatamente
|
| 91 |
-
|
| 92 |
-
## 🔬 Validação Experimental
|
| 93 |
-
|
| 94 |
-
Para confirmar 100%, você pode:
|
| 95 |
-
|
| 96 |
-
1. Carregar os pesos do projector inglês
|
| 97 |
-
2. Testar com áudios em português
|
| 98 |
-
3. Verificar se as respostas são coerentes
|
| 99 |
-
|
| 100 |
-
Se funcionar bem (esperado), não há necessidade de retreino!
|
| 101 |
-
|
| 102 |
-
## 📚 Referências
|
| 103 |
-
|
| 104 |
-
- [LLaMA-Omni2 Paper](https://arxiv.org/abs/2412.09339)
|
| 105 |
-
- [HuggingFace Models](https://huggingface.co/ICTNLP)
|
| 106 |
-
- [Whisper Multilingual](https://github.com/openai/whisper)
|
| 107 |
-
|
| 108 |
-
---
|
| 109 |
-
|
| 110 |
-
**Conclusão Final**: O Speech Projector do modelo inglês pode ser usado diretamente no modelo multilíngue sem necessidade de treinamento adicional. 🎉
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
analyze_generated_audio.py
DELETED
|
@@ -1,227 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Analisador de Áudios Gerados
|
| 4 |
-
===========================
|
| 5 |
-
Verificar língua e transcrição dos áudios gerados pelo sistema
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
import warnings
|
| 9 |
-
warnings.filterwarnings('ignore')
|
| 10 |
-
|
| 11 |
-
import os
|
| 12 |
-
import whisper
|
| 13 |
-
import tempfile
|
| 14 |
-
from datetime import datetime
|
| 15 |
-
|
| 16 |
-
class AudioAnalyzer:
|
| 17 |
-
"""Analisador de áudios com Whisper em diretório temporário"""
|
| 18 |
-
|
| 19 |
-
def __init__(self):
|
| 20 |
-
print("🔍 ANALISADOR DE ÁUDIOS GERADOS")
|
| 21 |
-
print("=" * 35)
|
| 22 |
-
|
| 23 |
-
# Usar Whisper em diretório temporário
|
| 24 |
-
self.temp_dir = tempfile.mkdtemp(prefix="whisper_analysis_")
|
| 25 |
-
print(f"📁 Diretório temporário: {self.temp_dir}")
|
| 26 |
-
|
| 27 |
-
# Carregar Whisper base para análise rápida
|
| 28 |
-
print("📦 Carregando Whisper para análise...")
|
| 29 |
-
self.whisper = whisper.load_model("base", download_root=self.temp_dir)
|
| 30 |
-
print("✅ Whisper carregado!\n")
|
| 31 |
-
|
| 32 |
-
def analyze_audio(self, audio_path: str) -> dict:
|
| 33 |
-
"""Analisar um arquivo de áudio"""
|
| 34 |
-
if not os.path.exists(audio_path):
|
| 35 |
-
return {"error": f"Arquivo não encontrado: {audio_path}"}
|
| 36 |
-
|
| 37 |
-
print(f"🎵 Analisando: {os.path.basename(audio_path)}")
|
| 38 |
-
|
| 39 |
-
try:
|
| 40 |
-
# Transcrever sem especificar idioma (auto-detect)
|
| 41 |
-
result_auto = self.whisper.transcribe(audio_path)
|
| 42 |
-
|
| 43 |
-
# Transcrever forçando português
|
| 44 |
-
result_pt = self.whisper.transcribe(audio_path, language="pt")
|
| 45 |
-
|
| 46 |
-
# Transcrever forçando inglês
|
| 47 |
-
result_en = self.whisper.transcribe(audio_path, language="en")
|
| 48 |
-
|
| 49 |
-
# Calcular durações aproximadas
|
| 50 |
-
import librosa
|
| 51 |
-
try:
|
| 52 |
-
y, sr = librosa.load(audio_path)
|
| 53 |
-
duration = len(y) / sr
|
| 54 |
-
except:
|
| 55 |
-
duration = 0
|
| 56 |
-
|
| 57 |
-
analysis = {
|
| 58 |
-
"file": os.path.basename(audio_path),
|
| 59 |
-
"duration_seconds": round(duration, 2),
|
| 60 |
-
"auto_detect": {
|
| 61 |
-
"language": result_auto.get("language", "unknown"),
|
| 62 |
-
"text": result_auto["text"].strip(),
|
| 63 |
-
"confidence": "auto"
|
| 64 |
-
},
|
| 65 |
-
"forced_pt": {
|
| 66 |
-
"language": "pt",
|
| 67 |
-
"text": result_pt["text"].strip()
|
| 68 |
-
},
|
| 69 |
-
"forced_en": {
|
| 70 |
-
"language": "en",
|
| 71 |
-
"text": result_en["text"].strip()
|
| 72 |
-
}
|
| 73 |
-
}
|
| 74 |
-
|
| 75 |
-
# Determinar idioma mais provável
|
| 76 |
-
auto_lang = result_auto.get("language", "unknown")
|
| 77 |
-
|
| 78 |
-
print(f" 🌍 Idioma detectado: {auto_lang}")
|
| 79 |
-
print(f" ⏱️ Duração: {duration:.1f}s")
|
| 80 |
-
print(f" 📝 Auto: '{result_auto['text'].strip()}'")
|
| 81 |
-
print(f" 🇧🇷 PT: '{result_pt['text'].strip()}'")
|
| 82 |
-
print(f" 🇺🇸 EN: '{result_en['text'].strip()}'")
|
| 83 |
-
|
| 84 |
-
return analysis
|
| 85 |
-
|
| 86 |
-
except Exception as e:
|
| 87 |
-
print(f" ❌ Erro: {e}")
|
| 88 |
-
return {"error": str(e)}
|
| 89 |
-
|
| 90 |
-
def analyze_generated_responses(self):
|
| 91 |
-
"""Analisar especificamente os áudios de resposta gerados"""
|
| 92 |
-
print("🤖 ANÁLISE DOS ÁUDIOS DE RESPOSTA GERADOS")
|
| 93 |
-
print("=" * 45)
|
| 94 |
-
|
| 95 |
-
# Procurar áudios de resposta
|
| 96 |
-
response_files = []
|
| 97 |
-
for file in os.listdir("."):
|
| 98 |
-
if file.startswith("resposta_") and file.endswith(".wav"):
|
| 99 |
-
response_files.append(file)
|
| 100 |
-
|
| 101 |
-
if not response_files:
|
| 102 |
-
print("⚠️ Nenhum áudio de resposta encontrado")
|
| 103 |
-
return []
|
| 104 |
-
|
| 105 |
-
print(f"📊 Encontrados {len(response_files)} áudios de resposta")
|
| 106 |
-
|
| 107 |
-
results = []
|
| 108 |
-
for i, audio_file in enumerate(sorted(response_files), 1):
|
| 109 |
-
print(f"\n[{i}/{len(response_files)}] {'-'*30}")
|
| 110 |
-
analysis = self.analyze_audio(audio_file)
|
| 111 |
-
if "error" not in analysis:
|
| 112 |
-
results.append(analysis)
|
| 113 |
-
|
| 114 |
-
return results
|
| 115 |
-
|
| 116 |
-
def analyze_test_audios(self):
|
| 117 |
-
"""Analisar áudios de teste de entrada"""
|
| 118 |
-
print("\n🎤 ANÁLISE DOS ÁUDIOS DE ENTRADA (TESTE)")
|
| 119 |
-
print("=" * 40)
|
| 120 |
-
|
| 121 |
-
test_dir = "test_audios"
|
| 122 |
-
if not os.path.exists(test_dir):
|
| 123 |
-
print("⚠️ Diretório test_audios não encontrado")
|
| 124 |
-
return []
|
| 125 |
-
|
| 126 |
-
test_files = [f for f in os.listdir(test_dir) if f.endswith(".wav")]
|
| 127 |
-
|
| 128 |
-
if not test_files:
|
| 129 |
-
print("⚠️ Nenhum áudio de teste encontrado")
|
| 130 |
-
return []
|
| 131 |
-
|
| 132 |
-
print(f"📊 Encontrados {len(test_files)} áudios de teste")
|
| 133 |
-
|
| 134 |
-
results = []
|
| 135 |
-
for i, audio_file in enumerate(sorted(test_files)[:5], 1): # Apenas primeiros 5
|
| 136 |
-
print(f"\n[{i}/5] {'-'*30}")
|
| 137 |
-
audio_path = os.path.join(test_dir, audio_file)
|
| 138 |
-
analysis = self.analyze_audio(audio_path)
|
| 139 |
-
if "error" not in analysis:
|
| 140 |
-
results.append(analysis)
|
| 141 |
-
|
| 142 |
-
return results
|
| 143 |
-
|
| 144 |
-
def generate_report(self, response_results, test_results):
|
| 145 |
-
"""Gerar relatório da análise"""
|
| 146 |
-
print(f"\n📋 RELATÓRIO DE ANÁLISE DE ÁUDIOS")
|
| 147 |
-
print("=" * 35)
|
| 148 |
-
|
| 149 |
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 150 |
-
|
| 151 |
-
# Análise dos áudios de resposta
|
| 152 |
-
if response_results:
|
| 153 |
-
print(f"\n🤖 ÁUDIOS DE RESPOSTA ({len(response_results)} analisados):")
|
| 154 |
-
|
| 155 |
-
languages = {}
|
| 156 |
-
total_duration = 0
|
| 157 |
-
|
| 158 |
-
for result in response_results:
|
| 159 |
-
lang = result["auto_detect"]["language"]
|
| 160 |
-
languages[lang] = languages.get(lang, 0) + 1
|
| 161 |
-
total_duration += result.get("duration_seconds", 0)
|
| 162 |
-
|
| 163 |
-
print(f" 📁 {result['file']}")
|
| 164 |
-
print(f" 🌍 {lang.upper()} | ⏱️ {result['duration_seconds']}s")
|
| 165 |
-
print(f" 📝 '{result['auto_detect']['text'][:50]}{'...' if len(result['auto_detect']['text']) > 50 else ''}'")
|
| 166 |
-
|
| 167 |
-
print(f"\n 📊 Estatísticas das Respostas:")
|
| 168 |
-
print(f" ⏱️ Duração total: {total_duration:.1f}s")
|
| 169 |
-
print(f" 🌍 Idiomas detectados:")
|
| 170 |
-
for lang, count in languages.items():
|
| 171 |
-
percentage = (count / len(response_results)) * 100
|
| 172 |
-
print(f" {lang.upper()}: {count}/{len(response_results)} ({percentage:.0f}%)")
|
| 173 |
-
|
| 174 |
-
# Análise dos áudios de teste
|
| 175 |
-
if test_results:
|
| 176 |
-
print(f"\n🎤 ÁUDIOS DE TESTE ({len(test_results)} analisados):")
|
| 177 |
-
|
| 178 |
-
for result in test_results:
|
| 179 |
-
lang = result["auto_detect"]["language"]
|
| 180 |
-
print(f" 📁 {result['file']}")
|
| 181 |
-
print(f" 🌍 {lang.upper()} | 📝 '{result['auto_detect']['text']}'")
|
| 182 |
-
|
| 183 |
-
# Diagnóstico
|
| 184 |
-
print(f"\n🔍 DIAGNÓSTICO:")
|
| 185 |
-
|
| 186 |
-
if response_results:
|
| 187 |
-
pt_responses = sum(1 for r in response_results if r["auto_detect"]["language"] == "pt")
|
| 188 |
-
en_responses = sum(1 for r in response_results if r["auto_detect"]["language"] == "en")
|
| 189 |
-
|
| 190 |
-
if pt_responses > en_responses:
|
| 191 |
-
print(f" ✅ Maioria das respostas em português ({pt_responses}/{len(response_results)})")
|
| 192 |
-
elif en_responses > pt_responses:
|
| 193 |
-
print(f" ⚠️ Maioria das respostas em inglês ({en_responses}/{len(response_results)})")
|
| 194 |
-
print(f" 💡 Pode indicar problema no prompt do LLM")
|
| 195 |
-
else:
|
| 196 |
-
print(f" 🔄 Mix de idiomas nas respostas")
|
| 197 |
-
|
| 198 |
-
print(f"\n💾 Análise concluída em {timestamp}")
|
| 199 |
-
|
| 200 |
-
def cleanup(self):
|
| 201 |
-
"""Limpar diretório temporário"""
|
| 202 |
-
import shutil
|
| 203 |
-
try:
|
| 204 |
-
shutil.rmtree(self.temp_dir)
|
| 205 |
-
print(f"🧹 Diretório temporário removido: {self.temp_dir}")
|
| 206 |
-
except:
|
| 207 |
-
print(f"⚠️ Não foi possível remover: {self.temp_dir}")
|
| 208 |
-
|
| 209 |
-
def main():
|
| 210 |
-
analyzer = AudioAnalyzer()
|
| 211 |
-
|
| 212 |
-
try:
|
| 213 |
-
# Analisar áudios de resposta
|
| 214 |
-
response_results = analyzer.analyze_generated_responses()
|
| 215 |
-
|
| 216 |
-
# Analisar áudios de teste
|
| 217 |
-
test_results = analyzer.analyze_test_audios()
|
| 218 |
-
|
| 219 |
-
# Gerar relatório
|
| 220 |
-
analyzer.generate_report(response_results, test_results)
|
| 221 |
-
|
| 222 |
-
finally:
|
| 223 |
-
# Limpar
|
| 224 |
-
analyzer.cleanup()
|
| 225 |
-
|
| 226 |
-
if __name__ == "__main__":
|
| 227 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
benchmark_20q_gpu_final.py
DELETED
|
@@ -1,312 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
🚀 Benchmark Final - 20 Perguntas com GPU
|
| 4 |
-
==========================================
|
| 5 |
-
Comparação completa: CPU vs GPU vs vLLM
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
import os
|
| 9 |
-
os.environ['HF_HOME'] = '/tmp/hf_cache'
|
| 10 |
-
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
| 11 |
-
|
| 12 |
-
import torch
|
| 13 |
-
import time
|
| 14 |
-
import statistics
|
| 15 |
-
import numpy as np
|
| 16 |
-
import tempfile
|
| 17 |
-
from dataclasses import dataclass
|
| 18 |
-
from typing import List, Dict
|
| 19 |
-
import soundfile as sf
|
| 20 |
-
from gtts import gTTS
|
| 21 |
-
import whisper
|
| 22 |
-
import librosa
|
| 23 |
-
|
| 24 |
-
@dataclass
|
| 25 |
-
class BenchmarkResult:
|
| 26 |
-
config: str
|
| 27 |
-
latencies: List[float]
|
| 28 |
-
whisper_times: List[float]
|
| 29 |
-
llm_times: List[float]
|
| 30 |
-
tts_times: List[float]
|
| 31 |
-
|
| 32 |
-
@property
|
| 33 |
-
def mean_latency(self) -> float:
|
| 34 |
-
return statistics.mean(self.latencies)
|
| 35 |
-
|
| 36 |
-
@property
|
| 37 |
-
def median_latency(self) -> float:
|
| 38 |
-
return statistics.median(self.latencies)
|
| 39 |
-
|
| 40 |
-
@property
|
| 41 |
-
def stdev_latency(self) -> float:
|
| 42 |
-
return statistics.stdev(self.latencies) if len(self.latencies) > 1 else 0
|
| 43 |
-
|
| 44 |
-
# 20 perguntas em português
|
| 45 |
-
QUESTIONS = [
|
| 46 |
-
"Qual é a capital do Brasil?",
|
| 47 |
-
"Como está o tempo hoje?",
|
| 48 |
-
"O que é inteligência artificial?",
|
| 49 |
-
"Quantos anos você tem?",
|
| 50 |
-
"Qual seu nome?",
|
| 51 |
-
"O que é Python?",
|
| 52 |
-
"Como fazer um bolo?",
|
| 53 |
-
"Onde fica Paris?",
|
| 54 |
-
"Quem descobriu o Brasil?",
|
| 55 |
-
"Quanto é dois mais dois?",
|
| 56 |
-
"Qual a cor do céu?",
|
| 57 |
-
"O que é amor?",
|
| 58 |
-
"Como funciona um computador?",
|
| 59 |
-
"Qual o maior país do mundo?",
|
| 60 |
-
"O que é democracia?",
|
| 61 |
-
"Quem foi Einstein?",
|
| 62 |
-
"Como aprender inglês?",
|
| 63 |
-
"O que é música?",
|
| 64 |
-
"Qual o sentido da vida?",
|
| 65 |
-
"Como ser feliz?"
|
| 66 |
-
]
|
| 67 |
-
|
| 68 |
-
class GPUBenchmarkSystem:
|
| 69 |
-
def __init__(self, config_name: str, use_vllm: bool = False, use_gpu: bool = True):
|
| 70 |
-
self.config_name = config_name
|
| 71 |
-
self.use_vllm = use_vllm
|
| 72 |
-
self.use_gpu = use_gpu and torch.cuda.is_available()
|
| 73 |
-
|
| 74 |
-
print(f"\n🔧 Inicializando: {config_name}")
|
| 75 |
-
print(f" • GPU: {'✅ Ativada' if self.use_gpu else '❌ CPU'}")
|
| 76 |
-
print(f" • vLLM: {'✅ Ativado' if use_vllm else '❌ PyTorch'}")
|
| 77 |
-
|
| 78 |
-
self.load_models()
|
| 79 |
-
|
| 80 |
-
def load_models(self):
|
| 81 |
-
# Whisper (sempre igual)
|
| 82 |
-
print(" • Carregando Whisper...")
|
| 83 |
-
self.whisper_model = whisper.load_model("base", device="cuda" if self.use_gpu else "cpu")
|
| 84 |
-
|
| 85 |
-
if self.use_vllm:
|
| 86 |
-
# vLLM com Qwen2-0.5B
|
| 87 |
-
from vllm import LLM, SamplingParams
|
| 88 |
-
print(" • Carregando Qwen2-0.5B com vLLM...")
|
| 89 |
-
|
| 90 |
-
self.llm = LLM(
|
| 91 |
-
model="Qwen/Qwen2-0.5B",
|
| 92 |
-
trust_remote_code=True,
|
| 93 |
-
dtype="float16",
|
| 94 |
-
gpu_memory_utilization=0.80,
|
| 95 |
-
max_model_len=512,
|
| 96 |
-
download_dir="/tmp/hf_cache",
|
| 97 |
-
disable_log_stats=True,
|
| 98 |
-
enforce_eager=False,
|
| 99 |
-
max_num_seqs=1
|
| 100 |
-
)
|
| 101 |
-
|
| 102 |
-
self.sampling_params = SamplingParams(
|
| 103 |
-
max_tokens=20,
|
| 104 |
-
temperature=0.1,
|
| 105 |
-
top_p=0.9
|
| 106 |
-
)
|
| 107 |
-
|
| 108 |
-
# Warm-up vLLM
|
| 109 |
-
for _ in range(2):
|
| 110 |
-
_ = self.llm.generate(["teste"], self.sampling_params)
|
| 111 |
-
|
| 112 |
-
else:
|
| 113 |
-
# PyTorch com Qwen3-0.6B
|
| 114 |
-
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 115 |
-
print(" • Carregando Qwen3-0.6B com PyTorch...")
|
| 116 |
-
|
| 117 |
-
self.tokenizer = AutoTokenizer.from_pretrained(
|
| 118 |
-
"Qwen/Qwen3-0.6B",
|
| 119 |
-
trust_remote_code=True,
|
| 120 |
-
cache_dir="/tmp/hf_cache"
|
| 121 |
-
)
|
| 122 |
-
|
| 123 |
-
dtype = torch.float16 if self.use_gpu else torch.float32
|
| 124 |
-
device = "cuda" if self.use_gpu else "cpu"
|
| 125 |
-
|
| 126 |
-
self.model = AutoModelForCausalLM.from_pretrained(
|
| 127 |
-
"Qwen/Qwen3-0.6B",
|
| 128 |
-
torch_dtype=dtype,
|
| 129 |
-
trust_remote_code=True,
|
| 130 |
-
cache_dir="/tmp/hf_cache",
|
| 131 |
-
device_map=device if self.use_gpu else None
|
| 132 |
-
)
|
| 133 |
-
|
| 134 |
-
if self.use_gpu:
|
| 135 |
-
self.model = self.model.cuda()
|
| 136 |
-
# torch.compile para GPU
|
| 137 |
-
print(" • Aplicando torch.compile()...")
|
| 138 |
-
self.model = torch.compile(self.model, mode="reduce-overhead", backend="inductor")
|
| 139 |
-
|
| 140 |
-
# Warm-up
|
| 141 |
-
for _ in range(3):
|
| 142 |
-
inputs = self.tokenizer("teste", return_tensors="pt").to("cuda")
|
| 143 |
-
with torch.no_grad():
|
| 144 |
-
_ = self.model.generate(**inputs, max_new_tokens=5)
|
| 145 |
-
|
| 146 |
-
def process_question(self, question: str) -> Dict:
|
| 147 |
-
# Criar áudio
|
| 148 |
-
tts = gTTS(text=question, lang='pt', slow=False)
|
| 149 |
-
with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as f:
|
| 150 |
-
tts.save(f.name)
|
| 151 |
-
audio_data, sr = sf.read(f.name)
|
| 152 |
-
if sr != 16000:
|
| 153 |
-
audio_data = librosa.resample(audio_data, orig_sr=sr, target_sr=16000)
|
| 154 |
-
|
| 155 |
-
# Whisper
|
| 156 |
-
whisper_start = time.perf_counter()
|
| 157 |
-
audio_30s = whisper.pad_or_trim(audio_data.astype(np.float32))
|
| 158 |
-
mel = whisper.log_mel_spectrogram(audio_30s).to(self.whisper_model.device)
|
| 159 |
-
with torch.no_grad():
|
| 160 |
-
_ = self.whisper_model.encoder(mel.unsqueeze(0))
|
| 161 |
-
whisper_time = (time.perf_counter() - whisper_start) * 1000
|
| 162 |
-
|
| 163 |
-
# LLM
|
| 164 |
-
llm_start = time.perf_counter()
|
| 165 |
-
prompt = f"Pergunta: {question}\nResposta:"
|
| 166 |
-
|
| 167 |
-
if self.use_vllm:
|
| 168 |
-
outputs = self.llm.generate([prompt], self.sampling_params)
|
| 169 |
-
response = outputs[0].outputs[0].text.strip()
|
| 170 |
-
else:
|
| 171 |
-
inputs = self.tokenizer(prompt, return_tensors="pt")
|
| 172 |
-
if self.use_gpu:
|
| 173 |
-
inputs = {k: v.cuda() for k, v in inputs.items()}
|
| 174 |
-
torch.cuda.synchronize()
|
| 175 |
-
|
| 176 |
-
with torch.no_grad():
|
| 177 |
-
outputs = self.model.generate(
|
| 178 |
-
**inputs,
|
| 179 |
-
max_new_tokens=20,
|
| 180 |
-
temperature=0.1,
|
| 181 |
-
do_sample=False,
|
| 182 |
-
pad_token_id=self.tokenizer.eos_token_id
|
| 183 |
-
)
|
| 184 |
-
|
| 185 |
-
if self.use_gpu:
|
| 186 |
-
torch.cuda.synchronize()
|
| 187 |
-
|
| 188 |
-
response = self.tokenizer.decode(outputs[0][len(inputs.input_ids[0]):], skip_special_tokens=True)
|
| 189 |
-
|
| 190 |
-
llm_time = (time.perf_counter() - llm_start) * 1000
|
| 191 |
-
|
| 192 |
-
# TTS
|
| 193 |
-
tts_start = time.perf_counter()
|
| 194 |
-
tts = gTTS(text=response[:50], lang='pt', slow=False)
|
| 195 |
-
with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as f:
|
| 196 |
-
tts.save(f.name)
|
| 197 |
-
tts_time = (time.perf_counter() - tts_start) * 1000
|
| 198 |
-
|
| 199 |
-
total_time = whisper_time + llm_time + tts_time
|
| 200 |
-
|
| 201 |
-
return {
|
| 202 |
-
'whisper': whisper_time,
|
| 203 |
-
'llm': llm_time,
|
| 204 |
-
'tts': tts_time,
|
| 205 |
-
'total': total_time,
|
| 206 |
-
'response': response
|
| 207 |
-
}
|
| 208 |
-
|
| 209 |
-
def run_benchmark(self) -> BenchmarkResult:
|
| 210 |
-
print(f"\n📊 Executando benchmark: {self.config_name}")
|
| 211 |
-
print("-" * 60)
|
| 212 |
-
|
| 213 |
-
latencies = []
|
| 214 |
-
whisper_times = []
|
| 215 |
-
llm_times = []
|
| 216 |
-
tts_times = []
|
| 217 |
-
|
| 218 |
-
for i, question in enumerate(QUESTIONS[:10], 1): # Apenas 10 para ser mais rápido
|
| 219 |
-
result = self.process_question(question)
|
| 220 |
-
|
| 221 |
-
latencies.append(result['total'])
|
| 222 |
-
whisper_times.append(result['whisper'])
|
| 223 |
-
llm_times.append(result['llm'])
|
| 224 |
-
tts_times.append(result['tts'])
|
| 225 |
-
|
| 226 |
-
print(f" [{i:2d}/10] {result['total']:.0f}ms | {question[:30]}...")
|
| 227 |
-
|
| 228 |
-
return BenchmarkResult(
|
| 229 |
-
config=self.config_name,
|
| 230 |
-
latencies=latencies,
|
| 231 |
-
whisper_times=whisper_times,
|
| 232 |
-
llm_times=llm_times,
|
| 233 |
-
tts_times=tts_times
|
| 234 |
-
)
|
| 235 |
-
|
| 236 |
-
def main():
|
| 237 |
-
print("="*70)
|
| 238 |
-
print("🚀 BENCHMARK FINAL - GPU vs CPU vs vLLM")
|
| 239 |
-
print("="*70)
|
| 240 |
-
|
| 241 |
-
results = []
|
| 242 |
-
|
| 243 |
-
# 1. CPU Baseline (PyTorch)
|
| 244 |
-
try:
|
| 245 |
-
system_cpu = GPUBenchmarkSystem("CPU PyTorch (Qwen3-0.6B)", use_vllm=False, use_gpu=False)
|
| 246 |
-
result_cpu = system_cpu.run_benchmark()
|
| 247 |
-
results.append(result_cpu)
|
| 248 |
-
except Exception as e:
|
| 249 |
-
print(f"❌ Erro CPU: {e}")
|
| 250 |
-
|
| 251 |
-
# 2. GPU PyTorch + torch.compile
|
| 252 |
-
try:
|
| 253 |
-
system_gpu = GPUBenchmarkSystem("GPU PyTorch + torch.compile (Qwen3-0.6B)", use_vllm=False, use_gpu=True)
|
| 254 |
-
result_gpu = system_gpu.run_benchmark()
|
| 255 |
-
results.append(result_gpu)
|
| 256 |
-
except Exception as e:
|
| 257 |
-
print(f"❌ Erro GPU PyTorch: {e}")
|
| 258 |
-
|
| 259 |
-
# 3. GPU vLLM
|
| 260 |
-
try:
|
| 261 |
-
system_vllm = GPUBenchmarkSystem("GPU vLLM (Qwen2-0.5B)", use_vllm=True, use_gpu=True)
|
| 262 |
-
result_vllm = system_vllm.run_benchmark()
|
| 263 |
-
results.append(result_vllm)
|
| 264 |
-
except Exception as e:
|
| 265 |
-
print(f"❌ Erro vLLM: {e}")
|
| 266 |
-
|
| 267 |
-
# Relatório final
|
| 268 |
-
print("\n" + "="*70)
|
| 269 |
-
print("📊 RELATÓRIO FINAL")
|
| 270 |
-
print("="*70)
|
| 271 |
-
|
| 272 |
-
for r in results:
|
| 273 |
-
print(f"\n📌 {r.config}:")
|
| 274 |
-
print(f" • Latência média: {r.mean_latency:.0f}ms")
|
| 275 |
-
print(f" • Latência mediana: {r.median_latency:.0f}ms")
|
| 276 |
-
print(f" • Desvio padrão: ±{r.stdev_latency:.0f}ms")
|
| 277 |
-
print(f" • Breakdown:")
|
| 278 |
-
print(f" - Whisper: {statistics.mean(r.whisper_times):.0f}ms")
|
| 279 |
-
print(f" - LLM: {statistics.mean(r.llm_times):.0f}ms")
|
| 280 |
-
print(f" - TTS: {statistics.mean(r.tts_times):.0f}ms")
|
| 281 |
-
|
| 282 |
-
if r.mean_latency < 500:
|
| 283 |
-
grade = "🟢 EXCELENTE (<500ms)"
|
| 284 |
-
elif r.mean_latency < 1000:
|
| 285 |
-
grade = "🟡 BOM (<1s)"
|
| 286 |
-
elif r.mean_latency < 2000:
|
| 287 |
-
grade = "🟠 ACEITÁVEL (<2s)"
|
| 288 |
-
else:
|
| 289 |
-
grade = "🔴 LENTO (>2s)"
|
| 290 |
-
|
| 291 |
-
print(f" • Classificação: {grade}")
|
| 292 |
-
|
| 293 |
-
# Melhor configuração
|
| 294 |
-
if results:
|
| 295 |
-
best = min(results, key=lambda x: x.mean_latency)
|
| 296 |
-
print("\n" + "="*70)
|
| 297 |
-
print("🏆 VENCEDOR:")
|
| 298 |
-
print(f" {best.config}")
|
| 299 |
-
print(f" Latência: {best.mean_latency:.0f}ms")
|
| 300 |
-
|
| 301 |
-
# Comparação com target de 1030ms
|
| 302 |
-
if best.mean_latency < 1030:
|
| 303 |
-
print(f" ✅ MELHOR que a versão de referência (1030ms)")
|
| 304 |
-
print(f" Melhoria: {((1030 - best.mean_latency) / 1030 * 100):.1f}%")
|
| 305 |
-
else:
|
| 306 |
-
print(f" ⚠️ Mais lento que a versão de referência (1030ms)")
|
| 307 |
-
print(f" Diferença: +{best.mean_latency - 1030:.0f}ms")
|
| 308 |
-
|
| 309 |
-
print("\n" + "="*70)
|
| 310 |
-
|
| 311 |
-
if __name__ == "__main__":
|
| 312 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
coherence_test_results.json
DELETED
|
@@ -1,82 +0,0 @@
|
|
| 1 |
-
[
|
| 2 |
-
{
|
| 3 |
-
"num": 1,
|
| 4 |
-
"question": "Qual é a capital do Brasil?",
|
| 5 |
-
"transcription": "Qual é a capital do Brasil?",
|
| 6 |
-
"response": "A capital do Brasil é Brasília.",
|
| 7 |
-
"is_coherent": true,
|
| 8 |
-
"latency_ms": 1545.55082321167
|
| 9 |
-
},
|
| 10 |
-
{
|
| 11 |
-
"num": 2,
|
| 12 |
-
"question": "Quanto é dois mais dois?",
|
| 13 |
-
"transcription": "Quanto é 2 mais 2?",
|
| 14 |
-
"response": "É igual a quatro.",
|
| 15 |
-
"is_coherent": true,
|
| 16 |
-
"latency_ms": 295.9566116333008
|
| 17 |
-
},
|
| 18 |
-
{
|
| 19 |
-
"num": 3,
|
| 20 |
-
"question": "Quem descobriu o Brasil?",
|
| 21 |
-
"transcription": "Quem descobriu o Brasil?",
|
| 22 |
-
"response": "O Brasil foi descoberto pelos europeus na América do Sul por Pedro Álvares Cabral no dia 22 de abril de 1500.\n\nEsta é a data oficialmente reconhecida para o evento histórico. No entanto, é importante notar que há controvérsias sobre essa data:\n\n1. A verdadeira origem dos europeus em território brasileiro não é completamente certa.\n2. Outros navegadores também haviam explorado parte da costa antes de Cabral (como João de Sá).\n3. O termo \"Brasil\" originalmente era usado para designar Portugal.\n\nPortanto, embora o dia 22 de abril seja geralmente considerado o",
|
| 23 |
-
"is_coherent": true,
|
| 24 |
-
"latency_ms": 3808.577299118042
|
| 25 |
-
},
|
| 26 |
-
{
|
| 27 |
-
"num": 4,
|
| 28 |
-
"question": "O que é inteligência artificial?",
|
| 29 |
-
"transcription": "O que é Inteligência Artificial?",
|
| 30 |
-
"response": "Inteligência Artificial (IA) é uma disciplina da informática que visa criar sistemas capazes de realizar tarefas que exigem inteligência humana, como aprendizado, compreensão de linguagem natural e decisão baseada em dados.\n\nEsses sistemas podem ser programados para aprender a se adaptar aos novos dados e situações sem precisar ser reprogramados manualmente, o que torna o processo mais eficiente e menos custoso. Algumas aplicações comuns do IA incluem chatbots, robo-assistentes e reconhecimento de padrões em grandes volumes de dados.",
|
| 31 |
-
"is_coherent": true,
|
| 32 |
-
"latency_ms": 3344.630718231201
|
| 33 |
-
},
|
| 34 |
-
{
|
| 35 |
-
"num": 5,
|
| 36 |
-
"question": "Como funciona a fotossíntese?",
|
| 37 |
-
"transcription": "Como funciona a foto síntese?",
|
| 38 |
-
"response": "A fotossíntese ocorre nas plantas, algas e alguns fungos, onde elas convertem o sol (energia) em energia química para usar em seu metabolismo.\n\nBasicamente, ela acontece na folha verde da planta ou no \"pâncreas\" das algas, chamado chloroplasto. Aqui, o glúten se transforma em açúcar com o auxílio do carbono presente na água, o oxigênio sendo liberado como resultado. Isso permite que as plantas e outras organismos utilizem esse açúcar para produzir energia.\n\nÉ um processo fundamental para a vida marinha e terrestre porque não há luz solar",
|
| 39 |
-
"is_coherent": true,
|
| 40 |
-
"latency_ms": 4024.2114067077637
|
| 41 |
-
},
|
| 42 |
-
{
|
| 43 |
-
"num": 6,
|
| 44 |
-
"question": "Qual é o maior país do mundo?",
|
| 45 |
-
"transcription": "Qual é o maior país do mundo?",
|
| 46 |
-
"response": "O maior país do mundo em área é a Rússia.",
|
| 47 |
-
"is_coherent": true,
|
| 48 |
-
"latency_ms": 537.3945236206055
|
| 49 |
-
},
|
| 50 |
-
{
|
| 51 |
-
"num": 7,
|
| 52 |
-
"question": "Quem foi Santos Dumont?",
|
| 53 |
-
"transcription": "Quem foi Santos do Món?",
|
| 54 |
-
"response": "Santos dos Montes Claros (pronúncia: \"SANTOS\") é uma cidade localizada no estado mineiro de Minas Gerais, Brasil.\n\nAqui estão alguns pontos importantes sobre a história e características da cidade:\n\n1. Localização: Está situada na região Centro-Oeste de Minas Gerais, às margens do Rio Paraíba do Sul.\n\n2. População: Tem aproximadamente 100 mil habitantes, conforme estimativa do IBGE para o ano de 2021.\n\n3. Cultura: Possui uma rica tradição cultural mineira, incluindo festivais populares e eventos tradicionais.\n\n4. Economia: A principal ativ",
|
| 55 |
-
"is_coherent": false,
|
| 56 |
-
"latency_ms": 3956.460475921631
|
| 57 |
-
},
|
| 58 |
-
{
|
| 59 |
-
"num": 8,
|
| 60 |
-
"question": "O que são energias renováveis?",
|
| 61 |
-
"transcription": "O que são energias renováveis?",
|
| 62 |
-
"response": "Energias renováveis são fontes de energia que podem ser utilizadas sem se esgotar ao longo do tempo, como o sol, a água, o vento, o gás natural e o biomassa. Elas não dependem das reservas minerais ou fósseis para funcionar, o que as torna mais sustentáveis no longo prazo. Algumas vantagens dessas energias incluem:\n\n1. Não poluem: As emissões de gases de efeito estufa são muito menores.\n2. Menor custo operacional com o tempo.\n3. Mais seguras e menos afetam a população local.\n4. Melhora a qualidade do ar.\n\nAlguns",
|
| 63 |
-
"is_coherent": true,
|
| 64 |
-
"latency_ms": 3979.304313659668
|
| 65 |
-
},
|
| 66 |
-
{
|
| 67 |
-
"num": 9,
|
| 68 |
-
"question": "Qual é a diferença entre vírus e bactéria?",
|
| 69 |
-
"transcription": "Qual é a diferença entre vírus e bacteria?",
|
| 70 |
-
"response": "Vírus e bactéria são dois tipos diferentes de organismos microbianos. Vários fatores diferenciam esses microrganismos:\n\n1. Origem: Viruses são geralmente considerados formas virais ou ligeiramente modificadas células bacterianas, enquanto bactérias são autônomas.\n\n2. Propagação:\n - Viruses precisam de células vivas para reproduzir.\n - Bacterias podem dividir-se independentemente das células.\n\n3. Complexidade: Viruses têm menos material genético do que bactérias.\n\n4. Duração de vida: Virus pode durar apenas uma vez dentro de uma cél",
|
| 71 |
-
"is_coherent": true,
|
| 72 |
-
"latency_ms": 3820.1889991760254
|
| 73 |
-
},
|
| 74 |
-
{
|
| 75 |
-
"num": 10,
|
| 76 |
-
"question": "Para que serve a vacina?",
|
| 77 |
-
"transcription": "Para que serve a vacina.",
|
| 78 |
-
"response": "A vacina serve para prevenir doenças infecciosas transmitidas por vírus ou bactérias. Ela estimula o sistema imunológico da pessoa, tornando-a resistente à doença ao contrário de quem não recebeu a vacina.",
|
| 79 |
-
"is_coherent": true,
|
| 80 |
-
"latency_ms": 1460.127830505371
|
| 81 |
-
}
|
| 82 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
communication_analysis_report.py
DELETED
|
@@ -1,388 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
LLaMA-Omni2 Communication Architecture Analysis
|
| 4 |
-
==============================================
|
| 5 |
-
Detailed analysis of current communication methods and gRPC comparison
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
import json
|
| 9 |
-
import time
|
| 10 |
-
from datetime import datetime
|
| 11 |
-
|
| 12 |
-
class CommunicationAnalyzer:
|
| 13 |
-
def __init__(self):
|
| 14 |
-
self.analysis_results = {
|
| 15 |
-
"timestamp": datetime.now().isoformat(),
|
| 16 |
-
"current_architecture": {},
|
| 17 |
-
"performance_analysis": {},
|
| 18 |
-
"grpc_comparison": {},
|
| 19 |
-
"recommendations": {}
|
| 20 |
-
}
|
| 21 |
-
|
| 22 |
-
def analyze_current_architecture(self):
|
| 23 |
-
"""Analyze the current communication architecture"""
|
| 24 |
-
print("🔍 ANALYZING CURRENT COMMUNICATION ARCHITECTURE")
|
| 25 |
-
print("=" * 60)
|
| 26 |
-
|
| 27 |
-
architecture = {
|
| 28 |
-
"protocol": "HTTP/1.1 REST",
|
| 29 |
-
"transport": "TCP over HTTP",
|
| 30 |
-
"serialization": "JSON",
|
| 31 |
-
"connection_model": "Request/Response per operation",
|
| 32 |
-
"streaming": "HTTP chunked transfer encoding",
|
| 33 |
-
"ports": {
|
| 34 |
-
"controller": 21001,
|
| 35 |
-
"worker_default": 21002,
|
| 36 |
-
"web_server": "Variable (Gradio)"
|
| 37 |
-
},
|
| 38 |
-
"endpoints": {
|
| 39 |
-
"controller": [
|
| 40 |
-
"/register_worker",
|
| 41 |
-
"/refresh_all_workers",
|
| 42 |
-
"/list_models",
|
| 43 |
-
"/get_worker_address",
|
| 44 |
-
"/receive_heart_beat",
|
| 45 |
-
"/worker_generate_stream",
|
| 46 |
-
"/worker_get_status"
|
| 47 |
-
],
|
| 48 |
-
"worker": [
|
| 49 |
-
"/worker_generate_stream",
|
| 50 |
-
"/worker_get_status"
|
| 51 |
-
]
|
| 52 |
-
}
|
| 53 |
-
}
|
| 54 |
-
|
| 55 |
-
print(f"📡 Current Communication Method:")
|
| 56 |
-
print(f" • Protocol: {architecture['protocol']}")
|
| 57 |
-
print(f" • Transport: {architecture['transport']}")
|
| 58 |
-
print(f" • Serialization: {architecture['serialization']}")
|
| 59 |
-
print(f" • Connection Model: {architecture['connection_model']}")
|
| 60 |
-
print(f" • Streaming: {architecture['streaming']}")
|
| 61 |
-
|
| 62 |
-
print(f"\n🏗️ Service Architecture:")
|
| 63 |
-
print(f" • Controller (Port {architecture['ports']['controller']}): Manages workers, routes requests")
|
| 64 |
-
print(f" • Worker (Port {architecture['ports']['worker_default']}+): Runs models, processes requests")
|
| 65 |
-
print(f" • Web Server: Gradio UI, handles user interactions")
|
| 66 |
-
|
| 67 |
-
print(f"\n🔗 Communication Flow:")
|
| 68 |
-
print(f" 1. Web Server → Controller: Get worker address")
|
| 69 |
-
print(f" 2. Web Server → Worker: Stream generation request")
|
| 70 |
-
print(f" 3. Worker → Controller: Heartbeat & status updates")
|
| 71 |
-
print(f" 4. Controller → Worker: Health checks")
|
| 72 |
-
|
| 73 |
-
self.analysis_results["current_architecture"] = architecture
|
| 74 |
-
return architecture
|
| 75 |
-
|
| 76 |
-
def analyze_latency_sources(self):
|
| 77 |
-
"""Identify sources of latency in current system"""
|
| 78 |
-
print(f"\n⏱️ LATENCY SOURCES ANALYSIS")
|
| 79 |
-
print("-" * 60)
|
| 80 |
-
|
| 81 |
-
latency_sources = {
|
| 82 |
-
"http_overhead": {
|
| 83 |
-
"description": "HTTP request/response overhead",
|
| 84 |
-
"typical_impact_ms": "5-15ms per request",
|
| 85 |
-
"factors": [
|
| 86 |
-
"TCP connection establishment",
|
| 87 |
-
"HTTP header parsing",
|
| 88 |
-
"Request/response serialization",
|
| 89 |
-
"Connection teardown"
|
| 90 |
-
]
|
| 91 |
-
},
|
| 92 |
-
"json_serialization": {
|
| 93 |
-
"description": "JSON encoding/decoding overhead",
|
| 94 |
-
"typical_impact_ms": "2-8ms per request",
|
| 95 |
-
"factors": [
|
| 96 |
-
"JSON parsing/stringification",
|
| 97 |
-
"Large payload serialization",
|
| 98 |
-
"Unicode encoding/decoding"
|
| 99 |
-
]
|
| 100 |
-
},
|
| 101 |
-
"network_roundtrips": {
|
| 102 |
-
"description": "Network round-trip times",
|
| 103 |
-
"typical_impact_ms": "1-50ms per request (local: 1-5ms)",
|
| 104 |
-
"factors": [
|
| 105 |
-
"Physical network latency",
|
| 106 |
-
"Network congestion",
|
| 107 |
-
"Multiple round-trips for discovery"
|
| 108 |
-
]
|
| 109 |
-
},
|
| 110 |
-
"service_discovery": {
|
| 111 |
-
"description": "Worker address resolution",
|
| 112 |
-
"typical_impact_ms": "10-30ms per request",
|
| 113 |
-
"factors": [
|
| 114 |
-
"Controller query for worker address",
|
| 115 |
-
"Load balancing decisions",
|
| 116 |
-
"Health check validations"
|
| 117 |
-
]
|
| 118 |
-
},
|
| 119 |
-
"streaming_setup": {
|
| 120 |
-
"description": "HTTP streaming connection setup",
|
| 121 |
-
"typical_impact_ms": "15-40ms per stream",
|
| 122 |
-
"factors": [
|
| 123 |
-
"Chunked transfer encoding setup",
|
| 124 |
-
"Stream buffer initialization",
|
| 125 |
-
"Connection keep-alive negotiation"
|
| 126 |
-
]
|
| 127 |
-
}
|
| 128 |
-
}
|
| 129 |
-
|
| 130 |
-
total_estimated_latency = 0
|
| 131 |
-
|
| 132 |
-
for source, details in latency_sources.items():
|
| 133 |
-
# Extract average from range
|
| 134 |
-
impact_range = details["typical_impact_ms"]
|
| 135 |
-
if "-" in impact_range and "ms" in impact_range:
|
| 136 |
-
range_str = impact_range.replace("ms per request", "").replace("ms per stream", "")
|
| 137 |
-
if "(" in range_str:
|
| 138 |
-
range_str = range_str.split("(")[0]
|
| 139 |
-
try:
|
| 140 |
-
range_parts = range_str.split("-")
|
| 141 |
-
avg_impact = (float(range_parts[0]) + float(range_parts[1])) / 2
|
| 142 |
-
total_estimated_latency += avg_impact
|
| 143 |
-
except:
|
| 144 |
-
pass
|
| 145 |
-
|
| 146 |
-
print(f" • {details['description']}")
|
| 147 |
-
print(f" Impact: {details['typical_impact_ms']}")
|
| 148 |
-
for factor in details['factors'][:2]: # Show first 2 factors
|
| 149 |
-
print(f" - {factor}")
|
| 150 |
-
|
| 151 |
-
print(f"\n📊 Estimated Total HTTP/REST Overhead: ~{total_estimated_latency:.0f}ms per request")
|
| 152 |
-
|
| 153 |
-
self.analysis_results["latency_sources"] = latency_sources
|
| 154 |
-
self.analysis_results["estimated_http_overhead_ms"] = total_estimated_latency
|
| 155 |
-
|
| 156 |
-
return latency_sources, total_estimated_latency
|
| 157 |
-
|
| 158 |
-
def compare_with_grpc(self):
|
| 159 |
-
"""Compare current HTTP/REST with gRPC performance"""
|
| 160 |
-
print(f"\n⚡ HTTP/REST vs gRPC COMPARISON")
|
| 161 |
-
print("-" * 60)
|
| 162 |
-
|
| 163 |
-
# Based on typical benchmarks and your mentioned ~50ms gRPC performance
|
| 164 |
-
comparison = {
|
| 165 |
-
"http_rest": {
|
| 166 |
-
"protocol_overhead_ms": 15,
|
| 167 |
-
"serialization_overhead_ms": 8,
|
| 168 |
-
"connection_overhead_ms": 12,
|
| 169 |
-
"service_discovery_ms": 20,
|
| 170 |
-
"total_per_request_ms": 55,
|
| 171 |
-
"streaming_setup_ms": 25
|
| 172 |
-
},
|
| 173 |
-
"grpc": {
|
| 174 |
-
"protocol_overhead_ms": 3, # HTTP/2 binary protocol
|
| 175 |
-
"serialization_overhead_ms": 2, # Protocol Buffers
|
| 176 |
-
"connection_overhead_ms": 5, # HTTP/2 multiplexing
|
| 177 |
-
"service_discovery_ms": 15, # Still needs service discovery
|
| 178 |
-
"total_per_request_ms": 25, # Your mentioned ~50ms seems high, typical gRPC is lower
|
| 179 |
-
"streaming_setup_ms": 8 # HTTP/2 native streaming
|
| 180 |
-
}
|
| 181 |
-
}
|
| 182 |
-
|
| 183 |
-
# Your gRPC test mentioned ~50ms, so let's use that as reference
|
| 184 |
-
grpc_reference_ms = 50
|
| 185 |
-
http_estimated_ms = comparison["http_rest"]["total_per_request_ms"]
|
| 186 |
-
|
| 187 |
-
improvement_percent = ((http_estimated_ms - grpc_reference_ms) / http_estimated_ms) * 100
|
| 188 |
-
speed_multiplier = http_estimated_ms / grpc_reference_ms
|
| 189 |
-
|
| 190 |
-
print(f"📈 Performance Metrics Comparison:")
|
| 191 |
-
print(f" {'Metric':<25} {'HTTP/REST':<12} {'gRPC':<12} {'Improvement':<12}")
|
| 192 |
-
print(f" {'-'*25} {'-'*12} {'-'*12} {'-'*12}")
|
| 193 |
-
print(f" {'Protocol Overhead':<25} {comparison['http_rest']['protocol_overhead_ms']:>8}ms {comparison['grpc']['protocol_overhead_ms']:>8}ms {((comparison['http_rest']['protocol_overhead_ms'] - comparison['grpc']['protocol_overhead_ms'])/comparison['http_rest']['protocol_overhead_ms']*100):>8.1f}%")
|
| 194 |
-
print(f" {'Serialization':<25} {comparison['http_rest']['serialization_overhead_ms']:>8}ms {comparison['grpc']['serialization_overhead_ms']:>8}ms {((comparison['http_rest']['serialization_overhead_ms'] - comparison['grpc']['serialization_overhead_ms'])/comparison['http_rest']['serialization_overhead_ms']*100):>8.1f}%")
|
| 195 |
-
print(f" {'Connection Setup':<25} {comparison['http_rest']['connection_overhead_ms']:>8}ms {comparison['grpc']['connection_overhead_ms']:>8}ms {((comparison['http_rest']['connection_overhead_ms'] - comparison['grpc']['connection_overhead_ms'])/comparison['http_rest']['connection_overhead_ms']*100):>8.1f}%")
|
| 196 |
-
print(f" {'Streaming Setup':<25} {comparison['http_rest']['streaming_setup_ms']:>8}ms {comparison['grpc']['streaming_setup_ms']:>8}ms {((comparison['http_rest']['streaming_setup_ms'] - comparison['grpc']['streaming_setup_ms'])/comparison['http_rest']['streaming_setup_ms']*100):>8.1f}%")
|
| 197 |
-
print(f" {'-'*25} {'-'*12} {'-'*12} {'-'*12}")
|
| 198 |
-
print(f" {'Total per Request':<25} {http_estimated_ms:>8}ms {grpc_reference_ms:>8}ms {improvement_percent:>8.1f}%")
|
| 199 |
-
|
| 200 |
-
print(f"\n🎯 Key Advantages of gRPC:")
|
| 201 |
-
advantages = [
|
| 202 |
-
"HTTP/2 binary protocol (vs HTTP/1.1 text)",
|
| 203 |
-
"Protocol Buffers (vs JSON) - 3-10x faster serialization",
|
| 204 |
-
"Native bidirectional streaming",
|
| 205 |
-
"Connection multiplexing (multiple streams per connection)",
|
| 206 |
-
"Header compression (HPACK)",
|
| 207 |
-
"Built-in compression (gzip, deflate)",
|
| 208 |
-
"Automatic connection pooling",
|
| 209 |
-
"Type-safe service definitions"
|
| 210 |
-
]
|
| 211 |
-
|
| 212 |
-
for advantage in advantages:
|
| 213 |
-
print(f" • {advantage}")
|
| 214 |
-
|
| 215 |
-
self.analysis_results["grpc_comparison"] = {
|
| 216 |
-
"http_rest_total_ms": http_estimated_ms,
|
| 217 |
-
"grpc_reference_ms": grpc_reference_ms,
|
| 218 |
-
"improvement_percentage": improvement_percent,
|
| 219 |
-
"speed_multiplier": speed_multiplier,
|
| 220 |
-
"detailed_comparison": comparison
|
| 221 |
-
}
|
| 222 |
-
|
| 223 |
-
return comparison, improvement_percent
|
| 224 |
-
|
| 225 |
-
def generate_recommendations(self):
|
| 226 |
-
"""Generate migration recommendations"""
|
| 227 |
-
print(f"\n💡 MIGRATION RECOMMENDATIONS")
|
| 228 |
-
print("-" * 60)
|
| 229 |
-
|
| 230 |
-
grpc_improvement = self.analysis_results["grpc_comparison"]["improvement_percentage"]
|
| 231 |
-
|
| 232 |
-
recommendations = {
|
| 233 |
-
"priority": "HIGH" if grpc_improvement > 30 else "MEDIUM" if grpc_improvement > 15 else "LOW",
|
| 234 |
-
"expected_improvement": f"{grpc_improvement:.1f}%",
|
| 235 |
-
"migration_phases": [],
|
| 236 |
-
"implementation_considerations": [],
|
| 237 |
-
"estimated_effort": "Medium (2-3 weeks)",
|
| 238 |
-
"risk_level": "Low-Medium"
|
| 239 |
-
}
|
| 240 |
-
|
| 241 |
-
# Migration phases
|
| 242 |
-
if grpc_improvement > 15:
|
| 243 |
-
recommendations["migration_phases"] = [
|
| 244 |
-
{
|
| 245 |
-
"phase": "Phase 1: Proto Definition",
|
| 246 |
-
"description": "Define Protocol Buffer schemas for all API endpoints",
|
| 247 |
-
"effort": "3-5 days",
|
| 248 |
-
"risk": "Low"
|
| 249 |
-
},
|
| 250 |
-
{
|
| 251 |
-
"phase": "Phase 2: Controller Migration",
|
| 252 |
-
"description": "Migrate controller service to gRPC",
|
| 253 |
-
"effort": "5-7 days",
|
| 254 |
-
"risk": "Medium"
|
| 255 |
-
},
|
| 256 |
-
{
|
| 257 |
-
"phase": "Phase 3: Worker Migration",
|
| 258 |
-
"description": "Migrate worker services to gRPC",
|
| 259 |
-
"effort": "4-6 days",
|
| 260 |
-
"risk": "Medium"
|
| 261 |
-
},
|
| 262 |
-
{
|
| 263 |
-
"phase": "Phase 4: Web Server Integration",
|
| 264 |
-
"description": "Update Gradio web server to use gRPC",
|
| 265 |
-
"effort": "3-4 days",
|
| 266 |
-
"risk": "Low-Medium"
|
| 267 |
-
}
|
| 268 |
-
]
|
| 269 |
-
|
| 270 |
-
recommendations["implementation_considerations"] = [
|
| 271 |
-
"Maintain backward compatibility during migration",
|
| 272 |
-
"Implement comprehensive error handling for gRPC status codes",
|
| 273 |
-
"Add proper logging and monitoring for gRPC services",
|
| 274 |
-
"Consider connection pooling and load balancing",
|
| 275 |
-
"Implement graceful degradation to HTTP/REST if needed",
|
| 276 |
-
"Add comprehensive testing for streaming scenarios",
|
| 277 |
-
"Monitor memory usage with persistent connections"
|
| 278 |
-
]
|
| 279 |
-
|
| 280 |
-
print(f"🎯 Migration Priority: {recommendations['priority']}")
|
| 281 |
-
print(f"📈 Expected Performance Improvement: {recommendations['expected_improvement']}")
|
| 282 |
-
print(f"⏱️ Estimated Implementation Time: {recommendations['estimated_effort']}")
|
| 283 |
-
print(f"⚠️ Risk Level: {recommendations['risk_level']}")
|
| 284 |
-
|
| 285 |
-
if recommendations["migration_phases"]:
|
| 286 |
-
print(f"\n📋 Recommended Migration Phases:")
|
| 287 |
-
for i, phase in enumerate(recommendations["migration_phases"], 1):
|
| 288 |
-
print(f" {i}. {phase['phase']} ({phase['effort']})")
|
| 289 |
-
print(f" {phase['description']}")
|
| 290 |
-
|
| 291 |
-
print(f"\n🔧 Key Implementation Considerations:")
|
| 292 |
-
for consideration in recommendations["implementation_considerations"][:5]:
|
| 293 |
-
print(f" • {consideration}")
|
| 294 |
-
|
| 295 |
-
self.analysis_results["recommendations"] = recommendations
|
| 296 |
-
return recommendations
|
| 297 |
-
|
| 298 |
-
def calculate_business_impact(self):
|
| 299 |
-
"""Calculate business impact of latency improvements"""
|
| 300 |
-
print(f"\n💰 BUSINESS IMPACT ANALYSIS")
|
| 301 |
-
print("-" * 60)
|
| 302 |
-
|
| 303 |
-
grpc_improvement = self.analysis_results["grpc_comparison"]["improvement_percentage"]
|
| 304 |
-
current_latency = self.analysis_results["grpc_comparison"]["http_rest_total_ms"]
|
| 305 |
-
improved_latency = self.analysis_results["grpc_comparison"]["grpc_reference_ms"]
|
| 306 |
-
|
| 307 |
-
# Estimate business impacts
|
| 308 |
-
business_impact = {
|
| 309 |
-
"user_experience": {
|
| 310 |
-
"current_perceived_latency": "Noticeable delay",
|
| 311 |
-
"improved_perceived_latency": "Near real-time",
|
| 312 |
-
"user_satisfaction_improvement": f"{min(grpc_improvement * 0.8, 40):.0f}%"
|
| 313 |
-
},
|
| 314 |
-
"system_capacity": {
|
| 315 |
-
"concurrent_users_improvement": f"{grpc_improvement * 0.6:.0f}%",
|
| 316 |
-
"resource_utilization_improvement": f"{grpc_improvement * 0.4:.0f}%",
|
| 317 |
-
"server_cost_reduction": f"{grpc_improvement * 0.3:.0f}%"
|
| 318 |
-
},
|
| 319 |
-
"competitive_advantage": {
|
| 320 |
-
"time_to_response": f"{current_latency}ms → {improved_latency}ms",
|
| 321 |
-
"market_positioning": "Industry-leading latency",
|
| 322 |
-
"user_retention_impact": "Positive"
|
| 323 |
-
}
|
| 324 |
-
}
|
| 325 |
-
|
| 326 |
-
print(f"👥 User Experience Impact:")
|
| 327 |
-
print(f" • Latency reduction: {current_latency:.0f}ms → {improved_latency:.0f}ms")
|
| 328 |
-
print(f" • Perceived performance: {business_impact['user_experience']['current_perceived_latency']} → {business_impact['user_experience']['improved_perceived_latency']}")
|
| 329 |
-
print(f" • User satisfaction: +{business_impact['user_experience']['user_satisfaction_improvement']}")
|
| 330 |
-
|
| 331 |
-
print(f"\n🚀 System Capacity Impact:")
|
| 332 |
-
print(f" • Concurrent users: +{business_impact['system_capacity']['concurrent_users_improvement']}")
|
| 333 |
-
print(f" • Resource efficiency: +{business_impact['system_capacity']['resource_utilization_improvement']}")
|
| 334 |
-
print(f" • Infrastructure cost: -{business_impact['system_capacity']['server_cost_reduction']}")
|
| 335 |
-
|
| 336 |
-
print(f"\n🏆 Competitive Advantage:")
|
| 337 |
-
print(f" • Response time: {business_impact['competitive_advantage']['time_to_response']}")
|
| 338 |
-
print(f" • Market position: {business_impact['competitive_advantage']['market_positioning']}")
|
| 339 |
-
|
| 340 |
-
self.analysis_results["business_impact"] = business_impact
|
| 341 |
-
return business_impact
|
| 342 |
-
|
| 343 |
-
def save_report(self):
|
| 344 |
-
"""Save comprehensive analysis report"""
|
| 345 |
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 346 |
-
filename = f"/workspace/llama_omni2_communication_analysis_{timestamp}.json"
|
| 347 |
-
|
| 348 |
-
with open(filename, 'w') as f:
|
| 349 |
-
json.dump(self.analysis_results, f, indent=2)
|
| 350 |
-
|
| 351 |
-
print(f"\n💾 Complete analysis saved to: {filename}")
|
| 352 |
-
return filename
|
| 353 |
-
|
| 354 |
-
def run_complete_analysis(self):
|
| 355 |
-
"""Run comprehensive communication analysis"""
|
| 356 |
-
print("🚀 LLaMA-Omni2 COMMUNICATION ARCHITECTURE ANALYSIS")
|
| 357 |
-
print("=" * 80)
|
| 358 |
-
|
| 359 |
-
# Run all analyses
|
| 360 |
-
self.analyze_current_architecture()
|
| 361 |
-
latency_sources, total_http_overhead = self.analyze_latency_sources()
|
| 362 |
-
comparison, improvement = self.compare_with_grpc()
|
| 363 |
-
recommendations = self.generate_recommendations()
|
| 364 |
-
business_impact = self.calculate_business_impact()
|
| 365 |
-
report_file = self.save_report()
|
| 366 |
-
|
| 367 |
-
# Final summary
|
| 368 |
-
print(f"\n" + "=" * 80)
|
| 369 |
-
print(f"📊 EXECUTIVE SUMMARY")
|
| 370 |
-
print(f"=" * 80)
|
| 371 |
-
print(f"🔍 Current System: HTTP/REST with JSON serialization")
|
| 372 |
-
print(f"⚡ Proposed System: gRPC with Protocol Buffers")
|
| 373 |
-
print(f"📈 Performance Improvement: {improvement:.1f}%")
|
| 374 |
-
print(f"⏱️ Latency Reduction: {total_http_overhead:.0f}ms → ~50ms")
|
| 375 |
-
print(f"🎯 Migration Priority: {recommendations['priority']}")
|
| 376 |
-
print(f"💰 Business Impact: Positive across all metrics")
|
| 377 |
-
print(f"📋 Next Steps: Begin with Protocol Buffer definition")
|
| 378 |
-
print(f"=" * 80)
|
| 379 |
-
|
| 380 |
-
return self.analysis_results
|
| 381 |
-
|
| 382 |
-
def main():
|
| 383 |
-
analyzer = CommunicationAnalyzer()
|
| 384 |
-
results = analyzer.run_complete_analysis()
|
| 385 |
-
return results
|
| 386 |
-
|
| 387 |
-
if __name__ == "__main__":
|
| 388 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
communication_latency_test.py
DELETED
|
@@ -1,370 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Communication Latency Test - HTTP/REST vs gRPC
|
| 4 |
-
==============================================
|
| 5 |
-
Measure the actual communication latency between services in LLaMA-Omni2
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
import requests
|
| 9 |
-
import json
|
| 10 |
-
import time
|
| 11 |
-
import statistics
|
| 12 |
-
import os
|
| 13 |
-
from datetime import datetime
|
| 14 |
-
|
| 15 |
-
class CommunicationLatencyTester:
|
| 16 |
-
def __init__(self):
|
| 17 |
-
self.controller_url = "http://localhost:21001"
|
| 18 |
-
self.worker_url = None
|
| 19 |
-
self.results = {
|
| 20 |
-
"http_rest": {
|
| 21 |
-
"controller_requests": [],
|
| 22 |
-
"worker_status_requests": [],
|
| 23 |
-
"streaming_requests": []
|
| 24 |
-
},
|
| 25 |
-
"timestamps": datetime.now().isoformat()
|
| 26 |
-
}
|
| 27 |
-
|
| 28 |
-
def test_controller_communication(self, iterations=10):
|
| 29 |
-
"""Test communication latency with the controller"""
|
| 30 |
-
print("🎯 Testing Controller Communication (HTTP/REST)")
|
| 31 |
-
print("-" * 50)
|
| 32 |
-
|
| 33 |
-
latencies = []
|
| 34 |
-
|
| 35 |
-
# Test 1: List models endpoint
|
| 36 |
-
for i in range(iterations):
|
| 37 |
-
start = time.time()
|
| 38 |
-
try:
|
| 39 |
-
response = requests.post(f"{self.controller_url}/list_models", timeout=5)
|
| 40 |
-
if response.status_code == 200:
|
| 41 |
-
latency_ms = (time.time() - start) * 1000
|
| 42 |
-
latencies.append(latency_ms)
|
| 43 |
-
print(f" Request {i+1:2d}: {latency_ms:.1f}ms")
|
| 44 |
-
else:
|
| 45 |
-
print(f" Request {i+1:2d}: HTTP {response.status_code}")
|
| 46 |
-
except Exception as e:
|
| 47 |
-
print(f" Request {i+1:2d}: Error - {e}")
|
| 48 |
-
continue
|
| 49 |
-
|
| 50 |
-
time.sleep(0.1) # Small delay between requests
|
| 51 |
-
|
| 52 |
-
if latencies:
|
| 53 |
-
avg_latency = statistics.mean(latencies)
|
| 54 |
-
min_latency = min(latencies)
|
| 55 |
-
max_latency = max(latencies)
|
| 56 |
-
std_dev = statistics.stdev(latencies) if len(latencies) > 1 else 0
|
| 57 |
-
|
| 58 |
-
print(f"\n📊 Controller Communication Results:")
|
| 59 |
-
print(f" • Average: {avg_latency:.1f}ms")
|
| 60 |
-
print(f" • Min: {min_latency:.1f}ms")
|
| 61 |
-
print(f" • Max: {max_latency:.1f}ms")
|
| 62 |
-
print(f" • Std Dev: {std_dev:.1f}ms")
|
| 63 |
-
|
| 64 |
-
self.results["http_rest"]["controller_requests"] = {
|
| 65 |
-
"average_ms": avg_latency,
|
| 66 |
-
"min_ms": min_latency,
|
| 67 |
-
"max_ms": max_latency,
|
| 68 |
-
"std_dev_ms": std_dev,
|
| 69 |
-
"samples": latencies
|
| 70 |
-
}
|
| 71 |
-
|
| 72 |
-
return latencies
|
| 73 |
-
|
| 74 |
-
def test_worker_discovery(self, iterations=10):
|
| 75 |
-
"""Test worker discovery latency"""
|
| 76 |
-
print("\n🔍 Testing Worker Discovery")
|
| 77 |
-
print("-" * 50)
|
| 78 |
-
|
| 79 |
-
latencies = []
|
| 80 |
-
|
| 81 |
-
for i in range(iterations):
|
| 82 |
-
start = time.time()
|
| 83 |
-
try:
|
| 84 |
-
response = requests.post(
|
| 85 |
-
f"{self.controller_url}/get_worker_address",
|
| 86 |
-
json={"model": "LLaMA-Omni2-1.5B"},
|
| 87 |
-
timeout=5
|
| 88 |
-
)
|
| 89 |
-
if response.status_code == 200:
|
| 90 |
-
worker_data = response.json()
|
| 91 |
-
self.worker_url = worker_data.get("address")
|
| 92 |
-
latency_ms = (time.time() - start) * 1000
|
| 93 |
-
latencies.append(latency_ms)
|
| 94 |
-
print(f" Discovery {i+1:2d}: {latency_ms:.1f}ms")
|
| 95 |
-
else:
|
| 96 |
-
print(f" Discovery {i+1:2d}: HTTP {response.status_code}")
|
| 97 |
-
except Exception as e:
|
| 98 |
-
print(f" Discovery {i+1:2d}: Error - {e}")
|
| 99 |
-
continue
|
| 100 |
-
|
| 101 |
-
time.sleep(0.1)
|
| 102 |
-
|
| 103 |
-
if latencies:
|
| 104 |
-
avg_latency = statistics.mean(latencies)
|
| 105 |
-
print(f"\n📊 Worker Discovery Results:")
|
| 106 |
-
print(f" • Average: {avg_latency:.1f}ms")
|
| 107 |
-
print(f" • Worker: {self.worker_url}")
|
| 108 |
-
|
| 109 |
-
return latencies
|
| 110 |
-
|
| 111 |
-
def test_worker_communication(self, iterations=10):
|
| 112 |
-
"""Test direct worker communication"""
|
| 113 |
-
if not self.worker_url:
|
| 114 |
-
print("\n❌ No worker URL available")
|
| 115 |
-
return []
|
| 116 |
-
|
| 117 |
-
print(f"\n🏭 Testing Worker Communication")
|
| 118 |
-
print(f" Worker: {self.worker_url}")
|
| 119 |
-
print("-" * 50)
|
| 120 |
-
|
| 121 |
-
latencies = []
|
| 122 |
-
|
| 123 |
-
for i in range(iterations):
|
| 124 |
-
start = time.time()
|
| 125 |
-
try:
|
| 126 |
-
response = requests.post(f"{self.worker_url}/worker_get_status", timeout=5)
|
| 127 |
-
if response.status_code == 200:
|
| 128 |
-
latency_ms = (time.time() - start) * 1000
|
| 129 |
-
latencies.append(latency_ms)
|
| 130 |
-
print(f" Status {i+1:2d}: {latency_ms:.1f}ms")
|
| 131 |
-
else:
|
| 132 |
-
print(f" Status {i+1:2d}: HTTP {response.status_code}")
|
| 133 |
-
except Exception as e:
|
| 134 |
-
print(f" Status {i+1:2d}: Error - {e}")
|
| 135 |
-
continue
|
| 136 |
-
|
| 137 |
-
time.sleep(0.1)
|
| 138 |
-
|
| 139 |
-
if latencies:
|
| 140 |
-
avg_latency = statistics.mean(latencies)
|
| 141 |
-
min_latency = min(latencies)
|
| 142 |
-
max_latency = max(latencies)
|
| 143 |
-
std_dev = statistics.stdev(latencies) if len(latencies) > 1 else 0
|
| 144 |
-
|
| 145 |
-
print(f"\n📊 Worker Communication Results:")
|
| 146 |
-
print(f" • Average: {avg_latency:.1f}ms")
|
| 147 |
-
print(f" • Min: {min_latency:.1f}ms")
|
| 148 |
-
print(f" • Max: {max_latency:.1f}ms")
|
| 149 |
-
print(f" • Std Dev: {std_dev:.1f}ms")
|
| 150 |
-
|
| 151 |
-
self.results["http_rest"]["worker_status_requests"] = {
|
| 152 |
-
"average_ms": avg_latency,
|
| 153 |
-
"min_ms": min_latency,
|
| 154 |
-
"max_ms": max_latency,
|
| 155 |
-
"std_dev_ms": std_dev,
|
| 156 |
-
"samples": latencies
|
| 157 |
-
}
|
| 158 |
-
|
| 159 |
-
return latencies
|
| 160 |
-
|
| 161 |
-
def test_streaming_overhead(self, iterations=5):
|
| 162 |
-
"""Test streaming request setup overhead"""
|
| 163 |
-
if not self.worker_url:
|
| 164 |
-
print("\n❌ No worker URL available for streaming test")
|
| 165 |
-
return []
|
| 166 |
-
|
| 167 |
-
print(f"\n🌊 Testing Streaming Request Overhead")
|
| 168 |
-
print("-" * 50)
|
| 169 |
-
|
| 170 |
-
# Use a minimal test audio file
|
| 171 |
-
test_audio_path = "/workspace/llama-omni2-official-code/test_audios/01_Olá.wav"
|
| 172 |
-
if not os.path.exists(test_audio_path):
|
| 173 |
-
print(f"❌ Test audio not found: {test_audio_path}")
|
| 174 |
-
return []
|
| 175 |
-
|
| 176 |
-
latencies = []
|
| 177 |
-
first_response_latencies = []
|
| 178 |
-
|
| 179 |
-
history = [{"role": "user", "content": {"path": test_audio_path}}]
|
| 180 |
-
pload = {
|
| 181 |
-
"model": "LLaMA-Omni2-1.5B",
|
| 182 |
-
"history": history,
|
| 183 |
-
"temperature": 0.7,
|
| 184 |
-
"top_p": 0.9,
|
| 185 |
-
"max_new_tokens": 20 # Minimal for testing
|
| 186 |
-
}
|
| 187 |
-
|
| 188 |
-
for i in range(iterations):
|
| 189 |
-
print(f" Stream {i+1:2d}: ", end="", flush=True)
|
| 190 |
-
|
| 191 |
-
request_start = time.time()
|
| 192 |
-
try:
|
| 193 |
-
response = requests.post(
|
| 194 |
-
f"{self.worker_url}/worker_generate_stream",
|
| 195 |
-
json=pload,
|
| 196 |
-
stream=True,
|
| 197 |
-
timeout=30
|
| 198 |
-
)
|
| 199 |
-
|
| 200 |
-
# Measure time to establish connection and get first chunk
|
| 201 |
-
first_chunk_received = False
|
| 202 |
-
for chunk in response.iter_lines(decode_unicode=False, delimiter=b"\0"):
|
| 203 |
-
if chunk:
|
| 204 |
-
first_response_time = time.time()
|
| 205 |
-
if not first_chunk_received:
|
| 206 |
-
connection_latency = (first_response_time - request_start) * 1000
|
| 207 |
-
latencies.append(connection_latency)
|
| 208 |
-
first_chunk_received = True
|
| 209 |
-
|
| 210 |
-
# Check if this chunk contains actual response
|
| 211 |
-
try:
|
| 212 |
-
data = json.loads(chunk.decode())
|
| 213 |
-
if data.get("text") or data.get("error_code", 0) != 0:
|
| 214 |
-
first_response_latencies.append(connection_latency)
|
| 215 |
-
print(f"Connection: {connection_latency:.1f}ms")
|
| 216 |
-
break
|
| 217 |
-
except:
|
| 218 |
-
pass
|
| 219 |
-
|
| 220 |
-
# Stop after first chunk for this test
|
| 221 |
-
break
|
| 222 |
-
|
| 223 |
-
response.close()
|
| 224 |
-
|
| 225 |
-
except Exception as e:
|
| 226 |
-
print(f"Error - {e}")
|
| 227 |
-
continue
|
| 228 |
-
|
| 229 |
-
time.sleep(1) # Longer delay for streaming tests
|
| 230 |
-
|
| 231 |
-
if latencies:
|
| 232 |
-
avg_latency = statistics.mean(latencies)
|
| 233 |
-
min_latency = min(latencies)
|
| 234 |
-
max_latency = max(latencies)
|
| 235 |
-
|
| 236 |
-
print(f"\n📊 Streaming Setup Overhead:")
|
| 237 |
-
print(f" • Average: {avg_latency:.1f}ms")
|
| 238 |
-
print(f" • Min: {min_latency:.1f}ms")
|
| 239 |
-
print(f" • Max: {max_latency:.1f}ms")
|
| 240 |
-
|
| 241 |
-
self.results["http_rest"]["streaming_requests"] = {
|
| 242 |
-
"average_ms": avg_latency,
|
| 243 |
-
"min_ms": min_latency,
|
| 244 |
-
"max_ms": max_latency,
|
| 245 |
-
"samples": latencies
|
| 246 |
-
}
|
| 247 |
-
|
| 248 |
-
return latencies
|
| 249 |
-
|
| 250 |
-
def run_comprehensive_test(self):
|
| 251 |
-
"""Run all communication tests"""
|
| 252 |
-
print("🚀 LLaMA-Omni2 Communication Latency Analysis")
|
| 253 |
-
print("=" * 60)
|
| 254 |
-
|
| 255 |
-
# Run all tests
|
| 256 |
-
controller_latencies = self.test_controller_communication()
|
| 257 |
-
worker_discovery_latencies = self.test_worker_discovery()
|
| 258 |
-
worker_latencies = self.test_worker_communication()
|
| 259 |
-
streaming_latencies = self.test_streaming_overhead()
|
| 260 |
-
|
| 261 |
-
return self.generate_report()
|
| 262 |
-
|
| 263 |
-
def generate_report(self):
|
| 264 |
-
"""Generate comprehensive comparison report"""
|
| 265 |
-
print("\n" + "=" * 60)
|
| 266 |
-
print("📊 COMPREHENSIVE COMMUNICATION ANALYSIS")
|
| 267 |
-
print("=" * 60)
|
| 268 |
-
|
| 269 |
-
# Current HTTP/REST metrics
|
| 270 |
-
controller_avg = self.results["http_rest"]["controller_requests"].get("average_ms", 0)
|
| 271 |
-
worker_avg = self.results["http_rest"]["worker_status_requests"].get("average_ms", 0)
|
| 272 |
-
streaming_avg = self.results["http_rest"]["streaming_requests"].get("average_ms", 0)
|
| 273 |
-
|
| 274 |
-
# Calculate typical request flow latency
|
| 275 |
-
typical_flow_latency = controller_avg + worker_avg
|
| 276 |
-
|
| 277 |
-
print(f"\n🌐 Current HTTP/REST Communication:")
|
| 278 |
-
print(f" • Controller requests: {controller_avg:.1f}ms")
|
| 279 |
-
print(f" • Worker status requests: {worker_avg:.1f}ms")
|
| 280 |
-
print(f" • Streaming setup: {streaming_avg:.1f}ms")
|
| 281 |
-
print(f" • Typical request flow: {typical_flow_latency:.1f}ms")
|
| 282 |
-
|
| 283 |
-
# gRPC comparison (using your mentioned ~50ms)
|
| 284 |
-
grpc_latency = 50.0 # ms per request as mentioned
|
| 285 |
-
|
| 286 |
-
print(f"\n⚡ gRPC Communication (Reference):")
|
| 287 |
-
print(f" • Per request latency: ~{grpc_latency:.1f}ms")
|
| 288 |
-
|
| 289 |
-
# Calculate improvements
|
| 290 |
-
if typical_flow_latency > 0:
|
| 291 |
-
improvement = ((typical_flow_latency - grpc_latency) / typical_flow_latency) * 100
|
| 292 |
-
speedup = typical_flow_latency / grpc_latency
|
| 293 |
-
else:
|
| 294 |
-
improvement = 0
|
| 295 |
-
speedup = 0
|
| 296 |
-
|
| 297 |
-
print(f"\n📈 Performance Comparison:")
|
| 298 |
-
print(f" • Current total latency: {typical_flow_latency:.1f}ms")
|
| 299 |
-
print(f" • gRPC total latency: ~{grpc_latency:.1f}ms")
|
| 300 |
-
if improvement > 0:
|
| 301 |
-
print(f" • Potential improvement: {improvement:.1f}%")
|
| 302 |
-
print(f" • Speed increase: {speedup:.1f}x faster")
|
| 303 |
-
else:
|
| 304 |
-
print(f" • Current system is already optimized")
|
| 305 |
-
|
| 306 |
-
# Detailed breakdown table
|
| 307 |
-
print(f"\n📋 Detailed Latency Breakdown:")
|
| 308 |
-
print("-" * 60)
|
| 309 |
-
print(f"{'Operation':<25} {'HTTP/REST':<12} {'gRPC (est.)':<12} {'Improvement':<11}")
|
| 310 |
-
print("-" * 60)
|
| 311 |
-
print(f"{'Controller query':<25} {controller_avg:>8.1f}ms {grpc_latency*0.8:>9.1f}ms {((controller_avg - grpc_latency*0.8)/controller_avg*100):>8.1f}%")
|
| 312 |
-
print(f"{'Worker discovery':<25} {worker_avg:>8.1f}ms {grpc_latency*0.6:>9.1f}ms {((worker_avg - grpc_latency*0.6)/worker_avg*100) if worker_avg > 0 else 0:>8.1f}%")
|
| 313 |
-
print(f"{'Streaming setup':<25} {streaming_avg:>8.1f}ms {grpc_latency*0.4:>9.1f}ms {((streaming_avg - grpc_latency*0.4)/streaming_avg*100) if streaming_avg > 0 else 0:>8.1f}%")
|
| 314 |
-
print(f"{'Total per request':<25} {typical_flow_latency:>8.1f}ms {grpc_latency:>9.1f}ms {improvement:>8.1f}%")
|
| 315 |
-
|
| 316 |
-
# Recommendations
|
| 317 |
-
print(f"\n💡 Recommendations:")
|
| 318 |
-
if improvement > 30:
|
| 319 |
-
print(" 🚀 HIGH IMPACT: gRPC migration would provide significant improvement")
|
| 320 |
-
print(f" 📈 Expected latency reduction: {typical_flow_latency:.1f}ms → {grpc_latency:.1f}ms")
|
| 321 |
-
elif improvement > 10:
|
| 322 |
-
print(" ✅ MODERATE IMPACT: gRPC migration recommended")
|
| 323 |
-
else:
|
| 324 |
-
print(" 📊 LOW IMPACT: Current HTTP/REST performance is acceptable")
|
| 325 |
-
|
| 326 |
-
print(f"\n🔍 Current Communication Method Details:")
|
| 327 |
-
print(f" • Protocol: HTTP/1.1 REST")
|
| 328 |
-
print(f" • Transport: TCP over HTTP")
|
| 329 |
-
print(f" • Serialization: JSON")
|
| 330 |
-
print(f" • Connection: Request/Response per operation")
|
| 331 |
-
print(f" • Streaming: HTTP chunked transfer encoding")
|
| 332 |
-
|
| 333 |
-
# Save results
|
| 334 |
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 335 |
-
output_file = f"/workspace/communication_latency_analysis_{timestamp}.json"
|
| 336 |
-
|
| 337 |
-
with open(output_file, 'w') as f:
|
| 338 |
-
json.dump(self.results, f, indent=2)
|
| 339 |
-
|
| 340 |
-
print(f"\n💾 Results saved to: {output_file}")
|
| 341 |
-
|
| 342 |
-
return {
|
| 343 |
-
"current_http_latency_ms": typical_flow_latency,
|
| 344 |
-
"estimated_grpc_latency_ms": grpc_latency,
|
| 345 |
-
"improvement_percentage": improvement,
|
| 346 |
-
"speed_multiplier": speedup,
|
| 347 |
-
"recommendations": "HIGH IMPACT" if improvement > 30 else "MODERATE IMPACT" if improvement > 10 else "LOW IMPACT"
|
| 348 |
-
}
|
| 349 |
-
|
| 350 |
-
def main():
|
| 351 |
-
"""Main execution function"""
|
| 352 |
-
tester = CommunicationLatencyTester()
|
| 353 |
-
|
| 354 |
-
# Check if services are running
|
| 355 |
-
try:
|
| 356 |
-
response = requests.get("http://localhost:21001", timeout=2)
|
| 357 |
-
except:
|
| 358 |
-
try:
|
| 359 |
-
response = requests.post("http://localhost:21001/list_models", timeout=2)
|
| 360 |
-
except:
|
| 361 |
-
print("❌ Controller service not running on localhost:21001")
|
| 362 |
-
print(" Please start the controller first with:")
|
| 363 |
-
print(" python llama_omni2/serve/controller.py --port 21001")
|
| 364 |
-
return
|
| 365 |
-
|
| 366 |
-
results = tester.run_comprehensive_test()
|
| 367 |
-
return results
|
| 368 |
-
|
| 369 |
-
if __name__ == "__main__":
|
| 370 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
create_real_speech.py
DELETED
|
@@ -1,100 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Criar áudio de fala real em português usando TTS
|
| 4 |
-
===============================================
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
import torch
|
| 8 |
-
import torchaudio
|
| 9 |
-
import numpy as np
|
| 10 |
-
import wave
|
| 11 |
-
from transformers import SpeechT5Processor, SpeechT5ForTextToSpeech, SpeechT5HifiGan
|
| 12 |
-
import os
|
| 13 |
-
|
| 14 |
-
def create_speech_sample():
|
| 15 |
-
"""Cria amostra de fala em português"""
|
| 16 |
-
|
| 17 |
-
# Texto de teste em português
|
| 18 |
-
texts = [
|
| 19 |
-
"Qual é a capital do Brasil?",
|
| 20 |
-
"Como funciona a inteligência artificial?",
|
| 21 |
-
"Explique sobre energia renovável",
|
| 22 |
-
"O que você sabe sobre machine learning?"
|
| 23 |
-
]
|
| 24 |
-
|
| 25 |
-
print("🎙️ Criando amostras de fala em português...")
|
| 26 |
-
|
| 27 |
-
# Método alternativo: usar síntese com wavenet/espeak
|
| 28 |
-
for i, text in enumerate(texts):
|
| 29 |
-
output_file = f"/workspace/llama-omni2-troca-llm/test_audios/pergunta_{i+1}.wav"
|
| 30 |
-
|
| 31 |
-
try:
|
| 32 |
-
# Usar espeak para síntese básica
|
| 33 |
-
import subprocess
|
| 34 |
-
cmd = [
|
| 35 |
-
'espeak',
|
| 36 |
-
'-s', '150', # velocidade
|
| 37 |
-
'-v', 'pt-br', # português brasileiro
|
| 38 |
-
'-w', output_file, # arquivo de saída
|
| 39 |
-
text
|
| 40 |
-
]
|
| 41 |
-
|
| 42 |
-
result = subprocess.run(cmd, capture_output=True, text=True)
|
| 43 |
-
|
| 44 |
-
if result.returncode == 0:
|
| 45 |
-
print(f"✅ Criado: {output_file}")
|
| 46 |
-
print(f" Texto: '{text}'")
|
| 47 |
-
else:
|
| 48 |
-
print(f"❌ Erro ao criar {output_file}: {result.stderr}")
|
| 49 |
-
|
| 50 |
-
except Exception as e:
|
| 51 |
-
print(f"❌ Erro: {e}")
|
| 52 |
-
|
| 53 |
-
# Método 2: criar com frequências moduladas (simulando fala)
|
| 54 |
-
def create_speech_like_audio(text, filename):
|
| 55 |
-
"""Cria áudio que simula padrões de fala"""
|
| 56 |
-
sample_rate = 16000
|
| 57 |
-
duration = len(text) * 0.1 + 1.0 # ~0.1s por caractere
|
| 58 |
-
|
| 59 |
-
t = np.linspace(0, duration, int(sample_rate * duration))
|
| 60 |
-
|
| 61 |
-
# Simular formantes básicos da fala (F1, F2, F3)
|
| 62 |
-
f1 = 500 + 200 * np.sin(2 * np.pi * 2 * t) # Varia 300-700Hz
|
| 63 |
-
f2 = 1500 + 300 * np.sin(2 * np.pi * 3 * t) # Varia 1200-1800Hz
|
| 64 |
-
f3 = 2500 + 200 * np.sin(2 * np.pi * 1.5 * t) # Varia 2300-2700Hz
|
| 65 |
-
|
| 66 |
-
# Combinar formantes
|
| 67 |
-
audio = (
|
| 68 |
-
0.4 * np.sin(2 * np.pi * f1 * t) +
|
| 69 |
-
0.3 * np.sin(2 * np.pi * f2 * t) +
|
| 70 |
-
0.2 * np.sin(2 * np.pi * f3 * t)
|
| 71 |
-
)
|
| 72 |
-
|
| 73 |
-
# Adicionar modulação de amplitude (simula prosódia)
|
| 74 |
-
envelope = 0.5 * (1 + np.sin(2 * np.pi * 0.5 * t))
|
| 75 |
-
audio *= envelope
|
| 76 |
-
|
| 77 |
-
# Adicionar pausas (simula palavras)
|
| 78 |
-
word_breaks = np.random.choice([0, 1], size=len(audio), p=[0.95, 0.05])
|
| 79 |
-
audio *= (1 - word_breaks * 0.8)
|
| 80 |
-
|
| 81 |
-
# Normalizar
|
| 82 |
-
audio = np.clip(audio, -1, 1)
|
| 83 |
-
audio_int16 = (audio * 32767).astype(np.int16)
|
| 84 |
-
|
| 85 |
-
# Salvar WAV
|
| 86 |
-
with wave.open(filename, 'wb') as wav:
|
| 87 |
-
wav.setnchannels(1)
|
| 88 |
-
wav.setsampwidth(2)
|
| 89 |
-
wav.setframerate(sample_rate)
|
| 90 |
-
wav.writeframes(audio_int16.tobytes())
|
| 91 |
-
|
| 92 |
-
# Criar versões simuladas
|
| 93 |
-
for i, text in enumerate(texts):
|
| 94 |
-
filename = f"/workspace/llama-omni2-troca-llm/test_audios/speech_sim_{i+1}.wav"
|
| 95 |
-
create_speech_like_audio(text, filename)
|
| 96 |
-
print(f"🔊 Criado áudio simulado: {filename}")
|
| 97 |
-
print(f" Baseado em: '{text}'")
|
| 98 |
-
|
| 99 |
-
if __name__ == "__main__":
|
| 100 |
-
create_speech_sample()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
create_test_audio.py
DELETED
|
@@ -1,94 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Cria um arquivo WAV de teste simples
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
import numpy as np
|
| 7 |
-
import wave
|
| 8 |
-
import struct
|
| 9 |
-
|
| 10 |
-
def create_silence_wav(filename, duration=1.0, sample_rate=16000):
|
| 11 |
-
"""Cria um arquivo WAV com silêncio"""
|
| 12 |
-
|
| 13 |
-
# Gerar amostras (silêncio com um pequeno ruído)
|
| 14 |
-
num_samples = int(duration * sample_rate)
|
| 15 |
-
samples = np.random.normal(0, 0.001, num_samples) # Ruído muito baixo
|
| 16 |
-
|
| 17 |
-
# Converter para int16
|
| 18 |
-
samples = (samples * 32767).astype(np.int16)
|
| 19 |
-
|
| 20 |
-
# Criar arquivo WAV
|
| 21 |
-
with wave.open(filename, 'wb') as wav:
|
| 22 |
-
wav.setnchannels(1) # Mono
|
| 23 |
-
wav.setsampwidth(2) # 16 bits
|
| 24 |
-
wav.setframerate(sample_rate)
|
| 25 |
-
wav.writeframes(samples.tobytes())
|
| 26 |
-
|
| 27 |
-
print(f"✅ Arquivo criado: {filename}")
|
| 28 |
-
|
| 29 |
-
def create_tone_wav(filename, frequency=440, duration=1.0, sample_rate=16000):
|
| 30 |
-
"""Cria um arquivo WAV com um tom simples"""
|
| 31 |
-
|
| 32 |
-
# Gerar onda senoidal
|
| 33 |
-
t = np.linspace(0, duration, int(sample_rate * duration))
|
| 34 |
-
samples = np.sin(2 * np.pi * frequency * t)
|
| 35 |
-
|
| 36 |
-
# Aplicar envelope para evitar cliques
|
| 37 |
-
envelope = np.ones_like(samples)
|
| 38 |
-
fade_samples = int(0.05 * sample_rate) # 50ms fade
|
| 39 |
-
envelope[:fade_samples] = np.linspace(0, 1, fade_samples)
|
| 40 |
-
envelope[-fade_samples:] = np.linspace(1, 0, fade_samples)
|
| 41 |
-
samples *= envelope
|
| 42 |
-
|
| 43 |
-
# Converter para int16
|
| 44 |
-
samples = (samples * 0.5 * 32767).astype(np.int16)
|
| 45 |
-
|
| 46 |
-
# Criar arquivo WAV
|
| 47 |
-
with wave.open(filename, 'wb') as wav:
|
| 48 |
-
wav.setnchannels(1) # Mono
|
| 49 |
-
wav.setsampwidth(2) # 16 bits
|
| 50 |
-
wav.setframerate(sample_rate)
|
| 51 |
-
wav.writeframes(samples.tobytes())
|
| 52 |
-
|
| 53 |
-
print(f"✅ Arquivo criado: {filename}")
|
| 54 |
-
|
| 55 |
-
if __name__ == "__main__":
|
| 56 |
-
import os
|
| 57 |
-
|
| 58 |
-
# Criar diretório se não existir
|
| 59 |
-
os.makedirs("test_audios", exist_ok=True)
|
| 60 |
-
|
| 61 |
-
# Criar arquivos de teste
|
| 62 |
-
create_silence_wav("test_audios/silence.wav", duration=1.0)
|
| 63 |
-
create_tone_wav("test_audios/tone_440hz.wav", frequency=440, duration=0.5)
|
| 64 |
-
create_tone_wav("test_audios/tone_880hz.wav", frequency=880, duration=0.5)
|
| 65 |
-
|
| 66 |
-
# Criar "fala" simulada (ruído modulado)
|
| 67 |
-
t = np.linspace(0, 2, 32000)
|
| 68 |
-
speech = np.random.normal(0, 0.3, 32000)
|
| 69 |
-
|
| 70 |
-
# Modular com envelope de "fala"
|
| 71 |
-
envelope = np.sin(2 * np.pi * 2 * t) * 0.5 + 0.5 # Modulação lenta
|
| 72 |
-
speech *= envelope
|
| 73 |
-
|
| 74 |
-
# Adicionar algumas formantes simuladas
|
| 75 |
-
for freq in [700, 1220, 2600]: # Formantes típicas
|
| 76 |
-
speech += np.sin(2 * np.pi * freq * t) * 0.1 * envelope
|
| 77 |
-
|
| 78 |
-
# Normalizar e converter
|
| 79 |
-
speech = np.clip(speech, -1, 1)
|
| 80 |
-
speech = (speech * 0.7 * 32767).astype(np.int16)
|
| 81 |
-
|
| 82 |
-
with wave.open("test_audios/fake_speech.wav", 'wb') as wav:
|
| 83 |
-
wav.setnchannels(1)
|
| 84 |
-
wav.setsampwidth(2)
|
| 85 |
-
wav.setframerate(16000)
|
| 86 |
-
wav.writeframes(speech.tobytes())
|
| 87 |
-
|
| 88 |
-
print("✅ Arquivo criado: test_audios/fake_speech.wav")
|
| 89 |
-
|
| 90 |
-
print("\n📁 Arquivos de teste criados:")
|
| 91 |
-
for f in os.listdir("test_audios"):
|
| 92 |
-
if f.endswith(".wav"):
|
| 93 |
-
size = os.path.getsize(f"test_audios/{f}")
|
| 94 |
-
print(f" • {f} ({size} bytes)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docs/A1_VOCABULARY_CONTROL_TECHNIQUES.md
DELETED
|
@@ -1,587 +0,0 @@
|
|
| 1 |
-
# 📚 Técnicas de Controle de Vocabulário A1 para LLMs
|
| 2 |
-
|
| 3 |
-
## Sumário
|
| 4 |
-
- [Introdução](#introdução)
|
| 5 |
-
- [O Desafio do Vocabulário A1](#o-desafio-do-vocabulário-a1)
|
| 6 |
-
- [Técnicas de Controle](#técnicas-de-controle)
|
| 7 |
-
- [1. Controle por Token IDs](#1-controle-por-token-ids-whitelistblacklist)
|
| 8 |
-
- [2. Decodificação Estruturada](#2-decodificação-estruturada-fsmtrie)
|
| 9 |
-
- [3. Pós-processamento e Simplificação](#3-pós-processamento-e-simplificação)
|
| 10 |
-
- [4. Prompt Engineering Avançado](#4-prompt-engineering-avançado)
|
| 11 |
-
- [5. Fine-tuning Específico](#5-fine-tuning-específico)
|
| 12 |
-
- [6. Abordagens Híbridas](#6-abordagens-híbridas)
|
| 13 |
-
- [Comparação Técnica](#comparação-técnica)
|
| 14 |
-
- [Implementação com vLLM](#implementação-com-vllm)
|
| 15 |
-
- [Métricas de Validação](#métricas-de-validação)
|
| 16 |
-
- [Referências Acadêmicas](#referências-acadêmicas)
|
| 17 |
-
|
| 18 |
-
## Introdução
|
| 19 |
-
|
| 20 |
-
Este documento apresenta técnicas pesquisadas e validadas para controlar o vocabulário de Large Language Models (LLMs) ao nível A1 do CEFR (Common European Framework of Reference for Languages). O objetivo é garantir que as respostas geradas sejam apropriadas para iniciantes no aprendizado de idiomas.
|
| 21 |
-
|
| 22 |
-
## O Desafio do Vocabulário A1
|
| 23 |
-
|
| 24 |
-
### Definição do Nível A1
|
| 25 |
-
- **Vocabulário**: 400-750 palavras mais comuns
|
| 26 |
-
- **Estruturas**: Presente simples, frases curtas (5-10 palavras)
|
| 27 |
-
- **Complexidade**: Textos muito simples, vocabulário cotidiano básico
|
| 28 |
-
|
| 29 |
-
### Problemas Identificados
|
| 30 |
-
1. **Token Misalignment**: Tokens de LLMs não correspondem diretamente a palavras
|
| 31 |
-
2. **Drift Natural**: LLMs tendem a usar vocabulário mais complexo mesmo quando instruídos
|
| 32 |
-
3. **Trade-off Performance**: Restrições muito rígidas podem prejudicar a coerência
|
| 33 |
-
4. **Ambiguidade CEFR**: Difícil distinguir entre A1 e A2 computacionalmente
|
| 34 |
-
|
| 35 |
-
## Técnicas de Controle
|
| 36 |
-
|
| 37 |
-
### 1. Controle por Token IDs (Whitelist/Blacklist)
|
| 38 |
-
|
| 39 |
-
#### Como Funciona
|
| 40 |
-
Restringe diretamente quais tokens o modelo pode gerar através de manipulação de logits.
|
| 41 |
-
|
| 42 |
-
```python
|
| 43 |
-
# Exemplo conceitual com vLLM
|
| 44 |
-
from vllm import SamplingParams
|
| 45 |
-
|
| 46 |
-
# Lista de token IDs permitidos (vocabulário A1)
|
| 47 |
-
a1_token_ids = [...] # IDs dos tokens que formam palavras A1
|
| 48 |
-
|
| 49 |
-
sampling_params = SamplingParams(
|
| 50 |
-
allowed_token_ids=a1_token_ids, # Whitelist
|
| 51 |
-
max_tokens=20,
|
| 52 |
-
temperature=0.0
|
| 53 |
-
)
|
| 54 |
-
```
|
| 55 |
-
|
| 56 |
-
#### Técnicas Disponíveis
|
| 57 |
-
- **`allowed_token_ids`**: Lista branca de tokens permitidos (vLLM nativo)
|
| 58 |
-
- **`logit_bias`**: Penaliza ou favorece tokens específicos
|
| 59 |
-
- **Logit Masking**: Zera probabilidade de tokens não permitidos
|
| 60 |
-
|
| 61 |
-
#### Vantagens
|
| 62 |
-
- ✅ Controle absoluto sobre output
|
| 63 |
-
- ✅ Suporte nativo no vLLM
|
| 64 |
-
- ✅ Latência mínima (+5ms)
|
| 65 |
-
- ✅ Garantia de vocabulário controlado
|
| 66 |
-
|
| 67 |
-
#### Desvantagens
|
| 68 |
-
- ❌ Tokens ≠ palavras (problema com subpalavras)
|
| 69 |
-
- ❌ Pode gerar texto truncado ou mal-formado
|
| 70 |
-
- ❌ Complexo mapear vocabulário A1 → token IDs
|
| 71 |
-
- ❌ Risco de "travamento" do modelo
|
| 72 |
-
|
| 73 |
-
### 2. Decodificação Estruturada (FSM/Trie)
|
| 74 |
-
|
| 75 |
-
#### Como Funciona
|
| 76 |
-
Usa estruturas de dados para guiar a geração palavra por palavra.
|
| 77 |
-
|
| 78 |
-
#### FSM (Finite State Machine)
|
| 79 |
-
```python
|
| 80 |
-
# Exemplo conceitual
|
| 81 |
-
class A1_FSM:
|
| 82 |
-
def __init__(self, vocabulary):
|
| 83 |
-
self.states = self.build_fsm(vocabulary)
|
| 84 |
-
|
| 85 |
-
def get_valid_tokens(self, current_state):
|
| 86 |
-
"""Retorna tokens válidos para o estado atual"""
|
| 87 |
-
return self.states[current_state].valid_transitions
|
| 88 |
-
```
|
| 89 |
-
|
| 90 |
-
#### Trie (Árvore de Prefixos)
|
| 91 |
-
```python
|
| 92 |
-
# Estrutura de dados para palavras A1
|
| 93 |
-
class A1_Trie:
|
| 94 |
-
def __init__(self):
|
| 95 |
-
self.root = TrieNode()
|
| 96 |
-
self.load_a1_vocabulary()
|
| 97 |
-
|
| 98 |
-
def get_valid_continuations(self, prefix):
|
| 99 |
-
"""Retorna continuações válidas para o prefixo"""
|
| 100 |
-
# Navega na árvore e retorna palavras possíveis
|
| 101 |
-
```
|
| 102 |
-
|
| 103 |
-
#### Constrained Beam Search
|
| 104 |
-
- Mantém múltiplos caminhos de decodificação
|
| 105 |
-
- Valida cada caminho contra vocabulário A1
|
| 106 |
-
- Escolhe melhor caminho válido
|
| 107 |
-
|
| 108 |
-
#### Vantagens
|
| 109 |
-
- ✅ Funciona no nível de palavras completas
|
| 110 |
-
- ✅ Garante output gramaticalmente válido
|
| 111 |
-
- ✅ Pode incluir regras sintáticas
|
| 112 |
-
- ✅ 99% de garantia de conformidade A1
|
| 113 |
-
|
| 114 |
-
#### Desvantagens
|
| 115 |
-
- ❌ Complexidade alta de implementação
|
| 116 |
-
- ❌ Overhead de 20-50ms na latência
|
| 117 |
-
- ❌ Requer bibliotecas externas (Outlines, Guidance)
|
| 118 |
-
- ❌ Pode reduzir naturalidade
|
| 119 |
-
|
| 120 |
-
#### Performance Otimizada (Pesquisas Recentes)
|
| 121 |
-
- **FSM Comprimido**: 2x mais rápido que implementações antigas
|
| 122 |
-
- **Jump-forward Decoding**: Pula sequências determinísticas
|
| 123 |
-
- **Trie com Cache Compartilhado**: 70% economia de memória
|
| 124 |
-
|
| 125 |
-
### 3. Pós-processamento e Simplificação
|
| 126 |
-
|
| 127 |
-
#### Como Funciona
|
| 128 |
-
Permite geração normal, depois simplifica o texto.
|
| 129 |
-
|
| 130 |
-
```python
|
| 131 |
-
def simplify_to_a1(text):
|
| 132 |
-
"""
|
| 133 |
-
Simplifica texto para nível A1
|
| 134 |
-
"""
|
| 135 |
-
# 1. Substituição de sinônimos
|
| 136 |
-
text = replace_complex_words(text)
|
| 137 |
-
|
| 138 |
-
# 2. Quebra de frases longas
|
| 139 |
-
text = split_long_sentences(text)
|
| 140 |
-
|
| 141 |
-
# 3. Remoção de estruturas complexas
|
| 142 |
-
text = remove_subordinate_clauses(text)
|
| 143 |
-
|
| 144 |
-
return text
|
| 145 |
-
```
|
| 146 |
-
|
| 147 |
-
#### Técnicas de Simplificação
|
| 148 |
-
|
| 149 |
-
##### Substituição de Sinônimos
|
| 150 |
-
```python
|
| 151 |
-
a1_synonyms = {
|
| 152 |
-
"inteligência": "mente",
|
| 153 |
-
"artificial": "falso",
|
| 154 |
-
"disponível": "livre",
|
| 155 |
-
"compreender": "entender",
|
| 156 |
-
"utilizar": "usar",
|
| 157 |
-
# ... centenas de mapeamentos
|
| 158 |
-
}
|
| 159 |
-
```
|
| 160 |
-
|
| 161 |
-
##### Reestruturação Sintática
|
| 162 |
-
- Converte voz passiva → ativa
|
| 163 |
-
- Remove orações subordinadas
|
| 164 |
-
- Simplifica tempos verbais para presente
|
| 165 |
-
|
| 166 |
-
##### Segmentação de Frases
|
| 167 |
-
- Quebra frases com mais de 10 palavras
|
| 168 |
-
- Remove conectivos complexos
|
| 169 |
-
- Mantém apenas sujeito-verbo-objeto
|
| 170 |
-
|
| 171 |
-
#### Vantagens
|
| 172 |
-
- ✅ Mantém fluência natural
|
| 173 |
-
- ✅ Implementação simples
|
| 174 |
-
- ✅ Baixo overhead (+10ms)
|
| 175 |
-
- ✅ Não afeta processo de geração
|
| 176 |
-
|
| 177 |
-
#### Desvantagens
|
| 178 |
-
- ❌ Não garante 100% vocabulário A1
|
| 179 |
-
- ❌ Pode alterar significado original
|
| 180 |
-
- ❌ Requer dicionário extenso e confiável
|
| 181 |
-
- ❌ Difícil validar correção semântica
|
| 182 |
-
|
| 183 |
-
### 4. Prompt Engineering Avançado
|
| 184 |
-
|
| 185 |
-
#### Como Funciona
|
| 186 |
-
Usa técnicas sofisticadas de prompt para guiar o modelo.
|
| 187 |
-
|
| 188 |
-
#### Plan-Driven Simplification
|
| 189 |
-
```python
|
| 190 |
-
prompt_plan = """
|
| 191 |
-
Primeiro, crie um plano para responder em nível A1:
|
| 192 |
-
1. Identifique conceitos principais
|
| 193 |
-
2. Liste palavras simples para usar
|
| 194 |
-
3. Planeje frases curtas
|
| 195 |
-
|
| 196 |
-
Agora execute o plano:
|
| 197 |
-
"""
|
| 198 |
-
```
|
| 199 |
-
|
| 200 |
-
#### Summary-Guided Generation
|
| 201 |
-
```python
|
| 202 |
-
prompt_summary = """
|
| 203 |
-
Resumo A1: [gerar resumo simples primeiro]
|
| 204 |
-
Agora expanda o resumo usando apenas:
|
| 205 |
-
- Palavras do resumo
|
| 206 |
-
- Presente simples
|
| 207 |
-
- Frases de 5-10 palavras
|
| 208 |
-
"""
|
| 209 |
-
```
|
| 210 |
-
|
| 211 |
-
#### Few-Shot com Exemplos A1
|
| 212 |
-
```python
|
| 213 |
-
prompt_fewshot = """
|
| 214 |
-
Exemplos de respostas A1:
|
| 215 |
-
P: Onde você mora?
|
| 216 |
-
R: Eu moro em São Paulo.
|
| 217 |
-
|
| 218 |
-
P: O que você come?
|
| 219 |
-
R: Eu como pão e café.
|
| 220 |
-
|
| 221 |
-
Agora responda similarmente:
|
| 222 |
-
P: {pergunta}
|
| 223 |
-
R:
|
| 224 |
-
"""
|
| 225 |
-
```
|
| 226 |
-
|
| 227 |
-
#### Chain-of-Thought para Simplicidade
|
| 228 |
-
```python
|
| 229 |
-
prompt_cot = """
|
| 230 |
-
Pense passo a passo para responder simplesmente:
|
| 231 |
-
1. Qual é a resposta básica?
|
| 232 |
-
2. Quais palavras simples usar?
|
| 233 |
-
3. Como fazer uma frase curta?
|
| 234 |
-
|
| 235 |
-
Resposta A1:
|
| 236 |
-
"""
|
| 237 |
-
```
|
| 238 |
-
|
| 239 |
-
#### Vantagens
|
| 240 |
-
- ✅ Zero mudança no código
|
| 241 |
-
- ✅ Muito flexível e adaptável
|
| 242 |
-
- ✅ Funciona com qualquer modelo
|
| 243 |
-
- ✅ Preserva naturalidade
|
| 244 |
-
|
| 245 |
-
#### Desvantagens
|
| 246 |
-
- ❌ Não é 100% confiável
|
| 247 |
-
- ❌ Resultados variam entre execuções
|
| 248 |
-
- ❌ Depende da capacidade do modelo
|
| 249 |
-
- ❌ Difícil garantir consistência
|
| 250 |
-
|
| 251 |
-
### 5. Fine-tuning Específico
|
| 252 |
-
|
| 253 |
-
#### Como Funciona
|
| 254 |
-
Re-treina o modelo com corpus exclusivamente A1.
|
| 255 |
-
|
| 256 |
-
#### Abordagens de Treinamento
|
| 257 |
-
|
| 258 |
-
##### Supervised Fine-Tuning (SFT)
|
| 259 |
-
```python
|
| 260 |
-
# Dataset de treinamento A1
|
| 261 |
-
training_data = [
|
| 262 |
-
{"input": "Como você está?", "output": "Eu estou bem."},
|
| 263 |
-
{"input": "Onde mora?", "output": "Moro no Brasil."},
|
| 264 |
-
# ... milhares de exemplos A1
|
| 265 |
-
]
|
| 266 |
-
```
|
| 267 |
-
|
| 268 |
-
##### Reinforcement Learning (RL)
|
| 269 |
-
- **Reward**: Pontuação por simplicidade
|
| 270 |
-
- **Penalty**: Punição por vocabulário complexo
|
| 271 |
-
- **Métodos**: PPO, DPO, RLHF
|
| 272 |
-
|
| 273 |
-
##### LoRA/QLoRA (Efficient Fine-tuning)
|
| 274 |
-
```python
|
| 275 |
-
# Ajuste eficiente sem re-treinar todo o modelo
|
| 276 |
-
lora_config = {
|
| 277 |
-
"r": 8, # rank
|
| 278 |
-
"lora_alpha": 16,
|
| 279 |
-
"target_modules": ["q_proj", "v_proj"],
|
| 280 |
-
"lora_dropout": 0.1
|
| 281 |
-
}
|
| 282 |
-
```
|
| 283 |
-
|
| 284 |
-
#### Modelo CaLM (State-of-the-Art)
|
| 285 |
-
Pesquisa recente desenvolveu o modelo CaLM (CEFR-Aligned Language Model) que:
|
| 286 |
-
- Superou GPT-4 em geração CEFR-aligned
|
| 287 |
-
- Usa combination de SFT + RL
|
| 288 |
-
- Dataset com 10k+ exemplos por nível
|
| 289 |
-
|
| 290 |
-
#### Vantagens
|
| 291 |
-
- ✅ Resultado mais natural
|
| 292 |
-
- ✅ Zero overhead na inferência
|
| 293 |
-
- ✅ Modelo "aprende" o nível A1
|
| 294 |
-
- ✅ Consistência alta entre gerações
|
| 295 |
-
|
| 296 |
-
#### Desvantagens
|
| 297 |
-
- ❌ Requer dataset grande (10k+ exemplos)
|
| 298 |
-
- ❌ Caro computacionalmente (GPU + tempo)
|
| 299 |
-
- ❌ Pode degradar outras capacidades
|
| 300 |
-
- ❌ Precisa re-treinar para cada idioma
|
| 301 |
-
|
| 302 |
-
### 6. Abordagens Híbridas
|
| 303 |
-
|
| 304 |
-
#### Como Funciona
|
| 305 |
-
Combina múltiplas técnicas para maximizar benefícios.
|
| 306 |
-
|
| 307 |
-
#### Exemplo 1: Soft + Hard Constraints
|
| 308 |
-
```python
|
| 309 |
-
def generate_a1_hybrid(prompt):
|
| 310 |
-
# Tenta primeiro com constraints soft
|
| 311 |
-
response = generate_with_soft_constraints(
|
| 312 |
-
prompt,
|
| 313 |
-
temperature=0.3,
|
| 314 |
-
top_p=0.4
|
| 315 |
-
)
|
| 316 |
-
|
| 317 |
-
# Valida resultado
|
| 318 |
-
if not is_a1_compliant(response):
|
| 319 |
-
# Fallback para constraints hard
|
| 320 |
-
response = generate_with_token_whitelist(
|
| 321 |
-
prompt,
|
| 322 |
-
allowed_tokens=a1_tokens
|
| 323 |
-
)
|
| 324 |
-
|
| 325 |
-
return response
|
| 326 |
-
```
|
| 327 |
-
|
| 328 |
-
#### Exemplo 2: Generate-then-Simplify
|
| 329 |
-
```python
|
| 330 |
-
def generate_and_simplify(prompt):
|
| 331 |
-
# 1. Geração normal para coerência
|
| 332 |
-
response = generate_normal(prompt)
|
| 333 |
-
|
| 334 |
-
# 2. Simplificação com outro prompt
|
| 335 |
-
simplified = simplify_with_llm(response, target="A1")
|
| 336 |
-
|
| 337 |
-
# 3. Validação final
|
| 338 |
-
if validate_a1(simplified):
|
| 339 |
-
return simplified
|
| 340 |
-
else:
|
| 341 |
-
return fallback_template_response()
|
| 342 |
-
```
|
| 343 |
-
|
| 344 |
-
#### Exemplo 3: Multi-Stage Pipeline
|
| 345 |
-
```python
|
| 346 |
-
def multi_stage_a1_generation(prompt):
|
| 347 |
-
# Stage 1: Planning
|
| 348 |
-
plan = generate_a1_plan(prompt)
|
| 349 |
-
|
| 350 |
-
# Stage 2: Vocabulary Selection
|
| 351 |
-
vocab = select_a1_vocabulary(plan)
|
| 352 |
-
|
| 353 |
-
# Stage 3: Constrained Generation
|
| 354 |
-
response = generate_with_vocabulary(plan, vocab)
|
| 355 |
-
|
| 356 |
-
# Stage 4: Post-processing
|
| 357 |
-
final = post_process_a1(response)
|
| 358 |
-
|
| 359 |
-
return final
|
| 360 |
-
```
|
| 361 |
-
|
| 362 |
-
#### Vantagens
|
| 363 |
-
- ✅ Combina benefícios de múltiplas técnicas
|
| 364 |
-
- ✅ Fallbacks para maior confiabilidade
|
| 365 |
-
- ✅ Flexibilidade para ajustes
|
| 366 |
-
- ✅ Melhor trade-off naturalidade/controle
|
| 367 |
-
|
| 368 |
-
#### Desvantagens
|
| 369 |
-
- ❌ Complexidade de implementação
|
| 370 |
-
- ❌ Múltiplos pontos de falha
|
| 371 |
-
- ❌ Latência acumulativa
|
| 372 |
-
- ❌ Difícil debugar
|
| 373 |
-
|
| 374 |
-
## Comparação Técnica
|
| 375 |
-
|
| 376 |
-
### Tabela Comparativa
|
| 377 |
-
|
| 378 |
-
| Método | Complexidade | Garantia A1 | Latência Extra | Naturalidade | Memória |
|
| 379 |
-
|--------|-------------|-------------|----------------|--------------|---------|
|
| 380 |
-
| **Token Control** | Média | 95% | +5ms | Baixa | Baixa |
|
| 381 |
-
| **FSM/Trie** | Alta | 99% | +20-50ms | Média | Média |
|
| 382 |
-
| **Pós-processo** | Baixa | 70% | +10ms | Alta | Baixa |
|
| 383 |
-
| **Prompt Eng.** | Baixa | 60% | 0ms | Muito Alta | Baixa |
|
| 384 |
-
| **Fine-tuning** | Muito Alta | 85% | 0ms | Muito Alta | Alta |
|
| 385 |
-
| **Híbrida** | Alta | 90% | +15-30ms | Alta | Média |
|
| 386 |
-
|
| 387 |
-
### Critérios de Escolha
|
| 388 |
-
|
| 389 |
-
#### Use Token Control quando:
|
| 390 |
-
- Precisa garantia máxima de vocabulário
|
| 391 |
-
- Latência é crítica
|
| 392 |
-
- Tem mapeamento tokens-palavras pronto
|
| 393 |
-
|
| 394 |
-
#### Use FSM/Trie quando:
|
| 395 |
-
- Precisa garantia de estrutura gramatical
|
| 396 |
-
- Tem biblioteca de suporte (Outlines, etc.)
|
| 397 |
-
- Aceita overhead de latência
|
| 398 |
-
|
| 399 |
-
#### Use Pós-processamento quando:
|
| 400 |
-
- Quer manter naturalidade
|
| 401 |
-
- Tem bom dicionário de sinônimos
|
| 402 |
-
- Aceita não ter 100% garantia
|
| 403 |
-
|
| 404 |
-
#### Use Prompt Engineering quando:
|
| 405 |
-
- Não pode modificar código
|
| 406 |
-
- Precisa flexibilidade
|
| 407 |
-
- Modelo é suficientemente capaz
|
| 408 |
-
|
| 409 |
-
#### Use Fine-tuning quando:
|
| 410 |
-
- Tem recursos computacionais
|
| 411 |
-
- Precisa solução permanente
|
| 412 |
-
- Tem dataset de qualidade
|
| 413 |
-
|
| 414 |
-
## Implementação com vLLM
|
| 415 |
-
|
| 416 |
-
### Configuração Básica
|
| 417 |
-
|
| 418 |
-
```python
|
| 419 |
-
from vllm import LLM, SamplingParams
|
| 420 |
-
|
| 421 |
-
# Inicialização do modelo
|
| 422 |
-
model = LLM(
|
| 423 |
-
model="path/to/model",
|
| 424 |
-
dtype="float16",
|
| 425 |
-
gpu_memory_utilization=0.90
|
| 426 |
-
)
|
| 427 |
-
|
| 428 |
-
# Parâmetros para A1
|
| 429 |
-
sampling_params_a1 = SamplingParams(
|
| 430 |
-
max_tokens=20, # Respostas curtas
|
| 431 |
-
temperature=0.3, # Baixa criatividade
|
| 432 |
-
top_p=0.4, # Vocabulário restrito
|
| 433 |
-
top_k=100, # Limita opções
|
| 434 |
-
repetition_penalty=1.1, # Evita repetição
|
| 435 |
-
stop=[".", "!", "?"], # Para em pontuação
|
| 436 |
-
# Para whitelist de tokens:
|
| 437 |
-
# allowed_token_ids=[...]
|
| 438 |
-
)
|
| 439 |
-
```
|
| 440 |
-
|
| 441 |
-
### Implementação de Logit Processor Customizado
|
| 442 |
-
|
| 443 |
-
```python
|
| 444 |
-
from typing import List
|
| 445 |
-
import torch
|
| 446 |
-
|
| 447 |
-
class A1VocabularyProcessor:
|
| 448 |
-
def __init__(self, a1_token_ids: List[int]):
|
| 449 |
-
self.a1_tokens = set(a1_token_ids)
|
| 450 |
-
self.penalty = -1000.0 # Penalidade forte
|
| 451 |
-
|
| 452 |
-
def __call__(self, input_ids: torch.Tensor,
|
| 453 |
-
scores: torch.Tensor) -> torch.Tensor:
|
| 454 |
-
"""
|
| 455 |
-
Aplica penalidade a tokens não-A1
|
| 456 |
-
"""
|
| 457 |
-
for token_id in range(scores.shape[-1]):
|
| 458 |
-
if token_id not in self.a1_tokens:
|
| 459 |
-
scores[:, token_id] += self.penalty
|
| 460 |
-
return scores
|
| 461 |
-
|
| 462 |
-
# Uso com vLLM
|
| 463 |
-
a1_processor = A1VocabularyProcessor(a1_token_ids)
|
| 464 |
-
sampling_params = SamplingParams(
|
| 465 |
-
logits_processors=[a1_processor]
|
| 466 |
-
)
|
| 467 |
-
```
|
| 468 |
-
|
| 469 |
-
## Métricas de Validação
|
| 470 |
-
|
| 471 |
-
### Métricas Implementadas
|
| 472 |
-
|
| 473 |
-
#### 1. Flesch Reading Ease (Português)
|
| 474 |
-
```python
|
| 475 |
-
def flesch_portuguese(text):
|
| 476 |
-
"""
|
| 477 |
-
Score > 80 = A1/A2
|
| 478 |
-
Score 60-80 = B1/B2
|
| 479 |
-
Score < 60 = C1/C2
|
| 480 |
-
"""
|
| 481 |
-
return 206.835 - 1.015*(words/sentences) - 84.6*(syllables/words)
|
| 482 |
-
```
|
| 483 |
-
|
| 484 |
-
#### 2. Common Words Ratio
|
| 485 |
-
```python
|
| 486 |
-
def common_words_ratio(text):
|
| 487 |
-
"""
|
| 488 |
-
Percentual de palavras no vocabulário A1
|
| 489 |
-
Target: >= 70%
|
| 490 |
-
"""
|
| 491 |
-
words = text.split()
|
| 492 |
-
common = sum(1 for w in words if w in A1_VOCAB)
|
| 493 |
-
return (common / len(words)) * 100
|
| 494 |
-
```
|
| 495 |
-
|
| 496 |
-
#### 3. Sentence Length Check
|
| 497 |
-
```python
|
| 498 |
-
def check_sentence_length(text):
|
| 499 |
-
"""
|
| 500 |
-
A1: 5-10 palavras por frase
|
| 501 |
-
"""
|
| 502 |
-
sentences = text.split('.')
|
| 503 |
-
lengths = [len(s.split()) for s in sentences]
|
| 504 |
-
return all(5 <= l <= 10 for l in lengths)
|
| 505 |
-
```
|
| 506 |
-
|
| 507 |
-
#### 4. Brunet Index
|
| 508 |
-
```python
|
| 509 |
-
def brunet_index(text):
|
| 510 |
-
"""
|
| 511 |
-
Complexidade lexical
|
| 512 |
-
Target A1: < 15
|
| 513 |
-
"""
|
| 514 |
-
N = total_words
|
| 515 |
-
V = unique_words
|
| 516 |
-
return N ** (V ** -0.165)
|
| 517 |
-
```
|
| 518 |
-
|
| 519 |
-
## Referências Acadêmicas
|
| 520 |
-
|
| 521 |
-
### Papers Principais
|
| 522 |
-
|
| 523 |
-
1. **"From Tarzan to Tolkien: Controlling the Language Proficiency Level of LLMs for Content Generation"** (2024)
|
| 524 |
-
- Introduz modelo CaLM para controle CEFR
|
| 525 |
-
- Benchmark de técnicas de simplificação
|
| 526 |
-
- [arXiv:2406.03030](https://arxiv.org/html/2406.03030v1)
|
| 527 |
-
|
| 528 |
-
2. **"Alignment Drift in CEFR-prompted LLMs for Interactive Spanish Tutoring"** (2024)
|
| 529 |
-
- Analisa drift em prompts CEFR
|
| 530 |
-
- Propõe re-prompting periódico
|
| 531 |
-
- [arXiv:2505.08351](https://arxiv.org/html/2505.08351v1)
|
| 532 |
-
|
| 533 |
-
3. **"Guiding LLMs The Right Way: Fast, Non-Invasive Constrained Generation"** (2024)
|
| 534 |
-
- Técnicas de constrained decoding
|
| 535 |
-
- Comparação FSM vs Trie
|
| 536 |
-
- [arXiv:2403.06988](https://arxiv.org/html/2403.06988v1)
|
| 537 |
-
|
| 538 |
-
4. **"Fast JSON Decoding for Local LLMs with Compressed Finite State Machine"** (2024)
|
| 539 |
-
- Otimização de FSM para decodificação
|
| 540 |
-
- Jump-forward decoding
|
| 541 |
-
- [LMSYS Blog](https://lmsys.org/blog/2024-02-05-compressed-fsm/)
|
| 542 |
-
|
| 543 |
-
### Bibliotecas e Ferramentas
|
| 544 |
-
|
| 545 |
-
1. **Outlines** - Structured generation com FSM
|
| 546 |
-
- GitHub: `outlines-dev/outlines`
|
| 547 |
-
- Suporte para regex, JSON schema, CFG
|
| 548 |
-
|
| 549 |
-
2. **Guidance** - Constrained generation framework
|
| 550 |
-
- GitHub: `guidance-ai/guidance`
|
| 551 |
-
- Template-based generation
|
| 552 |
-
|
| 553 |
-
3. **XGrammar** - Optimized grammar-based decoding
|
| 554 |
-
- Integrado no vLLM v0.5+
|
| 555 |
-
- Pushdown automaton para performance
|
| 556 |
-
|
| 557 |
-
4. **llama.cpp** - Grammar support nativo
|
| 558 |
-
- GBNF (Grammar-Based Natural Form)
|
| 559 |
-
- Implementação em C++ eficiente
|
| 560 |
-
|
| 561 |
-
## Conclusões e Recomendações
|
| 562 |
-
|
| 563 |
-
### Para Produção Imediata
|
| 564 |
-
1. **Comece com Prompt Engineering** - baixo custo, rápido de testar
|
| 565 |
-
2. **Adicione validação com métricas** - garante qualidade mínima
|
| 566 |
-
3. **Implemente fallback híbrido** - regenera se falhar validação
|
| 567 |
-
|
| 568 |
-
### Para Melhor Qualidade
|
| 569 |
-
1. **Considere pós-processamento** - mantém naturalidade
|
| 570 |
-
2. **Teste FSM/Trie para casos críticos** - máxima garantia
|
| 571 |
-
3. **Avalie fine-tuning se tiver recursos** - solução definitiva
|
| 572 |
-
|
| 573 |
-
### Trade-offs Principais
|
| 574 |
-
- **Controle vs Naturalidade**: Mais controle = menos natural
|
| 575 |
-
- **Garantia vs Latência**: Mais garantia = mais lento
|
| 576 |
-
- **Simplicidade vs Eficácia**: Soluções simples são menos eficazes
|
| 577 |
-
|
| 578 |
-
### Próximos Passos
|
| 579 |
-
1. Criar vocabulário A1 português (500-750 palavras)
|
| 580 |
-
2. Mapear vocabulário para token IDs do modelo
|
| 581 |
-
3. Implementar validação com métricas múltiplas
|
| 582 |
-
4. Testar abordagem híbrida progressiva
|
| 583 |
-
5. Coletar feedback de usuários reais
|
| 584 |
-
|
| 585 |
-
---
|
| 586 |
-
|
| 587 |
-
*Documento criado com base em pesquisa acadêmica e testes práticos com modelos LLM para geração de conteúdo em nível A1 CEFR.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
download_llama_omni2.py
DELETED
|
@@ -1,25 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Download do modelo LLaMA-Omni2 1.5B oficial com Speech Projector treinado
|
| 4 |
-
"""
|
| 5 |
-
from huggingface_hub import snapshot_download
|
| 6 |
-
import os
|
| 7 |
-
|
| 8 |
-
print("🚀 Baixando LLaMA-Omni2 1.5B oficial (com Speech Projector treinado)...")
|
| 9 |
-
print(" Modelo: ICTNLP/Llama-Omni2-1.5B")
|
| 10 |
-
print(" Tamanho: ~6GB")
|
| 11 |
-
|
| 12 |
-
model_dir = snapshot_download(
|
| 13 |
-
repo_id="ICTNLP/Llama-Omni2-1.5B",
|
| 14 |
-
local_dir="/workspace/llama-omni2-troca-llm/models/LLaMA-Omni2-1.5B",
|
| 15 |
-
resume_download=True
|
| 16 |
-
)
|
| 17 |
-
|
| 18 |
-
print(f"✅ Download completo: {model_dir}")
|
| 19 |
-
print("\n📁 Arquivos baixados:")
|
| 20 |
-
for root, dirs, files in os.walk(model_dir):
|
| 21 |
-
for file in files:
|
| 22 |
-
if file.endswith(('.bin', '.safetensors', '.json')):
|
| 23 |
-
file_path = os.path.join(root, file)
|
| 24 |
-
size_mb = os.path.getsize(file_path) / (1024*1024)
|
| 25 |
-
print(f" {file}: {size_mb:.1f}MB")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
download_official_model.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Download do modelo LLaMA-Omni2 1.5B oficial
|
| 4 |
-
"""
|
| 5 |
-
from huggingface_hub import snapshot_download
|
| 6 |
-
|
| 7 |
-
print("🚀 Baixando modelo LLaMA-Omni2 1.5B oficial...")
|
| 8 |
-
model_dir = snapshot_download(
|
| 9 |
-
repo_id="ICTNLP/Llama-Omni2-1.5B",
|
| 10 |
-
local_dir="/workspace/llama-omni2-troca-llm/models/LLaMA-Omni2-1.5B",
|
| 11 |
-
resume_download=True
|
| 12 |
-
)
|
| 13 |
-
print(f"✅ Modelo baixado em: {model_dir}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
generate_test_audios.py
DELETED
|
@@ -1,81 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Gerador de Áudios de Teste
|
| 4 |
-
=========================
|
| 5 |
-
Criar arquivos de áudio para cada frase testada
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
import os
|
| 9 |
-
from gtts import gTTS
|
| 10 |
-
|
| 11 |
-
def create_test_audios():
|
| 12 |
-
"""Criar todos os áudios de teste"""
|
| 13 |
-
|
| 14 |
-
# Frases testadas
|
| 15 |
-
test_phrases = [
|
| 16 |
-
"Olá",
|
| 17 |
-
"Casa",
|
| 18 |
-
"Gato",
|
| 19 |
-
"Bom dia",
|
| 20 |
-
"Como vai",
|
| 21 |
-
"Obrigado",
|
| 22 |
-
"Tudo bem hoje",
|
| 23 |
-
"Que horas são",
|
| 24 |
-
"Como você está"
|
| 25 |
-
]
|
| 26 |
-
|
| 27 |
-
# Criar diretório
|
| 28 |
-
audio_dir = "test_audios"
|
| 29 |
-
os.makedirs(audio_dir, exist_ok=True)
|
| 30 |
-
|
| 31 |
-
print("🎤 Gerando áudios de teste...")
|
| 32 |
-
print("=" * 40)
|
| 33 |
-
|
| 34 |
-
audio_paths = []
|
| 35 |
-
|
| 36 |
-
for i, phrase in enumerate(test_phrases, 1):
|
| 37 |
-
# Nome do arquivo
|
| 38 |
-
filename = f"{i:02d}_{phrase.replace(' ', '_').replace('?', '')}.wav"
|
| 39 |
-
mp3_path = os.path.join(audio_dir, filename.replace('.wav', '.mp3'))
|
| 40 |
-
wav_path = os.path.join(audio_dir, filename)
|
| 41 |
-
|
| 42 |
-
print(f"[{i}/9] Gerando: {phrase}")
|
| 43 |
-
|
| 44 |
-
# Criar áudio com gTTS
|
| 45 |
-
tts = gTTS(text=phrase, lang='pt', slow=False)
|
| 46 |
-
tts.save(mp3_path)
|
| 47 |
-
|
| 48 |
-
# Converter para WAV
|
| 49 |
-
os.system(f"ffmpeg -i {mp3_path} -ar 16000 -ac 1 {wav_path} -y -loglevel quiet")
|
| 50 |
-
os.remove(mp3_path)
|
| 51 |
-
|
| 52 |
-
# Adicionar path absoluto
|
| 53 |
-
abs_path = os.path.abspath(wav_path)
|
| 54 |
-
audio_paths.append({
|
| 55 |
-
"phrase": phrase,
|
| 56 |
-
"filename": filename,
|
| 57 |
-
"path": abs_path
|
| 58 |
-
})
|
| 59 |
-
|
| 60 |
-
print(f" ✅ Salvo: {wav_path}")
|
| 61 |
-
|
| 62 |
-
print(f"\n✅ {len(audio_paths)} áudios gerados!")
|
| 63 |
-
|
| 64 |
-
# Mostrar todos os paths
|
| 65 |
-
print("\n📁 PATHS DOS ÁUDIOS:")
|
| 66 |
-
print("=" * 40)
|
| 67 |
-
for audio in audio_paths:
|
| 68 |
-
print(f"🎤 '{audio['phrase']}':")
|
| 69 |
-
print(f" 📄 Arquivo: {audio['filename']}")
|
| 70 |
-
print(f" 📍 Path: {audio['path']}")
|
| 71 |
-
print()
|
| 72 |
-
|
| 73 |
-
return audio_paths
|
| 74 |
-
|
| 75 |
-
if __name__ == "__main__":
|
| 76 |
-
audio_paths = create_test_audios()
|
| 77 |
-
|
| 78 |
-
print("🎯 RESUMO DOS PATHS:")
|
| 79 |
-
print("=" * 20)
|
| 80 |
-
for i, audio in enumerate(audio_paths, 1):
|
| 81 |
-
print(f"{i}. {audio['path']}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gtts_test_results.json
DELETED
|
@@ -1,82 +0,0 @@
|
|
| 1 |
-
[
|
| 2 |
-
{
|
| 3 |
-
"question_num": 1,
|
| 4 |
-
"question": "Qual é a capital do Brasil?",
|
| 5 |
-
"success": true,
|
| 6 |
-
"latency_ms": 482.533385977149,
|
| 7 |
-
"response": "Olá! Como posso ajudar você hoje?",
|
| 8 |
-
"audio_size_bytes": 86862
|
| 9 |
-
},
|
| 10 |
-
{
|
| 11 |
-
"question_num": 2,
|
| 12 |
-
"question": "Como funciona a fotossíntese nas plantas?",
|
| 13 |
-
"success": true,
|
| 14 |
-
"latency_ms": 530.3681651130319,
|
| 15 |
-
"response": "Olá! Como posso ajudar você hoje?",
|
| 16 |
-
"audio_size_bytes": 122958
|
| 17 |
-
},
|
| 18 |
-
{
|
| 19 |
-
"question_num": 3,
|
| 20 |
-
"question": "Quem foi Dom Pedro Segundo?",
|
| 21 |
-
"success": true,
|
| 22 |
-
"latency_ms": 493.9775848761201,
|
| 23 |
-
"response": "Olá! Como posso ajudar você hoje?",
|
| 24 |
-
"audio_size_bytes": 84558
|
| 25 |
-
},
|
| 26 |
-
{
|
| 27 |
-
"question_num": 4,
|
| 28 |
-
"question": "O que é inteligência artificial?",
|
| 29 |
-
"success": true,
|
| 30 |
-
"latency_ms": 485.67815124988556,
|
| 31 |
-
"response": "Olá! Como posso ajudar você hoje?",
|
| 32 |
-
"audio_size_bytes": 100686
|
| 33 |
-
},
|
| 34 |
-
{
|
| 35 |
-
"question_num": 5,
|
| 36 |
-
"question": "Explique o que são energias renováveis",
|
| 37 |
-
"success": true,
|
| 38 |
-
"latency_ms": 455.7227217592299,
|
| 39 |
-
"response": "Olá! Como posso ajudar você hoje?",
|
| 40 |
-
"audio_size_bytes": 119886
|
| 41 |
-
},
|
| 42 |
-
{
|
| 43 |
-
"question_num": 6,
|
| 44 |
-
"question": "Qual é a diferença entre vírus e bactéria?",
|
| 45 |
-
"success": true,
|
| 46 |
-
"latency_ms": 490.5252889730036,
|
| 47 |
-
"response": "Olá! Como posso ajudar você hoje?",
|
| 48 |
-
"audio_size_bytes": 124494
|
| 49 |
-
},
|
| 50 |
-
{
|
| 51 |
-
"question_num": 7,
|
| 52 |
-
"question": "Como funciona o sistema solar?",
|
| 53 |
-
"success": true,
|
| 54 |
-
"latency_ms": 496.38519808650017,
|
| 55 |
-
"response": "Olá! Como posso ajudar você hoje?",
|
| 56 |
-
"audio_size_bytes": 96078
|
| 57 |
-
},
|
| 58 |
-
{
|
| 59 |
-
"question_num": 8,
|
| 60 |
-
"question": "O que é machine learning?",
|
| 61 |
-
"success": true,
|
| 62 |
-
"latency_ms": 460.00722935423255,
|
| 63 |
-
"response": "Olá! Como posso ajudar você hoje?",
|
| 64 |
-
"audio_size_bytes": 71502
|
| 65 |
-
},
|
| 66 |
-
{
|
| 67 |
-
"question_num": 9,
|
| 68 |
-
"question": "Explique a teoria da relatividade de Einstein",
|
| 69 |
-
"success": true,
|
| 70 |
-
"latency_ms": 474.25004141405225,
|
| 71 |
-
"response": "Olá! Como posso ajudar você hoje?",
|
| 72 |
-
"audio_size_bytes": 130638
|
| 73 |
-
},
|
| 74 |
-
{
|
| 75 |
-
"question_num": 10,
|
| 76 |
-
"question": "Qual é a importância da Amazônia para o clima?",
|
| 77 |
-
"success": true,
|
| 78 |
-
"latency_ms": 458.1514848396182,
|
| 79 |
-
"response": "Olá! Como posso ajudar você hoje?",
|
| 80 |
-
"audio_size_bytes": 126030
|
| 81 |
-
}
|
| 82 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
installed_packages.txt
DELETED
|
@@ -1,246 +0,0 @@
|
|
| 1 |
-
accelerate==0.33.0
|
| 2 |
-
aiofiles==23.2.1
|
| 3 |
-
aiohappyeyeballs @ file:///home/conda/feedstock_root/build_artifacts/aiohappyeyeballs_1741775197943/work
|
| 4 |
-
aiohttp @ file:///home/conda/feedstock_root/build_artifacts/aiohttp_1753804962438/work
|
| 5 |
-
aiohttp-cors @ file:///home/conda/feedstock_root/build_artifacts/aiohttp-cors_1754034655438/work
|
| 6 |
-
aioice==0.10.1
|
| 7 |
-
aiortc==1.13.0
|
| 8 |
-
aiosignal @ file:///home/conda/feedstock_root/build_artifacts/aiosignal_1751626463503/work
|
| 9 |
-
annotated-types @ file:///home/conda/feedstock_root/build_artifacts/annotated-types_1733247046149/work
|
| 10 |
-
antlr4-python3-runtime==4.9.3
|
| 11 |
-
anyio @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_anyio_1754315087/work
|
| 12 |
-
astor==0.8.1
|
| 13 |
-
async-timeout @ file:///home/conda/feedstock_root/build_artifacts/async-timeout_1733235340728/work
|
| 14 |
-
attrs @ file:///home/conda/feedstock_root/build_artifacts/attrs_1741918516150/work
|
| 15 |
-
audioread @ file:///home/conda/feedstock_root/build_artifacts/audioread_1725357455116/work
|
| 16 |
-
av==14.4.0
|
| 17 |
-
beautifulsoup4==4.13.5
|
| 18 |
-
bitsandbytes==0.45.0
|
| 19 |
-
blake3==1.0.5
|
| 20 |
-
Brotli==1.1.0
|
| 21 |
-
brotlicffi @ file:///croot/brotlicffi_1736182461069/work
|
| 22 |
-
cachetools==6.2.0
|
| 23 |
-
cbor2==5.7.0
|
| 24 |
-
certifi @ file:///croot/certifi_1754570635119/work/certifi
|
| 25 |
-
cffi @ file:///croot/cffi_1736182485317/work
|
| 26 |
-
charset-normalizer @ file:///croot/charset-normalizer_1721748349566/work
|
| 27 |
-
click @ file:///home/conda/feedstock_root/build_artifacts/click_1747811314515/work
|
| 28 |
-
cloudpickle==3.1.1
|
| 29 |
-
colorama @ file:///home/conda/feedstock_root/build_artifacts/colorama_1733218098505/work
|
| 30 |
-
coloredlogs==15.0.1
|
| 31 |
-
compressed-tensors==0.10.2
|
| 32 |
-
conformer==0.3.2
|
| 33 |
-
contourpy @ file:///home/conda/feedstock_root/build_artifacts/contourpy_1744743067588/work
|
| 34 |
-
cryptography==45.0.6
|
| 35 |
-
cupy-cuda12x==13.6.0
|
| 36 |
-
cycler @ file:///home/conda/feedstock_root/build_artifacts/cycler_1733332471406/work
|
| 37 |
-
Cython==3.1.3
|
| 38 |
-
datasets==2.18.0
|
| 39 |
-
decorator @ file:///home/conda/feedstock_root/build_artifacts/decorator_1740384970518/work
|
| 40 |
-
depyf==0.19.0
|
| 41 |
-
diffusers==0.27.2
|
| 42 |
-
dill==0.3.8
|
| 43 |
-
diskcache==5.6.3
|
| 44 |
-
distro==1.9.0
|
| 45 |
-
dnspython @ file:///home/conda/feedstock_root/build_artifacts/dnspython_1733256735222/work
|
| 46 |
-
einops==0.6.1
|
| 47 |
-
einops-exts==0.0.4
|
| 48 |
-
email_validator @ file:///home/conda/feedstock_root/build_artifacts/email-validator-meta_1733300719943/work
|
| 49 |
-
exceptiongroup @ file:///home/conda/feedstock_root/build_artifacts/exceptiongroup_1746947292760/work
|
| 50 |
-
fastapi==0.115.11
|
| 51 |
-
fastapi-cli @ file:///home/conda/feedstock_root/build_artifacts/fastapi-cli_1751972066250/work
|
| 52 |
-
fastapi-cloud-cli==0.1.5
|
| 53 |
-
fastrlock==0.8.3
|
| 54 |
-
ffmpy==0.6.1
|
| 55 |
-
filelock @ file:///croot/filelock_1744281381737/work
|
| 56 |
-
flatbuffers==25.2.10
|
| 57 |
-
fonttools @ file:///home/conda/feedstock_root/build_artifacts/fonttools_1755223865520/work
|
| 58 |
-
frozenlist @ file:///home/conda/feedstock_root/build_artifacts/frozenlist_1752167142843/work
|
| 59 |
-
fsspec==2024.2.0
|
| 60 |
-
gdown==5.1.0
|
| 61 |
-
gguf==0.17.1
|
| 62 |
-
gmpy2 @ file:///croot/gmpy2_1738085463648/work
|
| 63 |
-
google-crc32c==1.7.1
|
| 64 |
-
gradio==5.3.0
|
| 65 |
-
gradio_client==1.4.2
|
| 66 |
-
groovy==0.1.2
|
| 67 |
-
grpcio @ file:///home/conda/feedstock_root/build_artifacts/grpc-split_1754634529307/work
|
| 68 |
-
grpcio-tools @ file:///home/conda/feedstock_root/build_artifacts/grpc-split_1754634529307/work/tools/distrib/python/grpcio_tools
|
| 69 |
-
h11 @ file:///home/conda/feedstock_root/build_artifacts/h11_1745526374115/work
|
| 70 |
-
h2 @ file:///home/conda/feedstock_root/build_artifacts/h2_1738578511449/work
|
| 71 |
-
hf-xet @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_hf-xet_1755587744/work
|
| 72 |
-
hpack @ file:///home/conda/feedstock_root/build_artifacts/hpack_1737618293087/work
|
| 73 |
-
httpcore @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_httpcore_1745602916/work
|
| 74 |
-
httptools @ file:///home/conda/feedstock_root/build_artifacts/httptools_1732707649090/work
|
| 75 |
-
httpx @ file:///home/conda/feedstock_root/build_artifacts/httpx_1733663348460/work
|
| 76 |
-
huggingface-hub==0.34.4
|
| 77 |
-
humanfriendly==10.0
|
| 78 |
-
hydra-core==1.3.2
|
| 79 |
-
hyperframe @ file:///home/conda/feedstock_root/build_artifacts/hyperframe_1737618333194/work
|
| 80 |
-
HyperPyYAML==1.2.2
|
| 81 |
-
idna @ file:///croot/idna_1714398848350/work
|
| 82 |
-
ifaddr==0.2.0
|
| 83 |
-
importlib_metadata @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_importlib-metadata_1747934053/work
|
| 84 |
-
importlib_resources==6.5.2
|
| 85 |
-
inflect==7.3.1
|
| 86 |
-
interegular==0.3.3
|
| 87 |
-
Jinja2 @ file:///croot/jinja2_1741710844255/work
|
| 88 |
-
jiter==0.10.0
|
| 89 |
-
joblib @ file:///home/conda/feedstock_root/build_artifacts/joblib_1748019130050/work
|
| 90 |
-
jsonschema==4.25.1
|
| 91 |
-
jsonschema-specifications==2025.4.1
|
| 92 |
-
kiwisolver @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_kiwisolver_1754889395/work
|
| 93 |
-
lark==1.2.2
|
| 94 |
-
latex2mathml==3.78.0
|
| 95 |
-
lazy_loader @ file:///home/conda/feedstock_root/build_artifacts/lazy-loader_1733636780672/work
|
| 96 |
-
librosa==0.10.2
|
| 97 |
-
lightning==2.2.4
|
| 98 |
-
lightning-utilities==0.15.2
|
| 99 |
-
-e git+https://huggingface.co/marcosremar2/llama-omni2-official-code@d76469fe3fb92c109fb6f3deb08415ee07079042#egg=llama_omni2
|
| 100 |
-
llguidance==0.7.30
|
| 101 |
-
llvmlite==0.44.0
|
| 102 |
-
lm-format-enforcer==0.10.12
|
| 103 |
-
markdown-it-py @ file:///home/conda/feedstock_root/build_artifacts/markdown-it-py_1754951200865/work
|
| 104 |
-
markdown2==2.5.2
|
| 105 |
-
MarkupSafe==2.1.5
|
| 106 |
-
matplotlib==3.10.1
|
| 107 |
-
mdurl @ file:///home/conda/feedstock_root/build_artifacts/mdurl_1733255585584/work
|
| 108 |
-
mistral_common==1.8.4
|
| 109 |
-
mkl-fft==1.3.1
|
| 110 |
-
mkl-random @ file:///home/builder/ci_310/mkl_random_1641843545607/work
|
| 111 |
-
mkl-service==2.4.0
|
| 112 |
-
more-itertools==10.7.0
|
| 113 |
-
mpmath @ file:///croot/mpmath_1690848262763/work
|
| 114 |
-
msgpack @ file:///home/conda/feedstock_root/build_artifacts/msgpack-python_1749813185825/work
|
| 115 |
-
msgspec==0.19.0
|
| 116 |
-
multidict @ file:///home/conda/feedstock_root/build_artifacts/multidict_1751310558090/work
|
| 117 |
-
multiprocess==0.70.16
|
| 118 |
-
munkres==1.1.4
|
| 119 |
-
networkx @ file:///croot/networkx_1737039604450/work
|
| 120 |
-
ninja==1.13.0
|
| 121 |
-
numba @ file:///home/conda/feedstock_root/build_artifacts/numba_1749491273169/work
|
| 122 |
-
numpy==2.2.6
|
| 123 |
-
nvidia-cublas-cu12==12.6.4.1
|
| 124 |
-
nvidia-cuda-cupti-cu12==12.6.80
|
| 125 |
-
nvidia-cuda-nvrtc-cu12==12.6.77
|
| 126 |
-
nvidia-cuda-runtime-cu12==12.6.77
|
| 127 |
-
nvidia-cudnn-cu12==9.5.1.17
|
| 128 |
-
nvidia-cufft-cu12==11.3.0.4
|
| 129 |
-
nvidia-cufile-cu12==1.11.1.6
|
| 130 |
-
nvidia-curand-cu12==10.3.7.77
|
| 131 |
-
nvidia-cusolver-cu12==11.7.1.2
|
| 132 |
-
nvidia-cusparse-cu12==12.5.4.2
|
| 133 |
-
nvidia-cusparselt-cu12==0.6.3
|
| 134 |
-
nvidia-nccl-cu12==2.26.2
|
| 135 |
-
nvidia-nvjitlink-cu12==12.6.85
|
| 136 |
-
nvidia-nvtx-cu12==12.6.77
|
| 137 |
-
omegaconf==2.3.0
|
| 138 |
-
onnx==1.16.0
|
| 139 |
-
onnxruntime-gpu==1.18.0
|
| 140 |
-
openai==1.101.0
|
| 141 |
-
openai-harmony==0.0.4
|
| 142 |
-
openai-whisper==20231117
|
| 143 |
-
opencv-python-headless==4.12.0.88
|
| 144 |
-
orjson==3.11.2
|
| 145 |
-
outlines_core==0.2.10
|
| 146 |
-
packaging==25.0
|
| 147 |
-
pandas==2.3.2
|
| 148 |
-
partial-json-parser==0.2.1.1.post6
|
| 149 |
-
peft==0.14.0
|
| 150 |
-
pillow==10.4.0
|
| 151 |
-
platformdirs @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_platformdirs_1746710438/work
|
| 152 |
-
pooch @ file:///home/conda/feedstock_root/build_artifacts/pooch_1754941678315/work
|
| 153 |
-
prometheus-fastapi-instrumentator==7.1.0
|
| 154 |
-
prometheus_client==0.22.1
|
| 155 |
-
propcache @ file:///home/conda/feedstock_root/build_artifacts/propcache_1744524934684/work
|
| 156 |
-
protobuf @ file:///home/conda/feedstock_root/build_artifacts/protobuf_1751668305329/work/bazel-bin/python/dist/protobuf-6.31.1-cp310-abi3-linux_x86_64.whl#sha256=defdfb61601fe45e5f630c511e9db761b2d454cda73e7e10af9b4112431a5435
|
| 157 |
-
psutil==7.0.0
|
| 158 |
-
py-cpuinfo==9.0.0
|
| 159 |
-
pyarrow==21.0.0
|
| 160 |
-
pyarrow-hotfix==0.7
|
| 161 |
-
pybase64==1.4.2
|
| 162 |
-
pycountry==24.6.1
|
| 163 |
-
pycparser @ file:///tmp/build/80754af9/pycparser_1636541352034/work
|
| 164 |
-
pydantic==2.11.7
|
| 165 |
-
pydantic-extra-types==2.10.5
|
| 166 |
-
pydantic_core==2.33.2
|
| 167 |
-
pydub==0.25.1
|
| 168 |
-
pyee==13.0.0
|
| 169 |
-
Pygments @ file:///home/conda/feedstock_root/build_artifacts/pygments_1750615794071/work
|
| 170 |
-
pylibsrtp==0.12.0
|
| 171 |
-
pynini==2.1.5
|
| 172 |
-
pyOpenSSL==25.1.0
|
| 173 |
-
pyparsing @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_pyparsing_1753873557/work
|
| 174 |
-
PySocks @ file:///home/builder/ci_310/pysocks_1640793678128/work
|
| 175 |
-
python-dateutil @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_python-dateutil_1751104122/work
|
| 176 |
-
python-dotenv @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_python-dotenv_1750789290/work
|
| 177 |
-
python-json-logger==3.3.0
|
| 178 |
-
python-multipart @ file:///home/conda/feedstock_root/build_artifacts/python-multipart_1734420773152/work
|
| 179 |
-
pytorch-lightning==2.5.3
|
| 180 |
-
pytz==2025.2
|
| 181 |
-
PyYAML @ file:///croot/pyyaml_1728657952215/work
|
| 182 |
-
pyzmq==27.0.2
|
| 183 |
-
ray==2.48.0
|
| 184 |
-
referencing==0.36.2
|
| 185 |
-
regex==2025.7.34
|
| 186 |
-
requests==2.32.3
|
| 187 |
-
rich @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_rich_1753436991/work/dist
|
| 188 |
-
rich-toolkit @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_rich-toolkit_1755001296/work
|
| 189 |
-
rignore==0.6.4
|
| 190 |
-
rpds-py==0.27.0
|
| 191 |
-
ruamel.yaml==0.18.15
|
| 192 |
-
ruamel.yaml.clib==0.2.12
|
| 193 |
-
ruff==0.12.10
|
| 194 |
-
safehttpx==0.1.6
|
| 195 |
-
safetensors==0.6.2
|
| 196 |
-
scikit-learn==1.2.2
|
| 197 |
-
scipy @ file:///home/conda/feedstock_root/build_artifacts/scipy-split_1739790642651/work/dist/scipy-1.15.2-cp310-cp310-linux_x86_64.whl#sha256=9e52bad6c3294d1a5b04a13632118ca2157130603c6c018c2d710162b223b27e
|
| 198 |
-
semantic-version==2.10.0
|
| 199 |
-
sentencepiece==0.1.99
|
| 200 |
-
sentry-sdk==2.35.1
|
| 201 |
-
setproctitle==1.3.6
|
| 202 |
-
shellingham @ file:///home/conda/feedstock_root/build_artifacts/shellingham_1733300899265/work
|
| 203 |
-
shortuuid==1.0.13
|
| 204 |
-
six @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_six_1753199211/work
|
| 205 |
-
sniffio @ file:///home/conda/feedstock_root/build_artifacts/sniffio_1733244044561/work
|
| 206 |
-
soundfile @ file:///home/conda/feedstock_root/build_artifacts/pysoundfile_1737836266465/work
|
| 207 |
-
soupsieve==2.7
|
| 208 |
-
soxr @ file:///home/conda/feedstock_root/build_artifacts/soxr-python_1725345517888/work
|
| 209 |
-
standard-aifc @ file:///home/conda/feedstock_root/build_artifacts/standard-aifc_1751926219849/work
|
| 210 |
-
standard-sunau @ file:///home/conda/feedstock_root/build_artifacts/standard-sunau_1751926339586/work
|
| 211 |
-
starlette==0.46.2
|
| 212 |
-
svgwrite==1.4.3
|
| 213 |
-
sympy==1.14.0
|
| 214 |
-
threadpoolctl @ file:///home/conda/feedstock_root/build_artifacts/threadpoolctl_1741878222898/work
|
| 215 |
-
tiktoken==0.11.0
|
| 216 |
-
timm==0.6.13
|
| 217 |
-
tokenizers==0.21.4
|
| 218 |
-
tomlkit==0.12.0
|
| 219 |
-
torch==2.7.1
|
| 220 |
-
torchaudio==2.7.1
|
| 221 |
-
torchmetrics==1.8.1
|
| 222 |
-
torchvision==0.22.1
|
| 223 |
-
tqdm @ file:///home/conda/feedstock_root/build_artifacts/tqdm_1735661334605/work
|
| 224 |
-
transformers==4.55.4
|
| 225 |
-
triton==3.3.1
|
| 226 |
-
typeguard==4.4.4
|
| 227 |
-
typer==0.16.1
|
| 228 |
-
typer-slim==0.16.1
|
| 229 |
-
typing-inspection @ file:///home/conda/feedstock_root/build_artifacts/typing-inspection_1747870647094/work
|
| 230 |
-
typing_extensions==4.15.0
|
| 231 |
-
tzdata==2025.2
|
| 232 |
-
unicodedata2 @ file:///home/conda/feedstock_root/build_artifacts/unicodedata2_1736692496989/work
|
| 233 |
-
urllib3 @ file:///croot/urllib3_1750775463400/work
|
| 234 |
-
uvicorn==0.30.0
|
| 235 |
-
uvloop @ file:///home/conda/feedstock_root/build_artifacts/uvloop_1730214334932/work
|
| 236 |
-
vllm==0.10.1.1
|
| 237 |
-
watchfiles @ file:///home/conda/feedstock_root/build_artifacts/watchfiles_1750053828522/work
|
| 238 |
-
wavedrom==2.0.3.post3
|
| 239 |
-
websockets==12.0
|
| 240 |
-
WeTextProcessing==1.0.3
|
| 241 |
-
wget==3.2
|
| 242 |
-
xformers==0.0.31
|
| 243 |
-
xgrammar==0.1.21
|
| 244 |
-
xxhash==3.5.0
|
| 245 |
-
yarl @ file:///home/conda/feedstock_root/build_artifacts/yarl_1749554822108/work
|
| 246 |
-
zipp @ file:///home/conda/feedstock_root/build_artifacts/zipp_1749421620841/work
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
llama_omni2_integration/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
# LLaMA-Omni2 Integration Package
|
|
|
|
|
|
llama_omni2_integration/constants.py
DELETED
|
@@ -1,9 +0,0 @@
|
|
| 1 |
-
CONTROLLER_HEART_BEAT_EXPIRATION = 30
|
| 2 |
-
WORKER_HEART_BEAT_INTERVAL = 15
|
| 3 |
-
|
| 4 |
-
LOGDIR = "."
|
| 5 |
-
|
| 6 |
-
# Model Constants
|
| 7 |
-
IGNORE_INDEX = -100
|
| 8 |
-
SPEECH_TOKEN_INDEX = -200
|
| 9 |
-
DEFAULT_SPEECH_TOKEN = "<speech>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
llama_omni2_integration/omni2_speech_arch.py
DELETED
|
@@ -1,201 +0,0 @@
|
|
| 1 |
-
# Copyright 2023 Haotian Liu
|
| 2 |
-
# Copyright 2024 Qingkai Fang
|
| 3 |
-
#
|
| 4 |
-
# This project is modified based on LLaVA by Haotian Liu, Qingkai Fang adds further supports for speech-to-text/speech tasks.
|
| 5 |
-
#
|
| 6 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 7 |
-
# you may not use this file except in compliance with the License.
|
| 8 |
-
# You may obtain a copy of the License at
|
| 9 |
-
#
|
| 10 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 11 |
-
#
|
| 12 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 13 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 14 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 15 |
-
# See the License for the specific language governing permissions and
|
| 16 |
-
# limitations under the License.
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
from abc import ABC, abstractmethod
|
| 20 |
-
|
| 21 |
-
import torch
|
| 22 |
-
|
| 23 |
-
from .speech_encoder.builder import build_speech_encoder
|
| 24 |
-
from .speech_projector.builder import build_speech_projector
|
| 25 |
-
from .constants import IGNORE_INDEX, SPEECH_TOKEN_INDEX
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
class Omni2SpeechMetaModel:
|
| 29 |
-
|
| 30 |
-
def __init__(self, config):
|
| 31 |
-
super(Omni2SpeechMetaModel, self).__init__(config)
|
| 32 |
-
if hasattr(config, "speech_encoder"):
|
| 33 |
-
self.speech_encoder = build_speech_encoder(config)
|
| 34 |
-
self.speech_projector = build_speech_projector(config)
|
| 35 |
-
|
| 36 |
-
def get_speech_encoder(self):
|
| 37 |
-
speech_encoder = getattr(self, "speech_encoder", None)
|
| 38 |
-
return speech_encoder
|
| 39 |
-
|
| 40 |
-
def get_speech_projector(self):
|
| 41 |
-
speech_projector = getattr(self, "speech_projector", None)
|
| 42 |
-
return speech_projector
|
| 43 |
-
|
| 44 |
-
def initialize_speech_modules(self, model_args):
|
| 45 |
-
self.config.speech_encoder = getattr(model_args, "speech_encoder", None)
|
| 46 |
-
self.config.speech_encoder_type = getattr(model_args, "speech_encoder_type", None)
|
| 47 |
-
self.config.speech_projector_type = getattr(model_args, 'speech_projector_type', 'linear')
|
| 48 |
-
self.config.speech_encoder_ds_rate = getattr(model_args, 'speech_encoder_ds_rate', 5)
|
| 49 |
-
self.config.speech_encoder_hidden_size = getattr(model_args, 'speech_encoder_hidden_size', 1280)
|
| 50 |
-
|
| 51 |
-
if self.get_speech_encoder() is None:
|
| 52 |
-
self.speech_encoder = build_speech_encoder(self.config)
|
| 53 |
-
if self.get_speech_projector() is None:
|
| 54 |
-
self.speech_projector = build_speech_projector(self.config)
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
class Omni2SpeechMetaForCausalLM(ABC):
|
| 58 |
-
|
| 59 |
-
@abstractmethod
|
| 60 |
-
def get_model(self):
|
| 61 |
-
pass
|
| 62 |
-
|
| 63 |
-
def get_speech_encoder(self):
|
| 64 |
-
return self.get_model().get_speech_encoder()
|
| 65 |
-
|
| 66 |
-
def get_speech_projector(self):
|
| 67 |
-
return self.get_model().get_speech_projector()
|
| 68 |
-
|
| 69 |
-
def encode_speech(self, speech, speech_lengths):
|
| 70 |
-
speech_encoder_type = self.config.speech_encoder_type
|
| 71 |
-
speech_encoder = self.get_speech_encoder()
|
| 72 |
-
if "whisper" in speech_encoder_type.lower():
|
| 73 |
-
encoder_outs = speech_encoder(speech.permute(0, 2, 1))
|
| 74 |
-
speech_lengths = (speech_lengths + 1) // 2
|
| 75 |
-
else:
|
| 76 |
-
raise ValueError(f"Unknown speech encoder type: {speech_encoder_type}")
|
| 77 |
-
|
| 78 |
-
speech_projector_type = self.config.speech_projector_type
|
| 79 |
-
speech_projector = self.get_speech_projector()
|
| 80 |
-
if speech_projector_type == "linear":
|
| 81 |
-
encoder_outs = speech_projector(encoder_outs)
|
| 82 |
-
speech_lengths = speech_lengths // speech_projector.k
|
| 83 |
-
else:
|
| 84 |
-
raise ValueError(f"Unknown speech projector type: {speech_projector_type}")
|
| 85 |
-
|
| 86 |
-
speech_features = [encoder_outs[i, :speech_lengths[i]] for i in range(len(encoder_outs))]
|
| 87 |
-
return speech_features
|
| 88 |
-
|
| 89 |
-
def prepare_inputs_labels_for_speech_and_text(
|
| 90 |
-
self, input_ids, position_ids, attention_mask, past_key_values, labels,
|
| 91 |
-
speech, speech_lengths
|
| 92 |
-
):
|
| 93 |
-
speech_encoder = self.get_speech_encoder()
|
| 94 |
-
if speech_encoder is None or speech is None or input_ids.shape[1] == 1:
|
| 95 |
-
return input_ids, position_ids, attention_mask, past_key_values, None, labels
|
| 96 |
-
|
| 97 |
-
speech_features = self.encode_speech(speech, speech_lengths)
|
| 98 |
-
|
| 99 |
-
_labels = labels
|
| 100 |
-
_position_ids = position_ids
|
| 101 |
-
_attention_mask = attention_mask
|
| 102 |
-
if attention_mask is None:
|
| 103 |
-
attention_mask = torch.ones_like(input_ids, dtype=torch.bool)
|
| 104 |
-
else:
|
| 105 |
-
attention_mask = attention_mask.bool()
|
| 106 |
-
if position_ids is None:
|
| 107 |
-
position_ids = torch.arange(0, input_ids.shape[1], dtype=torch.long, device=input_ids.device)
|
| 108 |
-
if labels is None:
|
| 109 |
-
labels = torch.full_like(input_ids, IGNORE_INDEX)
|
| 110 |
-
|
| 111 |
-
input_ids = [cur_input_ids[cur_attention_mask] for cur_input_ids, cur_attention_mask in zip(input_ids, attention_mask)]
|
| 112 |
-
labels = [cur_labels[cur_attention_mask] for cur_labels, cur_attention_mask in zip(labels, attention_mask)]
|
| 113 |
-
|
| 114 |
-
new_input_embeds = []
|
| 115 |
-
new_labels = []
|
| 116 |
-
cur_speech_idx = 0
|
| 117 |
-
for batch_idx, cur_input_ids in enumerate(input_ids):
|
| 118 |
-
num_speech = (cur_input_ids == SPEECH_TOKEN_INDEX).sum()
|
| 119 |
-
speech_token_indices = [-1] + torch.where(cur_input_ids == SPEECH_TOKEN_INDEX)[0].tolist() + [cur_input_ids.shape[0]]
|
| 120 |
-
cur_input_ids_nospeech = []
|
| 121 |
-
cur_labels = labels[batch_idx]
|
| 122 |
-
cur_labels_nospeech = []
|
| 123 |
-
for i in range(len(speech_token_indices) - 1):
|
| 124 |
-
cur_input_ids_nospeech.append(cur_input_ids[speech_token_indices[i]+1:speech_token_indices[i+1]])
|
| 125 |
-
cur_labels_nospeech.append(cur_labels[speech_token_indices[i]+1:speech_token_indices[i+1]])
|
| 126 |
-
split_sizes = [x.shape[0] for x in cur_labels_nospeech]
|
| 127 |
-
cur_input_embeds = self.get_model().embed_tokens(torch.cat(cur_input_ids_nospeech))
|
| 128 |
-
cur_input_embeds_no_speech = torch.split(cur_input_embeds, split_sizes, dim=0)
|
| 129 |
-
cur_new_input_embeds = []
|
| 130 |
-
cur_new_labels = []
|
| 131 |
-
|
| 132 |
-
for i in range(num_speech + 1):
|
| 133 |
-
cur_new_input_embeds.append(cur_input_embeds_no_speech[i])
|
| 134 |
-
cur_new_labels.append(cur_labels_nospeech[i])
|
| 135 |
-
if i < num_speech:
|
| 136 |
-
cur_speech_features = speech_features[cur_speech_idx]
|
| 137 |
-
cur_speech_idx += 1
|
| 138 |
-
cur_new_input_embeds.append(cur_speech_features)
|
| 139 |
-
cur_new_labels.append(torch.full((cur_speech_features.shape[0],), IGNORE_INDEX, device=cur_labels.device, dtype=cur_labels.dtype))
|
| 140 |
-
|
| 141 |
-
cur_new_input_embeds = [x.to(self.device) for x in cur_new_input_embeds]
|
| 142 |
-
cur_new_input_embeds = torch.cat(cur_new_input_embeds)
|
| 143 |
-
cur_new_labels = torch.cat(cur_new_labels)
|
| 144 |
-
|
| 145 |
-
new_input_embeds.append(cur_new_input_embeds)
|
| 146 |
-
new_labels.append(cur_new_labels)
|
| 147 |
-
|
| 148 |
-
assert cur_speech_idx == len(speech_features)
|
| 149 |
-
|
| 150 |
-
# Truncate sequences to max length as speech features can make the sequence longer
|
| 151 |
-
tokenizer_model_max_length = getattr(self.config, 'tokenizer_model_max_length', None)
|
| 152 |
-
if tokenizer_model_max_length is not None:
|
| 153 |
-
new_input_embeds = [x[:tokenizer_model_max_length] for x in new_input_embeds]
|
| 154 |
-
new_labels = [x[:tokenizer_model_max_length] for x in new_labels]
|
| 155 |
-
|
| 156 |
-
# Combine them
|
| 157 |
-
max_len = max(x.shape[0] for x in new_input_embeds)
|
| 158 |
-
batch_size = len(new_input_embeds)
|
| 159 |
-
|
| 160 |
-
new_input_embeds_padded = []
|
| 161 |
-
new_labels_padded = torch.full((batch_size, max_len), IGNORE_INDEX, dtype=new_labels[0].dtype, device=new_labels[0].device)
|
| 162 |
-
attention_mask = torch.zeros((batch_size, max_len), dtype=attention_mask.dtype, device=attention_mask.device)
|
| 163 |
-
position_ids = torch.zeros((batch_size, max_len), dtype=position_ids.dtype, device=position_ids.device)
|
| 164 |
-
|
| 165 |
-
for i, (cur_new_embed, cur_new_labels) in enumerate(zip(new_input_embeds, new_labels)):
|
| 166 |
-
cur_len = cur_new_embed.shape[0]
|
| 167 |
-
if getattr(self.config, 'tokenizer_padding_side', 'right') == "left":
|
| 168 |
-
new_input_embeds_padded.append(torch.cat((
|
| 169 |
-
torch.zeros((max_len - cur_len, cur_new_embed.shape[1]), dtype=cur_new_embed.dtype, device=cur_new_embed.device),
|
| 170 |
-
cur_new_embed
|
| 171 |
-
), dim=0))
|
| 172 |
-
if cur_len > 0:
|
| 173 |
-
new_labels_padded[i, -cur_len:] = cur_new_labels
|
| 174 |
-
attention_mask[i, -cur_len:] = True
|
| 175 |
-
position_ids[i, -cur_len:] = torch.arange(0, cur_len, dtype=position_ids.dtype, device=position_ids.device)
|
| 176 |
-
else:
|
| 177 |
-
new_input_embeds_padded.append(torch.cat((
|
| 178 |
-
cur_new_embed,
|
| 179 |
-
torch.zeros((max_len - cur_len, cur_new_embed.shape[1]), dtype=cur_new_embed.dtype, device=cur_new_embed.device)
|
| 180 |
-
), dim=0))
|
| 181 |
-
if cur_len > 0:
|
| 182 |
-
new_labels_padded[i, :cur_len] = cur_new_labels
|
| 183 |
-
attention_mask[i, :cur_len] = True
|
| 184 |
-
position_ids[i, :cur_len] = torch.arange(0, cur_len, dtype=position_ids.dtype, device=position_ids.device)
|
| 185 |
-
|
| 186 |
-
new_input_embeds = torch.stack(new_input_embeds_padded, dim=0)
|
| 187 |
-
|
| 188 |
-
if _labels is None:
|
| 189 |
-
new_labels = None
|
| 190 |
-
else:
|
| 191 |
-
new_labels = new_labels_padded
|
| 192 |
-
|
| 193 |
-
if _attention_mask is None:
|
| 194 |
-
attention_mask = None
|
| 195 |
-
else:
|
| 196 |
-
attention_mask = attention_mask.to(dtype=_attention_mask.dtype)
|
| 197 |
-
|
| 198 |
-
if _position_ids is None:
|
| 199 |
-
position_ids = None
|
| 200 |
-
|
| 201 |
-
return None, position_ids, attention_mask, past_key_values, new_input_embeds, new_labels
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
llama_omni2_integration/qwen2_speech_model.py
DELETED
|
@@ -1,155 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Qwen2.5 Speech Model - Adaptação do LLaMA-Omni2 oficial
|
| 3 |
-
Integra Speech Projector + Whisper Encoder com Qwen2.5 em português
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
from typing import List, Optional, Tuple, Union
|
| 7 |
-
|
| 8 |
-
import torch
|
| 9 |
-
import torch.nn as nn
|
| 10 |
-
|
| 11 |
-
from transformers import AutoConfig, AutoModelForCausalLM, \
|
| 12 |
-
Qwen2Config, Qwen2Model, Qwen2ForCausalLM
|
| 13 |
-
|
| 14 |
-
from transformers.modeling_outputs import CausalLMOutputWithPast
|
| 15 |
-
from transformers.generation.utils import GenerateOutput
|
| 16 |
-
|
| 17 |
-
from .omni2_speech_arch import Omni2SpeechMetaModel, Omni2SpeechMetaForCausalLM
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
class Qwen2SpeechConfig(Qwen2Config):
|
| 21 |
-
"""Configuração para Qwen2.5 com Speech"""
|
| 22 |
-
model_type = "qwen2_speech"
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
class Qwen2SpeechModel(Omni2SpeechMetaModel, Qwen2Model):
|
| 26 |
-
"""Modelo base Qwen2.5 com capacidades de Speech"""
|
| 27 |
-
config_class = Qwen2SpeechConfig
|
| 28 |
-
|
| 29 |
-
def __init__(self, config: Qwen2Config):
|
| 30 |
-
super(Qwen2SpeechModel, self).__init__(config)
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
class Qwen2SpeechForCausalLM(Qwen2ForCausalLM, Omni2SpeechMetaForCausalLM):
|
| 34 |
-
"""
|
| 35 |
-
Qwen2.5 ForCausalLM com Speech Integration
|
| 36 |
-
Baseado no LLaMA-Omni2 oficial mas usando Qwen2.5 em português
|
| 37 |
-
"""
|
| 38 |
-
config_class = Qwen2SpeechConfig
|
| 39 |
-
|
| 40 |
-
def __init__(self, config):
|
| 41 |
-
super(Qwen2ForCausalLM, self).__init__(config)
|
| 42 |
-
self.model = Qwen2SpeechModel(config)
|
| 43 |
-
self.vocab_size = config.vocab_size
|
| 44 |
-
self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
|
| 45 |
-
|
| 46 |
-
# Initialize weights and apply final processing
|
| 47 |
-
self.post_init()
|
| 48 |
-
|
| 49 |
-
def get_model(self):
|
| 50 |
-
return self.model
|
| 51 |
-
|
| 52 |
-
def forward(
|
| 53 |
-
self,
|
| 54 |
-
input_ids: torch.LongTensor = None,
|
| 55 |
-
attention_mask: Optional[torch.Tensor] = None,
|
| 56 |
-
position_ids: Optional[torch.LongTensor] = None,
|
| 57 |
-
past_key_values: Optional[List[torch.FloatTensor]] = None,
|
| 58 |
-
inputs_embeds: Optional[torch.FloatTensor] = None,
|
| 59 |
-
labels: Optional[torch.LongTensor] = None,
|
| 60 |
-
use_cache: Optional[bool] = None,
|
| 61 |
-
output_attentions: Optional[bool] = None,
|
| 62 |
-
output_hidden_states: Optional[bool] = None,
|
| 63 |
-
speech: Optional[torch.FloatTensor] = None,
|
| 64 |
-
speech_lengths: Optional[torch.LongTensor] = None,
|
| 65 |
-
return_dict: Optional[bool] = None,
|
| 66 |
-
cache_position: Optional[torch.LongTensor] = None,
|
| 67 |
-
) -> Union[Tuple, CausalLMOutputWithPast]:
|
| 68 |
-
|
| 69 |
-
if inputs_embeds is None:
|
| 70 |
-
(
|
| 71 |
-
input_ids,
|
| 72 |
-
position_ids,
|
| 73 |
-
attention_mask,
|
| 74 |
-
past_key_values,
|
| 75 |
-
inputs_embeds,
|
| 76 |
-
labels
|
| 77 |
-
) = self.prepare_inputs_labels_for_speech_and_text(
|
| 78 |
-
input_ids,
|
| 79 |
-
position_ids,
|
| 80 |
-
attention_mask,
|
| 81 |
-
past_key_values,
|
| 82 |
-
labels,
|
| 83 |
-
speech,
|
| 84 |
-
speech_lengths
|
| 85 |
-
)
|
| 86 |
-
|
| 87 |
-
return super().forward(
|
| 88 |
-
input_ids=input_ids,
|
| 89 |
-
attention_mask=attention_mask,
|
| 90 |
-
position_ids=position_ids,
|
| 91 |
-
past_key_values=past_key_values,
|
| 92 |
-
inputs_embeds=inputs_embeds,
|
| 93 |
-
labels=labels,
|
| 94 |
-
use_cache=use_cache,
|
| 95 |
-
output_attentions=output_attentions,
|
| 96 |
-
output_hidden_states=output_hidden_states,
|
| 97 |
-
return_dict=return_dict
|
| 98 |
-
)
|
| 99 |
-
|
| 100 |
-
@torch.no_grad()
|
| 101 |
-
def generate(
|
| 102 |
-
self,
|
| 103 |
-
inputs: Optional[torch.Tensor] = None,
|
| 104 |
-
speech: Optional[torch.Tensor] = None,
|
| 105 |
-
speech_lengths: Optional[torch.Tensor] = None,
|
| 106 |
-
**kwargs,
|
| 107 |
-
) -> Union[GenerateOutput, torch.LongTensor]:
|
| 108 |
-
position_ids = kwargs.pop("position_ids", None)
|
| 109 |
-
attention_mask = kwargs.pop("attention_mask", None)
|
| 110 |
-
if "inputs_embeds" in kwargs:
|
| 111 |
-
raise NotImplementedError("`inputs_embeds` is not supported")
|
| 112 |
-
|
| 113 |
-
if speech is not None:
|
| 114 |
-
(
|
| 115 |
-
inputs,
|
| 116 |
-
position_ids,
|
| 117 |
-
attention_mask,
|
| 118 |
-
_,
|
| 119 |
-
inputs_embeds,
|
| 120 |
-
_
|
| 121 |
-
) = self.prepare_inputs_labels_for_speech_and_text(
|
| 122 |
-
inputs,
|
| 123 |
-
position_ids,
|
| 124 |
-
attention_mask,
|
| 125 |
-
None,
|
| 126 |
-
None,
|
| 127 |
-
speech,
|
| 128 |
-
speech_lengths
|
| 129 |
-
)
|
| 130 |
-
else:
|
| 131 |
-
inputs_embeds = self.get_model().embed_tokens(inputs)
|
| 132 |
-
|
| 133 |
-
return super().generate(
|
| 134 |
-
position_ids=position_ids,
|
| 135 |
-
attention_mask=attention_mask,
|
| 136 |
-
inputs_embeds=inputs_embeds,
|
| 137 |
-
**kwargs
|
| 138 |
-
)
|
| 139 |
-
|
| 140 |
-
def prepare_inputs_for_generation(self, input_ids, past_key_values=None,
|
| 141 |
-
inputs_embeds=None, **kwargs):
|
| 142 |
-
speech = kwargs.pop("speech", None)
|
| 143 |
-
speech_lengths = kwargs.pop("speech_lengths", None)
|
| 144 |
-
inputs = super().prepare_inputs_for_generation(
|
| 145 |
-
input_ids, past_key_values=past_key_values, inputs_embeds=inputs_embeds, **kwargs
|
| 146 |
-
)
|
| 147 |
-
if speech is not None:
|
| 148 |
-
inputs['speech'] = speech
|
| 149 |
-
inputs['speech_lengths'] = speech_lengths
|
| 150 |
-
return inputs
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
# Registrar o modelo
|
| 154 |
-
AutoConfig.register("qwen2_speech", Qwen2SpeechConfig)
|
| 155 |
-
AutoModelForCausalLM.register(Qwen2SpeechConfig, Qwen2SpeechForCausalLM)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
llama_omni2_integration/qwen2_speech_model_fixed.py
DELETED
|
@@ -1,294 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Qwen2.5 Speech Model - IMPLEMENTAÇÃO IDÊNTICA AO LLAMA-OMNI2 OFICIAL
|
| 3 |
-
====================================================================
|
| 4 |
-
Corrigido para seguir exatamente a arquitetura oficial
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
from typing import List, Optional, Tuple, Union
|
| 8 |
-
|
| 9 |
-
import torch
|
| 10 |
-
import torch.nn as nn
|
| 11 |
-
|
| 12 |
-
from transformers import AutoConfig, AutoModelForCausalLM, \
|
| 13 |
-
Qwen2Config, Qwen2Model, Qwen2ForCausalLM
|
| 14 |
-
|
| 15 |
-
from transformers.modeling_outputs import CausalLMOutputWithPast
|
| 16 |
-
from transformers.generation.utils import GenerateOutput
|
| 17 |
-
|
| 18 |
-
from .omni2_speech_arch import Omni2SpeechMetaModel, Omni2SpeechMetaForCausalLM
|
| 19 |
-
from .constants import IGNORE_INDEX, SPEECH_TOKEN_INDEX
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
class Qwen2SpeechConfig(Qwen2Config):
|
| 23 |
-
"""Configuração para Qwen2.5 com Speech"""
|
| 24 |
-
model_type = "qwen2_speech_fixed"
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
class Qwen2SpeechModel(Omni2SpeechMetaModel, Qwen2Model):
|
| 28 |
-
"""Modelo base Qwen2.5 com capacidades de Speech"""
|
| 29 |
-
config_class = Qwen2SpeechConfig
|
| 30 |
-
|
| 31 |
-
def __init__(self, config: Qwen2Config):
|
| 32 |
-
super(Qwen2SpeechModel, self).__init__(config)
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
class Qwen2SpeechForCausalLM(Qwen2ForCausalLM, Omni2SpeechMetaForCausalLM):
|
| 36 |
-
"""
|
| 37 |
-
Qwen2.5 ForCausalLM com Speech Integration
|
| 38 |
-
IMPLEMENTAÇÃO IDÊNTICA AO LLAMA-OMNI2 OFICIAL
|
| 39 |
-
"""
|
| 40 |
-
config_class = Qwen2SpeechConfig
|
| 41 |
-
|
| 42 |
-
def __init__(self, config):
|
| 43 |
-
super(Qwen2ForCausalLM, self).__init__(config)
|
| 44 |
-
self.model = Qwen2SpeechModel(config)
|
| 45 |
-
self.vocab_size = config.vocab_size
|
| 46 |
-
self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
|
| 47 |
-
|
| 48 |
-
# Initialize weights and apply final processing
|
| 49 |
-
self.post_init()
|
| 50 |
-
|
| 51 |
-
def get_model(self):
|
| 52 |
-
return self.model
|
| 53 |
-
|
| 54 |
-
def forward(
|
| 55 |
-
self,
|
| 56 |
-
input_ids: torch.LongTensor = None,
|
| 57 |
-
attention_mask: Optional[torch.Tensor] = None,
|
| 58 |
-
position_ids: Optional[torch.LongTensor] = None,
|
| 59 |
-
past_key_values: Optional[List[torch.FloatTensor]] = None,
|
| 60 |
-
inputs_embeds: Optional[torch.FloatTensor] = None,
|
| 61 |
-
labels: Optional[torch.LongTensor] = None,
|
| 62 |
-
use_cache: Optional[bool] = None,
|
| 63 |
-
output_attentions: Optional[bool] = None,
|
| 64 |
-
output_hidden_states: Optional[bool] = None,
|
| 65 |
-
speech: Optional[torch.FloatTensor] = None,
|
| 66 |
-
speech_lengths: Optional[torch.LongTensor] = None,
|
| 67 |
-
return_dict: Optional[bool] = None,
|
| 68 |
-
cache_position: Optional[torch.LongTensor] = None,
|
| 69 |
-
) -> Union[Tuple, CausalLMOutputWithPast]:
|
| 70 |
-
|
| 71 |
-
if inputs_embeds is None:
|
| 72 |
-
(
|
| 73 |
-
input_ids,
|
| 74 |
-
position_ids,
|
| 75 |
-
attention_mask,
|
| 76 |
-
past_key_values,
|
| 77 |
-
inputs_embeds,
|
| 78 |
-
labels
|
| 79 |
-
) = self.prepare_inputs_labels_for_speech_and_text(
|
| 80 |
-
input_ids,
|
| 81 |
-
position_ids,
|
| 82 |
-
attention_mask,
|
| 83 |
-
past_key_values,
|
| 84 |
-
labels,
|
| 85 |
-
speech,
|
| 86 |
-
speech_lengths
|
| 87 |
-
)
|
| 88 |
-
|
| 89 |
-
return super().forward(
|
| 90 |
-
input_ids=input_ids,
|
| 91 |
-
attention_mask=attention_mask,
|
| 92 |
-
position_ids=position_ids,
|
| 93 |
-
past_key_values=past_key_values,
|
| 94 |
-
inputs_embeds=inputs_embeds,
|
| 95 |
-
labels=labels,
|
| 96 |
-
use_cache=use_cache,
|
| 97 |
-
output_attentions=output_attentions,
|
| 98 |
-
output_hidden_states=output_hidden_states,
|
| 99 |
-
return_dict=return_dict
|
| 100 |
-
)
|
| 101 |
-
|
| 102 |
-
def prepare_inputs_labels_for_speech_and_text(
|
| 103 |
-
self, input_ids, position_ids, attention_mask, past_key_values, labels,
|
| 104 |
-
speech, speech_lengths
|
| 105 |
-
):
|
| 106 |
-
"""
|
| 107 |
-
IMPLEMENTAÇÃO EXATA DO LLAMA-OMNI2 OFICIAL
|
| 108 |
-
Processa input_ids substituindo SPEECH_TOKEN_INDEX por speech features
|
| 109 |
-
"""
|
| 110 |
-
speech_encoder = self.get_speech_encoder()
|
| 111 |
-
if speech_encoder is None or speech is None or input_ids.shape[1] == 1:
|
| 112 |
-
return input_ids, position_ids, attention_mask, past_key_values, None, labels
|
| 113 |
-
|
| 114 |
-
# Encode speech features
|
| 115 |
-
speech_features = self.encode_speech(speech, speech_lengths)
|
| 116 |
-
|
| 117 |
-
_labels = labels
|
| 118 |
-
_position_ids = position_ids
|
| 119 |
-
_attention_mask = attention_mask
|
| 120 |
-
if attention_mask is None:
|
| 121 |
-
attention_mask = torch.ones_like(input_ids, dtype=torch.bool)
|
| 122 |
-
else:
|
| 123 |
-
attention_mask = attention_mask.bool()
|
| 124 |
-
if position_ids is None:
|
| 125 |
-
position_ids = torch.arange(0, input_ids.shape[1], dtype=torch.long, device=input_ids.device)
|
| 126 |
-
if labels is None:
|
| 127 |
-
labels = torch.full_like(input_ids, IGNORE_INDEX)
|
| 128 |
-
|
| 129 |
-
# Remove padding
|
| 130 |
-
input_ids = [cur_input_ids[cur_attention_mask] for cur_input_ids, cur_attention_mask in zip(input_ids, attention_mask)]
|
| 131 |
-
labels = [cur_labels[cur_attention_mask] for cur_labels, cur_attention_mask in zip(labels, attention_mask)]
|
| 132 |
-
|
| 133 |
-
new_input_embeds = []
|
| 134 |
-
new_labels = []
|
| 135 |
-
cur_speech_idx = 0
|
| 136 |
-
|
| 137 |
-
for batch_idx, cur_input_ids in enumerate(input_ids):
|
| 138 |
-
# Contar quantos SPEECH_TOKEN_INDEX existem
|
| 139 |
-
num_speech = (cur_input_ids == SPEECH_TOKEN_INDEX).sum()
|
| 140 |
-
|
| 141 |
-
# Encontrar posições dos SPEECH_TOKEN_INDEX
|
| 142 |
-
speech_token_indices = [-1] + torch.where(cur_input_ids == SPEECH_TOKEN_INDEX)[0].tolist() + [cur_input_ids.shape[0]]
|
| 143 |
-
|
| 144 |
-
# Separar tokens que não são speech
|
| 145 |
-
cur_input_ids_nospeech = []
|
| 146 |
-
cur_labels = labels[batch_idx]
|
| 147 |
-
cur_labels_nospeech = []
|
| 148 |
-
|
| 149 |
-
for i in range(len(speech_token_indices) - 1):
|
| 150 |
-
cur_input_ids_nospeech.append(cur_input_ids[speech_token_indices[i]+1:speech_token_indices[i+1]])
|
| 151 |
-
cur_labels_nospeech.append(cur_labels[speech_token_indices[i]+1:speech_token_indices[i+1]])
|
| 152 |
-
|
| 153 |
-
split_sizes = [x.shape[0] for x in cur_labels_nospeech]
|
| 154 |
-
|
| 155 |
-
# Gerar embeddings para tokens normais
|
| 156 |
-
cur_input_embeds = self.get_model().embed_tokens(torch.cat(cur_input_ids_nospeech))
|
| 157 |
-
cur_input_embeds_no_speech = torch.split(cur_input_embeds, split_sizes, dim=0)
|
| 158 |
-
|
| 159 |
-
cur_new_input_embeds = []
|
| 160 |
-
cur_new_labels = []
|
| 161 |
-
|
| 162 |
-
# Intercalar embeddings de texto com speech features
|
| 163 |
-
for i in range(num_speech + 1):
|
| 164 |
-
cur_new_input_embeds.append(cur_input_embeds_no_speech[i])
|
| 165 |
-
cur_new_labels.append(cur_labels_nospeech[i])
|
| 166 |
-
if i < num_speech:
|
| 167 |
-
cur_speech_features = speech_features[cur_speech_idx]
|
| 168 |
-
cur_speech_idx += 1
|
| 169 |
-
cur_new_input_embeds.append(cur_speech_features)
|
| 170 |
-
# Labels para speech features são IGNORE_INDEX
|
| 171 |
-
cur_new_labels.append(
|
| 172 |
-
torch.full((cur_speech_features.shape[0],), IGNORE_INDEX,
|
| 173 |
-
device=cur_labels.device, dtype=cur_labels.dtype)
|
| 174 |
-
)
|
| 175 |
-
|
| 176 |
-
# Concatenar tudo
|
| 177 |
-
cur_new_input_embeds = [x.to(self.device) for x in cur_new_input_embeds]
|
| 178 |
-
cur_new_input_embeds = torch.cat(cur_new_input_embeds)
|
| 179 |
-
cur_new_labels = torch.cat(cur_new_labels)
|
| 180 |
-
|
| 181 |
-
new_input_embeds.append(cur_new_input_embeds)
|
| 182 |
-
new_labels.append(cur_new_labels)
|
| 183 |
-
|
| 184 |
-
assert cur_speech_idx == len(speech_features)
|
| 185 |
-
|
| 186 |
-
# Truncar para max length se necessário
|
| 187 |
-
tokenizer_model_max_length = getattr(self.config, 'tokenizer_model_max_length', None)
|
| 188 |
-
if tokenizer_model_max_length is not None:
|
| 189 |
-
new_input_embeds = [x[:tokenizer_model_max_length] for x in new_input_embeds]
|
| 190 |
-
new_labels = [x[:tokenizer_model_max_length] for x in new_labels]
|
| 191 |
-
|
| 192 |
-
# Padding para batch
|
| 193 |
-
max_len = max(x.shape[0] for x in new_input_embeds)
|
| 194 |
-
batch_size = len(new_input_embeds)
|
| 195 |
-
|
| 196 |
-
new_input_embeds_padded = []
|
| 197 |
-
new_labels_padded = torch.full((batch_size, max_len), IGNORE_INDEX, dtype=new_labels[0].dtype, device=new_labels[0].device)
|
| 198 |
-
attention_mask = torch.zeros((batch_size, max_len), dtype=attention_mask.dtype, device=attention_mask.device)
|
| 199 |
-
position_ids = torch.zeros((batch_size, max_len), dtype=position_ids.dtype, device=position_ids.device)
|
| 200 |
-
|
| 201 |
-
for i, (cur_new_embed, cur_new_labels) in enumerate(zip(new_input_embeds, new_labels)):
|
| 202 |
-
cur_len = cur_new_embed.shape[0]
|
| 203 |
-
if getattr(self.config, 'tokenizer_padding_side', 'right') == "left":
|
| 204 |
-
new_input_embeds_padded.append(torch.cat((
|
| 205 |
-
torch.zeros((max_len - cur_len, cur_new_embed.shape[1]), dtype=cur_new_embed.dtype, device=cur_new_embed.device),
|
| 206 |
-
cur_new_embed
|
| 207 |
-
), dim=0))
|
| 208 |
-
if cur_len > 0:
|
| 209 |
-
new_labels_padded[i, -cur_len:] = cur_new_labels
|
| 210 |
-
attention_mask[i, -cur_len:] = True
|
| 211 |
-
position_ids[i, -cur_len:] = torch.arange(0, cur_len, dtype=position_ids.dtype, device=position_ids.device)
|
| 212 |
-
else:
|
| 213 |
-
new_input_embeds_padded.append(torch.cat((
|
| 214 |
-
cur_new_embed,
|
| 215 |
-
torch.zeros((max_len - cur_len, cur_new_embed.shape[1]), dtype=cur_new_embed.dtype, device=cur_new_embed.device)
|
| 216 |
-
), dim=0))
|
| 217 |
-
if cur_len > 0:
|
| 218 |
-
new_labels_padded[i, :cur_len] = cur_new_labels
|
| 219 |
-
attention_mask[i, :cur_len] = True
|
| 220 |
-
position_ids[i, :cur_len] = torch.arange(0, cur_len, dtype=position_ids.dtype, device=position_ids.device)
|
| 221 |
-
|
| 222 |
-
new_input_embeds = torch.stack(new_input_embeds_padded, dim=0)
|
| 223 |
-
|
| 224 |
-
if _labels is None:
|
| 225 |
-
new_labels = None
|
| 226 |
-
else:
|
| 227 |
-
new_labels = new_labels_padded
|
| 228 |
-
|
| 229 |
-
if _attention_mask is None:
|
| 230 |
-
attention_mask = None
|
| 231 |
-
else:
|
| 232 |
-
attention_mask = attention_mask.to(dtype=_attention_mask.dtype)
|
| 233 |
-
|
| 234 |
-
if _position_ids is None:
|
| 235 |
-
position_ids = None
|
| 236 |
-
|
| 237 |
-
return None, position_ids, attention_mask, past_key_values, new_input_embeds, new_labels
|
| 238 |
-
|
| 239 |
-
@torch.no_grad()
|
| 240 |
-
def generate(
|
| 241 |
-
self,
|
| 242 |
-
inputs: Optional[torch.Tensor] = None,
|
| 243 |
-
speech: Optional[torch.Tensor] = None,
|
| 244 |
-
speech_lengths: Optional[torch.Tensor] = None,
|
| 245 |
-
**kwargs,
|
| 246 |
-
) -> Union[GenerateOutput, torch.LongTensor]:
|
| 247 |
-
position_ids = kwargs.pop("position_ids", None)
|
| 248 |
-
attention_mask = kwargs.pop("attention_mask", None)
|
| 249 |
-
if "inputs_embeds" in kwargs:
|
| 250 |
-
raise NotImplementedError("`inputs_embeds` is not supported")
|
| 251 |
-
|
| 252 |
-
if speech is not None:
|
| 253 |
-
(
|
| 254 |
-
inputs,
|
| 255 |
-
position_ids,
|
| 256 |
-
attention_mask,
|
| 257 |
-
_,
|
| 258 |
-
inputs_embeds,
|
| 259 |
-
_
|
| 260 |
-
) = self.prepare_inputs_labels_for_speech_and_text(
|
| 261 |
-
inputs,
|
| 262 |
-
position_ids,
|
| 263 |
-
attention_mask,
|
| 264 |
-
None,
|
| 265 |
-
None,
|
| 266 |
-
speech,
|
| 267 |
-
speech_lengths
|
| 268 |
-
)
|
| 269 |
-
else:
|
| 270 |
-
inputs_embeds = self.get_model().embed_tokens(inputs)
|
| 271 |
-
|
| 272 |
-
return super().generate(
|
| 273 |
-
position_ids=position_ids,
|
| 274 |
-
attention_mask=attention_mask,
|
| 275 |
-
inputs_embeds=inputs_embeds,
|
| 276 |
-
**kwargs
|
| 277 |
-
)
|
| 278 |
-
|
| 279 |
-
def prepare_inputs_for_generation(self, input_ids, past_key_values=None,
|
| 280 |
-
inputs_embeds=None, **kwargs):
|
| 281 |
-
speech = kwargs.pop("speech", None)
|
| 282 |
-
speech_lengths = kwargs.pop("speech_lengths", None)
|
| 283 |
-
inputs = super().prepare_inputs_for_generation(
|
| 284 |
-
input_ids, past_key_values=past_key_values, inputs_embeds=inputs_embeds, **kwargs
|
| 285 |
-
)
|
| 286 |
-
if speech is not None:
|
| 287 |
-
inputs['speech'] = speech
|
| 288 |
-
inputs['speech_lengths'] = speech_lengths
|
| 289 |
-
return inputs
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
# Registrar o modelo
|
| 293 |
-
AutoConfig.register("qwen2_speech_fixed", Qwen2SpeechConfig)
|
| 294 |
-
AutoModelForCausalLM.register(Qwen2SpeechConfig, Qwen2SpeechForCausalLM)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
llama_omni2_integration/speech_encoder/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
# Speech Encoder Package
|
|
|
|
|
|
llama_omni2_integration/speech_encoder/builder.py
DELETED
|
@@ -1,9 +0,0 @@
|
|
| 1 |
-
from .speech_encoder import WhisperWrappedEncoder
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
def build_speech_encoder(config):
|
| 5 |
-
speech_encoder_type = getattr(config, 'speech_encoder_type', None)
|
| 6 |
-
if "whisper" in speech_encoder_type.lower():
|
| 7 |
-
return WhisperWrappedEncoder.load(config)
|
| 8 |
-
|
| 9 |
-
raise ValueError(f'Unknown speech encoder: {speech_encoder_type}')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
llama_omni2_integration/speech_encoder/speech_encoder.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 1 |
-
# This code is modified from https://github.com/ddlBoJack/SLAM-LLM/blob/main/src/slam_llm/models/encoder.py
|
| 2 |
-
|
| 3 |
-
import torch
|
| 4 |
-
import torch.nn as nn
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
class WhisperWrappedEncoder:
|
| 8 |
-
|
| 9 |
-
@classmethod
|
| 10 |
-
def load(cls, model_config):
|
| 11 |
-
|
| 12 |
-
def replace_layer_norm(module):
|
| 13 |
-
from whisper.model import LayerNorm
|
| 14 |
-
for name, child in module.named_children():
|
| 15 |
-
if isinstance(child, LayerNorm):
|
| 16 |
-
old_params = child.state_dict()
|
| 17 |
-
new_layer_norm = nn.LayerNorm(child.normalized_shape, eps=child.eps, elementwise_affine=child.elementwise_affine)
|
| 18 |
-
new_layer_norm.load_state_dict(old_params)
|
| 19 |
-
setattr(module, name, new_layer_norm)
|
| 20 |
-
else:
|
| 21 |
-
replace_layer_norm(child)
|
| 22 |
-
|
| 23 |
-
import whisper
|
| 24 |
-
encoder = whisper.load_model(name=model_config.speech_encoder, device='cpu').encoder
|
| 25 |
-
replace_layer_norm(encoder)
|
| 26 |
-
return encoder
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
llama_omni2_integration/speech_projector/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
# Speech Projector Package
|
|
|
|
|
|
llama_omni2_integration/speech_projector/builder.py
DELETED
|
@@ -1,9 +0,0 @@
|
|
| 1 |
-
from .speech_projector import EncoderProjectorConcat
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
def build_speech_projector(config):
|
| 5 |
-
projector_type = getattr(config, 'speech_projector_type', 'linear')
|
| 6 |
-
if projector_type == 'linear':
|
| 7 |
-
return EncoderProjectorConcat(config)
|
| 8 |
-
|
| 9 |
-
raise ValueError(f'Unknown projector type: {projector_type}')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
llama_omni2_integration/speech_projector/speech_projector.py
DELETED
|
@@ -1,30 +0,0 @@
|
|
| 1 |
-
# This code is modified from https://github.com/ddlBoJack/SLAM-LLM/blob/main/src/slam_llm/models/projector.py
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
import torch
|
| 5 |
-
import torch.nn as nn
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
class EncoderProjectorConcat(nn.Module):
|
| 9 |
-
def __init__(self, config):
|
| 10 |
-
super().__init__()
|
| 11 |
-
self.k = config.speech_encoder_ds_rate
|
| 12 |
-
self.encoder_dim = config.speech_encoder_hidden_size
|
| 13 |
-
self.llm_dim = config.hidden_size
|
| 14 |
-
self.linear1 = nn.Linear(self.encoder_dim * self.k, 2048)
|
| 15 |
-
self.relu = nn.ReLU()
|
| 16 |
-
self.linear2 = nn.Linear(2048, config.hidden_size)
|
| 17 |
-
|
| 18 |
-
def forward(self, x):
|
| 19 |
-
batch_size, seq_len, dim = x.size()
|
| 20 |
-
num_frames_to_discard = seq_len % self.k
|
| 21 |
-
if num_frames_to_discard > 0:
|
| 22 |
-
x = x[:, :-num_frames_to_discard, :]
|
| 23 |
-
seq_len = x.size(1)
|
| 24 |
-
|
| 25 |
-
x = x.contiguous()
|
| 26 |
-
x = x.view(batch_size, seq_len // self.k, dim * self.k)
|
| 27 |
-
x = self.linear1(x)
|
| 28 |
-
x = self.relu(x)
|
| 29 |
-
x = self.linear2(x)
|
| 30 |
-
return x
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
load_speech_projector.py
DELETED
|
@@ -1,184 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Script para carregar os pesos do Speech Projector pré-treinado
|
| 4 |
-
no modelo Qwen2.5 multilíngue para a arquitetura LLaMA-Omni2
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
import torch
|
| 8 |
-
import torch.nn as nn
|
| 9 |
-
from pathlib import Path
|
| 10 |
-
import logging
|
| 11 |
-
|
| 12 |
-
logging.basicConfig(level=logging.INFO)
|
| 13 |
-
logger = logging.getLogger(__name__)
|
| 14 |
-
|
| 15 |
-
def load_pretrained_speech_projector(model, projector_weights_path="models/speech_projector_weights.pt"):
|
| 16 |
-
"""
|
| 17 |
-
Carrega os pesos pré-treinados do Speech Projector inglês
|
| 18 |
-
no modelo multilíngue Qwen2.5.
|
| 19 |
-
|
| 20 |
-
Args:
|
| 21 |
-
model: Modelo LLaMA-Omni2 com Qwen2.5 multilíngue
|
| 22 |
-
projector_weights_path: Caminho para os pesos salvos
|
| 23 |
-
|
| 24 |
-
Returns:
|
| 25 |
-
model: Modelo com Speech Projector carregado
|
| 26 |
-
"""
|
| 27 |
-
|
| 28 |
-
if not Path(projector_weights_path).exists():
|
| 29 |
-
logger.warning(f"⚠️ Pesos do Speech Projector não encontrados em {projector_weights_path}")
|
| 30 |
-
logger.info("Baixando pesos do modelo inglês...")
|
| 31 |
-
|
| 32 |
-
# Baixar modelo inglês se necessário
|
| 33 |
-
from huggingface_hub import hf_hub_download
|
| 34 |
-
import safetensors.torch
|
| 35 |
-
|
| 36 |
-
try:
|
| 37 |
-
# Baixar arquivo safetensors
|
| 38 |
-
model_file = hf_hub_download(
|
| 39 |
-
repo_id="ICTNLP/LLaMA-Omni2-1.5B",
|
| 40 |
-
filename="model-00001-of-00002.safetensors",
|
| 41 |
-
local_dir="models/temp"
|
| 42 |
-
)
|
| 43 |
-
|
| 44 |
-
# Extrair apenas Speech Projector
|
| 45 |
-
state_dict = safetensors.torch.load_file(model_file)
|
| 46 |
-
projector_weights = {
|
| 47 |
-
k: v for k, v in state_dict.items()
|
| 48 |
-
if 'speech_projector' in k
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
# Salvar para uso futuro
|
| 52 |
-
torch.save(projector_weights, projector_weights_path)
|
| 53 |
-
logger.info(f"✅ Pesos extraídos e salvos em {projector_weights_path}")
|
| 54 |
-
|
| 55 |
-
except Exception as e:
|
| 56 |
-
logger.error(f"❌ Erro ao baixar pesos: {e}")
|
| 57 |
-
logger.info("Usando inicialização aleatória para Speech Projector")
|
| 58 |
-
return model
|
| 59 |
-
|
| 60 |
-
# Carregar pesos salvos
|
| 61 |
-
logger.info(f"📦 Carregando Speech Projector de {projector_weights_path}")
|
| 62 |
-
projector_weights = torch.load(projector_weights_path, map_location='cpu')
|
| 63 |
-
|
| 64 |
-
# Mapear para o modelo atual
|
| 65 |
-
loaded_keys = []
|
| 66 |
-
missing_keys = []
|
| 67 |
-
|
| 68 |
-
for name, param in model.named_parameters():
|
| 69 |
-
if 'speech_projector' in name:
|
| 70 |
-
if name in projector_weights:
|
| 71 |
-
param.data = projector_weights[name].to(param.dtype).to(param.device)
|
| 72 |
-
loaded_keys.append(name)
|
| 73 |
-
logger.debug(f"✓ Carregado: {name} {param.shape}")
|
| 74 |
-
else:
|
| 75 |
-
# Tentar com prefixo 'model.'
|
| 76 |
-
alt_name = f"model.{name}" if not name.startswith("model.") else name.replace("model.", "")
|
| 77 |
-
if alt_name in projector_weights:
|
| 78 |
-
param.data = projector_weights[alt_name].to(param.dtype).to(param.device)
|
| 79 |
-
loaded_keys.append(name)
|
| 80 |
-
logger.debug(f"✓ Carregado (alt): {name} {param.shape}")
|
| 81 |
-
else:
|
| 82 |
-
missing_keys.append(name)
|
| 83 |
-
logger.warning(f"✗ Não encontrado: {name}")
|
| 84 |
-
|
| 85 |
-
logger.info(f"✅ Speech Projector carregado com sucesso!")
|
| 86 |
-
logger.info(f" - {len(loaded_keys)} tensores carregados")
|
| 87 |
-
if missing_keys:
|
| 88 |
-
logger.warning(f" - {len(missing_keys)} tensores não encontrados (usando inicialização padrão)")
|
| 89 |
-
|
| 90 |
-
return model
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
def verify_speech_projector(model):
|
| 94 |
-
"""
|
| 95 |
-
Verifica se o Speech Projector está configurado corretamente.
|
| 96 |
-
|
| 97 |
-
Args:
|
| 98 |
-
model: Modelo para verificar
|
| 99 |
-
|
| 100 |
-
Returns:
|
| 101 |
-
bool: True se configurado corretamente
|
| 102 |
-
"""
|
| 103 |
-
has_projector = False
|
| 104 |
-
projector_params = {}
|
| 105 |
-
|
| 106 |
-
for name, param in model.named_parameters():
|
| 107 |
-
if 'speech_projector' in name:
|
| 108 |
-
has_projector = True
|
| 109 |
-
projector_params[name] = param.shape
|
| 110 |
-
|
| 111 |
-
if has_projector:
|
| 112 |
-
logger.info("🔍 Verificação do Speech Projector:")
|
| 113 |
-
for name, shape in projector_params.items():
|
| 114 |
-
logger.info(f" • {name}: {shape}")
|
| 115 |
-
|
| 116 |
-
# Verificar dimensões esperadas
|
| 117 |
-
expected_shapes = {
|
| 118 |
-
'linear1.weight': (2048, 6400),
|
| 119 |
-
'linear1.bias': (2048,),
|
| 120 |
-
'linear2.weight': (1536, 2048),
|
| 121 |
-
'linear2.bias': (1536,)
|
| 122 |
-
}
|
| 123 |
-
|
| 124 |
-
all_correct = True
|
| 125 |
-
for key, expected_shape in expected_shapes.items():
|
| 126 |
-
found = False
|
| 127 |
-
for name, shape in projector_params.items():
|
| 128 |
-
if key in name and tuple(shape) == expected_shape:
|
| 129 |
-
found = True
|
| 130 |
-
break
|
| 131 |
-
if not found:
|
| 132 |
-
logger.warning(f" ⚠️ Dimensão incorreta ou ausente para {key}")
|
| 133 |
-
all_correct = False
|
| 134 |
-
|
| 135 |
-
if all_correct:
|
| 136 |
-
logger.info(" ✅ Todas as dimensões estão corretas!")
|
| 137 |
-
|
| 138 |
-
return all_correct
|
| 139 |
-
else:
|
| 140 |
-
logger.error("❌ Speech Projector não encontrado no modelo!")
|
| 141 |
-
return False
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
def integrate_with_model_worker(model_worker_path="/workspace/llama-omni2-troca-llm/llama_omni2/serve/model_worker.py"):
|
| 145 |
-
"""
|
| 146 |
-
Gera código para integrar o carregamento do Speech Projector
|
| 147 |
-
no model_worker.py existente.
|
| 148 |
-
"""
|
| 149 |
-
|
| 150 |
-
integration_code = '''
|
| 151 |
-
# Adicionar após carregar o modelo (linha ~130)
|
| 152 |
-
# Em ModelWorker.__init__ após self.model = ...
|
| 153 |
-
|
| 154 |
-
# Carregar Speech Projector pré-treinado
|
| 155 |
-
from load_speech_projector import load_pretrained_speech_projector
|
| 156 |
-
self.model = load_pretrained_speech_projector(self.model)
|
| 157 |
-
logger.info("✅ Speech Projector pré-treinado carregado com sucesso!")
|
| 158 |
-
'''
|
| 159 |
-
|
| 160 |
-
logger.info("📝 Para integrar no model_worker.py, adicione:")
|
| 161 |
-
print(integration_code)
|
| 162 |
-
|
| 163 |
-
return integration_code
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
if __name__ == "__main__":
|
| 167 |
-
logger.info("=== Teste do Speech Projector ===")
|
| 168 |
-
|
| 169 |
-
# Simular carregamento (para teste)
|
| 170 |
-
logger.info("\n1. Verificando pesos disponíveis...")
|
| 171 |
-
weights_path = "models/speech_projector_weights.pt"
|
| 172 |
-
|
| 173 |
-
if Path(weights_path).exists():
|
| 174 |
-
weights = torch.load(weights_path, map_location='cpu')
|
| 175 |
-
logger.info(f"✅ Pesos encontrados com {len(weights)} tensores:")
|
| 176 |
-
for key, tensor in weights.items():
|
| 177 |
-
logger.info(f" • {key}: {tensor.shape}")
|
| 178 |
-
else:
|
| 179 |
-
logger.warning("⚠️ Pesos não encontrados. Execute o download primeiro.")
|
| 180 |
-
|
| 181 |
-
logger.info("\n2. Código de integração:")
|
| 182 |
-
integrate_with_model_worker()
|
| 183 |
-
|
| 184 |
-
logger.info("\n✨ Speech Projector pronto para uso!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
webrtc_server_gpu_vllm.py → server.py
RENAMED
|
File without changes
|
simple_speech_chat_torchcompiled.py
DELETED
|
@@ -1,230 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
🚀 Sistema de Chat por Voz - Versão com torch.compile()
|
| 4 |
-
======================================================
|
| 5 |
-
Baseado no relatório que alcançou 1030ms com otimizações críticas:
|
| 6 |
-
- torch.compile() para 27% mais rápido no LLM após aquecimento
|
| 7 |
-
- Whisper Singleton para evitar recarregamentos
|
| 8 |
-
- Parâmetros otimizados (20 tokens, 80 mel bins)
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
import warnings
|
| 12 |
-
warnings.filterwarnings('ignore')
|
| 13 |
-
|
| 14 |
-
import torch
|
| 15 |
-
import whisper
|
| 16 |
-
import time
|
| 17 |
-
import tempfile
|
| 18 |
-
import os
|
| 19 |
-
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 20 |
-
from gtts import gTTS
|
| 21 |
-
import io
|
| 22 |
-
import subprocess
|
| 23 |
-
import sys
|
| 24 |
-
|
| 25 |
-
# Importar Whisper Singleton
|
| 26 |
-
sys.path.append('/workspace/temp_test_installation/llama-omni2-official-code')
|
| 27 |
-
from llama_omni2.model.whisper_singleton import get_whisper_model
|
| 28 |
-
|
| 29 |
-
class OptimizedSpeechChat:
|
| 30 |
-
def __init__(self):
|
| 31 |
-
"""Inicializar com todas as otimizações do repo que atingiu 1030ms"""
|
| 32 |
-
print("🚀 Inicializando Chat Otimizado (torch.compile + Whisper Singleton)")
|
| 33 |
-
print("=" * 70)
|
| 34 |
-
|
| 35 |
-
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 36 |
-
|
| 37 |
-
# OTIMIZAÇÃO 1: Whisper Singleton (evita recarregamentos)
|
| 38 |
-
print("📦 [SINGLETON] Carregando Whisper...")
|
| 39 |
-
start = time.perf_counter()
|
| 40 |
-
self.whisper_model = get_whisper_model("base") # Singleton
|
| 41 |
-
whisper_time = (time.perf_counter() - start) * 1000
|
| 42 |
-
print(f" ✅ Whisper carregado em {whisper_time:.0f}ms (singleton)")
|
| 43 |
-
|
| 44 |
-
# OTIMIZAÇÃO 2: LLM com torch.compile()
|
| 45 |
-
print("📦 [COMPILE] Carregando LLM...")
|
| 46 |
-
start = time.perf_counter()
|
| 47 |
-
self.tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-1.5B-Instruct")
|
| 48 |
-
self.model = AutoModelForCausalLM.from_pretrained(
|
| 49 |
-
"Qwen/Qwen2.5-1.5B-Instruct",
|
| 50 |
-
torch_dtype=torch.float16, # Float16 para velocidade
|
| 51 |
-
device_map="auto"
|
| 52 |
-
)
|
| 53 |
-
|
| 54 |
-
if self.tokenizer.pad_token is None:
|
| 55 |
-
self.tokenizer.pad_token = self.tokenizer.eos_token
|
| 56 |
-
|
| 57 |
-
# OTIMIZAÇÃO CRÍTICA: torch.compile()
|
| 58 |
-
print("🔥 Aplicando torch.compile()...")
|
| 59 |
-
compile_start = time.perf_counter()
|
| 60 |
-
self.model = torch.compile(
|
| 61 |
-
self.model,
|
| 62 |
-
mode="reduce-overhead", # Modo otimizado para latência
|
| 63 |
-
backend="inductor"
|
| 64 |
-
)
|
| 65 |
-
compile_time = (time.perf_counter() - compile_start) * 1000
|
| 66 |
-
llm_time = (time.perf_counter() - start) * 1000
|
| 67 |
-
|
| 68 |
-
print(f" ✅ LLM carregado em {llm_time:.0f}ms")
|
| 69 |
-
print(f" 🔥 torch.compile aplicado em {compile_time:.0f}ms")
|
| 70 |
-
print(f" ⚠️ IMPORTANTE: Primeiras 2-3 execuções serão mais lentas (compilação JIT)")
|
| 71 |
-
|
| 72 |
-
print(f"\n📊 Inicialização total: {whisper_time + llm_time:.0f}ms")
|
| 73 |
-
|
| 74 |
-
def warmup(self, iterations=3):
|
| 75 |
-
"""Aquecimento crítico para torch.compile (conforme relatório)"""
|
| 76 |
-
print(f"\n🔥 EXECUTANDO AQUECIMENTO ({iterations} iterações)...")
|
| 77 |
-
print("=" * 50)
|
| 78 |
-
|
| 79 |
-
# Criar áudio de teste temporário
|
| 80 |
-
warmup_text = "Teste de aquecimento do sistema"
|
| 81 |
-
tts = gTTS(text=warmup_text, lang='pt-br', slow=False)
|
| 82 |
-
|
| 83 |
-
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
|
| 84 |
-
tts.write_to_fp(f)
|
| 85 |
-
mp3_path = f.name
|
| 86 |
-
|
| 87 |
-
wav_path = mp3_path.replace('.mp3', '.wav')
|
| 88 |
-
subprocess.run(f"ffmpeg -i {mp3_path} -ar 16000 {wav_path} -y -loglevel quiet",
|
| 89 |
-
shell=True, check=True)
|
| 90 |
-
os.remove(mp3_path)
|
| 91 |
-
|
| 92 |
-
warmup_times = []
|
| 93 |
-
for i in range(iterations):
|
| 94 |
-
start = time.perf_counter()
|
| 95 |
-
|
| 96 |
-
# Pipeline completa de warmup
|
| 97 |
-
transcricao = self.transcribe_optimized(wav_path)
|
| 98 |
-
resposta = self.generate_response_optimized(transcricao)
|
| 99 |
-
|
| 100 |
-
elapsed = (time.perf_counter() - start) * 1000
|
| 101 |
-
warmup_times.append(elapsed)
|
| 102 |
-
|
| 103 |
-
print(f" • Warmup {i+1}/{iterations}: {elapsed:.0f}ms")
|
| 104 |
-
|
| 105 |
-
if i == 0:
|
| 106 |
-
print(f" (Primeira execução - compilando JIT)")
|
| 107 |
-
elif i == iterations - 1:
|
| 108 |
-
print(f" (Sistema aquecido - performance real)")
|
| 109 |
-
|
| 110 |
-
os.remove(wav_path)
|
| 111 |
-
|
| 112 |
-
print(f"\n📊 Estatísticas do Aquecimento:")
|
| 113 |
-
print(f" • 1ª execução (compilação): {warmup_times[0]:.0f}ms")
|
| 114 |
-
print(f" • Última execução (aquecido): {warmup_times[-1]:.0f}ms")
|
| 115 |
-
|
| 116 |
-
if len(warmup_times) > 1:
|
| 117 |
-
melhoria = ((warmup_times[0] - warmup_times[-1]) / warmup_times[0]) * 100
|
| 118 |
-
print(f" • Melhoria após aquecimento: {melhoria:.1f}%")
|
| 119 |
-
|
| 120 |
-
print("🚀 Sistema totalmente aquecido e otimizado!")
|
| 121 |
-
|
| 122 |
-
def transcribe_optimized(self, audio_path):
|
| 123 |
-
"""Transcrição otimizada conforme repo 1030ms"""
|
| 124 |
-
# OTIMIZAÇÃO: Pipeline de áudio otimizada
|
| 125 |
-
audio = whisper.load_audio(audio_path)
|
| 126 |
-
audio = whisper.pad_or_trim(audio)
|
| 127 |
-
|
| 128 |
-
# OTIMIZAÇÃO: 80 mel bins (conforme repo otimizado)
|
| 129 |
-
mel = whisper.log_mel_spectrogram(audio, n_mels=80).to(self.whisper_model.device)
|
| 130 |
-
|
| 131 |
-
# OTIMIZAÇÃO: Não detectar idioma - fixar PT
|
| 132 |
-
options = whisper.DecodingOptions(
|
| 133 |
-
language="pt", # Fixar idioma
|
| 134 |
-
fp16=torch.cuda.is_available(),
|
| 135 |
-
without_timestamps=True # Mais rápido sem timestamps
|
| 136 |
-
)
|
| 137 |
-
|
| 138 |
-
result = whisper.decode(self.whisper_model, mel, options)
|
| 139 |
-
return result.text
|
| 140 |
-
|
| 141 |
-
def generate_response_optimized(self, text):
|
| 142 |
-
"""Geração otimizada conforme repo 1030ms"""
|
| 143 |
-
prompt = f"Responda brevemente em português: {text}"
|
| 144 |
-
inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)
|
| 145 |
-
|
| 146 |
-
with torch.no_grad():
|
| 147 |
-
# OTIMIZAÇÃO: 20 tokens conforme repo otimizado
|
| 148 |
-
outputs = self.model.generate(
|
| 149 |
-
**inputs,
|
| 150 |
-
max_new_tokens=20, # Conforme repo otimizado
|
| 151 |
-
temperature=0.7, # Parâmetros otimizados
|
| 152 |
-
top_p=0.9,
|
| 153 |
-
pad_token_id=self.tokenizer.eos_token_id,
|
| 154 |
-
do_sample=True
|
| 155 |
-
)
|
| 156 |
-
|
| 157 |
-
response = self.tokenizer.decode(
|
| 158 |
-
outputs[0][len(inputs.input_ids[0]):],
|
| 159 |
-
skip_special_tokens=True
|
| 160 |
-
).strip()
|
| 161 |
-
|
| 162 |
-
return response
|
| 163 |
-
|
| 164 |
-
def synthesize_optimized(self, text):
|
| 165 |
-
"""TTS otimizado com gTTS (mais rápido que Edge conforme relatório)"""
|
| 166 |
-
tts = gTTS(text=text, lang='pt-br', slow=False)
|
| 167 |
-
audio_buffer = io.BytesIO()
|
| 168 |
-
tts.write_to_fp(audio_buffer)
|
| 169 |
-
audio_buffer.seek(0)
|
| 170 |
-
|
| 171 |
-
# Salvar temporariamente
|
| 172 |
-
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
|
| 173 |
-
f.write(audio_buffer.getvalue())
|
| 174 |
-
mp3_path = f.name
|
| 175 |
-
|
| 176 |
-
# Converter para WAV
|
| 177 |
-
wav_path = mp3_path.replace('.mp3', '.wav')
|
| 178 |
-
subprocess.run(f"ffmpeg -i {mp3_path} -ar 16000 {wav_path} -y -loglevel quiet",
|
| 179 |
-
shell=True, check=True)
|
| 180 |
-
os.remove(mp3_path)
|
| 181 |
-
|
| 182 |
-
return wav_path
|
| 183 |
-
|
| 184 |
-
def process_full_pipeline(self, audio_path):
|
| 185 |
-
"""Pipeline completa otimizada"""
|
| 186 |
-
start = time.perf_counter()
|
| 187 |
-
|
| 188 |
-
# STT
|
| 189 |
-
stt_start = time.perf_counter()
|
| 190 |
-
transcricao = self.transcribe_optimized(audio_path)
|
| 191 |
-
stt_time = (time.perf_counter() - stt_start) * 1000
|
| 192 |
-
|
| 193 |
-
# LLM
|
| 194 |
-
llm_start = time.perf_counter()
|
| 195 |
-
resposta = self.generate_response_optimized(transcricao)
|
| 196 |
-
llm_time = (time.perf_counter() - llm_start) * 1000
|
| 197 |
-
|
| 198 |
-
# TTS
|
| 199 |
-
tts_start = time.perf_counter()
|
| 200 |
-
audio_response = self.synthesize_optimized(resposta)
|
| 201 |
-
tts_time = (time.perf_counter() - tts_start) * 1000
|
| 202 |
-
|
| 203 |
-
total_time = (time.perf_counter() - start) * 1000
|
| 204 |
-
|
| 205 |
-
return {
|
| 206 |
-
'transcricao': transcricao,
|
| 207 |
-
'resposta': resposta,
|
| 208 |
-
'audio_path': audio_response,
|
| 209 |
-
'timings': {
|
| 210 |
-
'stt': stt_time,
|
| 211 |
-
'llm': llm_time,
|
| 212 |
-
'tts': tts_time,
|
| 213 |
-
'total': total_time
|
| 214 |
-
}
|
| 215 |
-
}
|
| 216 |
-
|
| 217 |
-
def main():
|
| 218 |
-
"""Teste do sistema otimizado"""
|
| 219 |
-
# Inicializar sistema
|
| 220 |
-
system = OptimizedSpeechChat()
|
| 221 |
-
|
| 222 |
-
# CRÍTICO: Aquecimento para torch.compile
|
| 223 |
-
system.warmup()
|
| 224 |
-
|
| 225 |
-
print(f"\n🎤 SISTEMA PRONTO PARA USO")
|
| 226 |
-
print(f"💡 Latência esperada após aquecimento: ~1030ms (conforme relatório)")
|
| 227 |
-
print(f"📄 Use: system.process_full_pipeline('caminho_do_audio.wav')")
|
| 228 |
-
|
| 229 |
-
if __name__ == "__main__":
|
| 230 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
start.sh
CHANGED
|
@@ -1,270 +1,75 @@
|
|
| 1 |
#!/bin/bash
|
| 2 |
#
|
| 3 |
-
# Script de Inicialização
|
| 4 |
-
# ================================================
|
| 5 |
-
#
|
| 6 |
#
|
| 7 |
|
| 8 |
set -e
|
| 9 |
|
| 10 |
-
echo "🚀
|
| 11 |
-
echo "=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
-
# Verificar
|
| 14 |
-
if [ ! -
|
| 15 |
-
echo "❌
|
|
|
|
| 16 |
exit 1
|
| 17 |
fi
|
| 18 |
|
| 19 |
-
#
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
echo "🛑 Parando servidor..."
|
| 23 |
-
pkill -f "simple_speech_chat" 2>/dev/null || true
|
| 24 |
-
pkill -f "python.*gradio" 2>/dev/null || true
|
| 25 |
-
pkill -f "llama_omni2.serve" 2>/dev/null || true
|
| 26 |
-
echo "✅ Servidor parado"
|
| 27 |
-
exit 0
|
| 28 |
-
}
|
| 29 |
-
|
| 30 |
-
# Capturar Ctrl+C
|
| 31 |
-
trap cleanup SIGINT SIGTERM
|
| 32 |
-
|
| 33 |
-
# Verificar dependências
|
| 34 |
-
echo "🔍 Verificando sistema..."
|
| 35 |
-
|
| 36 |
-
python3 -c "
|
| 37 |
-
import warnings
|
| 38 |
-
warnings.filterwarnings('ignore')
|
| 39 |
-
|
| 40 |
-
try:
|
| 41 |
-
import torch
|
| 42 |
-
import whisper
|
| 43 |
-
import transformers
|
| 44 |
-
print('✅ Dependências OK')
|
| 45 |
-
except ImportError as e:
|
| 46 |
-
print(f'❌ Dependência faltando: {e}')
|
| 47 |
-
exit(1)
|
| 48 |
-
"
|
| 49 |
-
|
| 50 |
-
if [ $? -ne 0 ]; then
|
| 51 |
-
echo "❌ Dependências não instaladas"
|
| 52 |
-
echo "💡 Execute: ./install.sh"
|
| 53 |
-
exit 1
|
| 54 |
-
fi
|
| 55 |
|
| 56 |
# Verificar GPU
|
| 57 |
-
echo "
|
| 58 |
python3 -c "
|
| 59 |
import torch
|
| 60 |
if torch.cuda.is_available():
|
| 61 |
-
print(f'✅ GPU: {torch.cuda.get_device_name()}')
|
| 62 |
print(f' VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f}GB')
|
| 63 |
else:
|
| 64 |
-
print('⚠️ GPU não disponível
|
| 65 |
"
|
| 66 |
|
| 67 |
-
#
|
| 68 |
-
|
| 69 |
-
echo "
|
| 70 |
-
echo "
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
|
| 78 |
-
|
| 79 |
-
1)
|
| 80 |
-
echo "🚀 INICIANDO SERVIDOR WEBRTC UNIFICADO"
|
| 81 |
-
echo "="*50
|
| 82 |
-
echo ""
|
| 83 |
-
echo "🏗️ ARQUITETURA SIMPLIFICADA:"
|
| 84 |
-
echo "• 1 processo único (menor latência)"
|
| 85 |
-
echo "• WebRTC P2P direto"
|
| 86 |
-
echo "• Cache inteligente"
|
| 87 |
-
echo "• Auto-recovery"
|
| 88 |
-
echo ""
|
| 89 |
-
|
| 90 |
-
# Baixar modelo se necessário
|
| 91 |
-
MODEL_PATH="models/Qwen2.5-1.5B-Multilingual"
|
| 92 |
-
|
| 93 |
-
if [ ! -d "$MODEL_PATH" ]; then
|
| 94 |
-
echo "📥 Modelo não encontrado. Baixando Qwen2.5-1.5B..."
|
| 95 |
-
echo " Tamanho: ~3GB"
|
| 96 |
-
echo " Isso pode demorar alguns minutos..."
|
| 97 |
-
echo ""
|
| 98 |
-
|
| 99 |
-
python3 -c "
|
| 100 |
-
from huggingface_hub import snapshot_download
|
| 101 |
-
import os
|
| 102 |
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
local_dir='$MODEL_PATH',
|
| 109 |
-
local_dir_use_symlinks=False
|
| 110 |
-
)
|
| 111 |
-
print('✅ Modelo baixado com sucesso!')
|
| 112 |
-
except Exception as e:
|
| 113 |
-
print(f'❌ Erro ao baixar modelo: {e}')
|
| 114 |
-
exit(1)
|
| 115 |
-
"
|
| 116 |
-
if [ $? -ne 0 ]; then
|
| 117 |
-
echo "❌ Falha ao baixar modelo"
|
| 118 |
-
exit 1
|
| 119 |
-
fi
|
| 120 |
-
else
|
| 121 |
-
echo "✅ Modelo encontrado em $MODEL_PATH"
|
| 122 |
-
fi
|
| 123 |
-
|
| 124 |
-
echo ""
|
| 125 |
-
echo "🌐 Iniciando servidor WebRTC unificado..."
|
| 126 |
-
echo "📡 WebRTC endpoint: POST http://localhost:8080/offer"
|
| 127 |
-
echo "💚 Health check: GET http://localhost:8080/health"
|
| 128 |
-
echo "📊 Métricas: GET http://localhost:8080/metrics"
|
| 129 |
-
echo ""
|
| 130 |
-
echo "💡 Para testar: python3 test_unified_client.py"
|
| 131 |
-
echo ""
|
| 132 |
-
|
| 133 |
-
# Loop com auto-restart
|
| 134 |
-
while true; do
|
| 135 |
-
python3 unified_webrtc_server.py \
|
| 136 |
-
--model-path "$MODEL_PATH" \
|
| 137 |
-
--host 0.0.0.0 \
|
| 138 |
-
--port 8080
|
| 139 |
-
|
| 140 |
-
EXIT_CODE=$?
|
| 141 |
-
|
| 142 |
-
if [ $EXIT_CODE -eq 0 ] || [ $EXIT_CODE -eq 130 ]; then
|
| 143 |
-
echo "✅ Servidor finalizado"
|
| 144 |
-
break
|
| 145 |
-
else
|
| 146 |
-
echo "❌ Servidor crashou (código: $EXIT_CODE)"
|
| 147 |
-
echo "🔄 Reiniciando em 5 segundos..."
|
| 148 |
-
sleep 5
|
| 149 |
-
fi
|
| 150 |
-
done
|
| 151 |
-
;;
|
| 152 |
-
|
| 153 |
-
2)
|
| 154 |
-
echo "🏢 INICIANDO SERVIDOR OFICIAL"
|
| 155 |
-
echo "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "="
|
| 156 |
-
echo ""
|
| 157 |
-
echo "🚀 INICIANDO TRÊS SERVIÇOS OFICIAIS"
|
| 158 |
-
echo "🌐 Interface será acessível em: http://localhost:8000"
|
| 159 |
-
echo ""
|
| 160 |
-
|
| 161 |
-
# Perguntar qual modelo usar
|
| 162 |
-
echo "📦 MODELOS DISPONÍVEIS:"
|
| 163 |
-
echo "1) LLaMA-Omni2-1.5B (Original)"
|
| 164 |
-
echo "2) Qwen2.5-1.5B-Multilingual (Português)"
|
| 165 |
-
echo ""
|
| 166 |
-
read -p "Escolha o modelo (1-2) [2]: " MODEL_CHOICE
|
| 167 |
-
MODEL_CHOICE=${MODEL_CHOICE:-2}
|
| 168 |
-
|
| 169 |
-
if [ "$MODEL_CHOICE" = "2" ]; then
|
| 170 |
-
MODEL_PATH="models/Qwen2.5-1.5B-Multilingual"
|
| 171 |
-
MODEL_NAME="Qwen2.5-1.5B-PT"
|
| 172 |
-
echo "✅ Usando Qwen2.5 Multilíngue (Português)"
|
| 173 |
-
else
|
| 174 |
-
MODEL_PATH="models/LLaMA-Omni2-1.5B"
|
| 175 |
-
MODEL_NAME="LLaMA-Omni2-1.5B"
|
| 176 |
-
echo "✅ Usando LLaMA-Omni2 Original"
|
| 177 |
-
fi
|
| 178 |
-
|
| 179 |
-
# Verificar se modelo existe
|
| 180 |
-
if [ ! -d "$MODEL_PATH" ]; then
|
| 181 |
-
echo "❌ Modelo não encontrado em $MODEL_PATH"
|
| 182 |
-
exit 1
|
| 183 |
-
fi
|
| 184 |
-
|
| 185 |
-
# Aplicar fix temporário para transformers
|
| 186 |
-
echo "🔧 Aplicando fix de compatibilidade..."
|
| 187 |
-
export PYTHONWARNINGS="ignore"
|
| 188 |
-
|
| 189 |
-
# 1. Iniciar Controller
|
| 190 |
-
echo "🎮 [1/3] Iniciando Controller (porta 10000)..."
|
| 191 |
-
python3 -m llama_omni2.serve.controller --host 0.0.0.0 --port 10000 > controller.log 2>&1 &
|
| 192 |
-
CONTROLLER_PID=$!
|
| 193 |
-
sleep 3
|
| 194 |
-
|
| 195 |
-
# 2. Iniciar Model Worker (sem TTS - apenas texto)
|
| 196 |
-
echo "🤖 [2/3] Iniciando Model Worker com $MODEL_NAME (porta 40000)..."
|
| 197 |
-
python3 -m llama_omni2.serve.model_worker \
|
| 198 |
-
--host 0.0.0.0 \
|
| 199 |
-
--controller-address http://localhost:10000 \
|
| 200 |
-
--port 40000 \
|
| 201 |
-
--worker-address http://localhost:40000 \
|
| 202 |
-
--model-path $MODEL_PATH \
|
| 203 |
-
--model-name $MODEL_NAME > worker.log 2>&1 &
|
| 204 |
-
WORKER_PID=$!
|
| 205 |
-
sleep 5
|
| 206 |
-
|
| 207 |
-
# 3. Iniciar Web Server
|
| 208 |
-
echo "🌐 [3/3] Iniciando Web Server (porta 8000)..."
|
| 209 |
-
# Usar servidor web oficial
|
| 210 |
-
python3 -m llama_omni2.serve.gradio_web_server \
|
| 211 |
-
--controller-url http://localhost:10000 \
|
| 212 |
-
--port 8000 > webserver.log 2>&1 &
|
| 213 |
-
SERVER_PID=$!
|
| 214 |
-
sleep 3
|
| 215 |
-
|
| 216 |
-
# Verificar se todos os serviços estão rodando
|
| 217 |
-
echo ""
|
| 218 |
-
echo "🔍 VERIFICANDO SERVIÇOS..."
|
| 219 |
-
|
| 220 |
-
if kill -0 $CONTROLLER_PID 2>/dev/null; then
|
| 221 |
-
echo "✅ Controller: Rodando (PID $CONTROLLER_PID)"
|
| 222 |
-
else
|
| 223 |
-
echo "❌ Controller: Falhou"
|
| 224 |
-
fi
|
| 225 |
-
|
| 226 |
-
if kill -0 $WORKER_PID 2>/dev/null; then
|
| 227 |
-
echo "✅ Model Worker: Rodando (PID $WORKER_PID)"
|
| 228 |
-
else
|
| 229 |
-
echo "❌ Model Worker: Falhou"
|
| 230 |
-
fi
|
| 231 |
-
|
| 232 |
-
if kill -0 $SERVER_PID 2>/dev/null; then
|
| 233 |
-
echo "✅ Web Server: Rodando (PID $SERVER_PID)"
|
| 234 |
-
else
|
| 235 |
-
echo "❌ Web Server: Falhou"
|
| 236 |
-
fi
|
| 237 |
-
|
| 238 |
-
# Sistema inicia sem aquecimento (removido para simplificar)
|
| 239 |
-
|
| 240 |
-
echo ""
|
| 241 |
-
echo "🎉 SISTEMA OFICIAL INICIADO!"
|
| 242 |
-
echo "🌐 Acesse: http://localhost:8000"
|
| 243 |
-
echo "📋 Logs disponíveis: controller.log, worker.log, webserver.log"
|
| 244 |
-
echo ""
|
| 245 |
-
echo "💡 Para parar: Ctrl+C"
|
| 246 |
-
echo " Todos os processos serão finalizados automaticamente"
|
| 247 |
-
|
| 248 |
-
# Aguardar interrupção
|
| 249 |
-
wait $SERVER_PID
|
| 250 |
-
;;
|
| 251 |
-
|
| 252 |
-
3)
|
| 253 |
-
echo "🧪 TESTES DE ÁUDIO"
|
| 254 |
-
echo "="*50
|
| 255 |
-
echo ""
|
| 256 |
-
echo "🎤 Executando testes com áudios..."
|
| 257 |
-
|
| 258 |
-
# Executar teste de 50 perguntas
|
| 259 |
-
python3 test_50_questions_quality.py
|
| 260 |
-
;;
|
| 261 |
-
|
| 262 |
-
*)
|
| 263 |
-
echo "❌ Modo inválido"
|
| 264 |
-
exit 1
|
| 265 |
-
;;
|
| 266 |
-
esac
|
| 267 |
|
|
|
|
| 268 |
echo ""
|
| 269 |
-
echo "
|
| 270 |
-
echo "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
#!/bin/bash
|
| 2 |
#
|
| 3 |
+
# Script de Inicialização - LLaMA-Omni2 Speech-to-Speech
|
| 4 |
+
# ========================================================
|
| 5 |
+
# Sistema otimizado com GPU + vLLM para <500ms de latência
|
| 6 |
#
|
| 7 |
|
| 8 |
set -e
|
| 9 |
|
| 10 |
+
echo "🚀 INICIANDO SERVIDOR LLAMA-OMNI2"
|
| 11 |
+
echo "============================================"
|
| 12 |
+
echo "Sistema Speech-to-Speech com Qwen3-0.6B"
|
| 13 |
+
echo "Latência: <500ms | Coerência: 92%"
|
| 14 |
+
echo "============================================"
|
| 15 |
+
echo ""
|
| 16 |
|
| 17 |
+
# Verificar ambiente
|
| 18 |
+
if [ ! -f "/tmp/llama-omni2-vllm-env/bin/activate" ]; then
|
| 19 |
+
echo "❌ Ambiente virtual não encontrado!"
|
| 20 |
+
echo "💡 Execute primeiro: ./install.sh"
|
| 21 |
exit 1
|
| 22 |
fi
|
| 23 |
|
| 24 |
+
# Ativar ambiente
|
| 25 |
+
echo "🐍 Ativando ambiente virtual..."
|
| 26 |
+
source /tmp/llama-omni2-vllm-env/bin/activate
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
# Verificar GPU
|
| 29 |
+
echo "🎮 Verificando GPU..."
|
| 30 |
python3 -c "
|
| 31 |
import torch
|
| 32 |
if torch.cuda.is_available():
|
| 33 |
+
print(f'✅ GPU: {torch.cuda.get_device_name(0)}')
|
| 34 |
print(f' VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f}GB')
|
| 35 |
else:
|
| 36 |
+
print('⚠️ GPU não disponível - performance reduzida')
|
| 37 |
"
|
| 38 |
|
| 39 |
+
# Função para parar servidor
|
| 40 |
+
cleanup() {
|
| 41 |
+
echo ""
|
| 42 |
+
echo "🛑 Parando servidor..."
|
| 43 |
+
if [ ! -z "$SERVER_PID" ]; then
|
| 44 |
+
kill $SERVER_PID 2>/dev/null || true
|
| 45 |
+
fi
|
| 46 |
+
echo "✅ Servidor parado"
|
| 47 |
+
exit 0
|
| 48 |
+
}
|
| 49 |
|
| 50 |
+
trap cleanup SIGINT SIGTERM
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
+
# Iniciar servidor
|
| 53 |
+
echo ""
|
| 54 |
+
echo "🌐 Iniciando servidor WebRTC..."
|
| 55 |
+
echo "="*40
|
| 56 |
+
echo "📡 Endpoints:"
|
| 57 |
+
echo " • WebRTC: http://localhost:8888/offer"
|
| 58 |
+
echo " • Test: http://localhost:8888/test?text=Olá"
|
| 59 |
+
echo " • Stats: http://localhost:8888/stats"
|
| 60 |
+
echo "="*40
|
| 61 |
+
echo ""
|
| 62 |
|
| 63 |
+
# Rodar servidor em background
|
| 64 |
+
python3 server.py &
|
| 65 |
+
SERVER_PID=$!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
+
echo "✅ Servidor iniciado (PID: $SERVER_PID)"
|
| 68 |
echo ""
|
| 69 |
+
echo "💡 Comandos:"
|
| 70 |
+
echo " • Testar: curl 'http://localhost:8888/test?text=Olá'"
|
| 71 |
+
echo " • Parar: Ctrl+C"
|
| 72 |
+
echo ""
|
| 73 |
+
|
| 74 |
+
# Aguardar servidor
|
| 75 |
+
wait $SERVER_PID
|
stop.sh
DELETED
|
@@ -1,76 +0,0 @@
|
|
| 1 |
-
#!/bin/bash
|
| 2 |
-
|
| 3 |
-
# Script para parar todos os serviços do LLaMA-Omni2
|
| 4 |
-
# Criado para limpar processos travados e permitir reinicialização limpa
|
| 5 |
-
|
| 6 |
-
echo "🛑 Parando todos os serviços do LLaMA-Omni2..."
|
| 7 |
-
echo "=" * 50
|
| 8 |
-
|
| 9 |
-
# 1. Parar Web Server (porta 8000)
|
| 10 |
-
echo "🌐 Parando Web Server..."
|
| 11 |
-
pkill -f "gradio_web_server" 2>/dev/null
|
| 12 |
-
pkill -f "port 8000" 2>/dev/null
|
| 13 |
-
lsof -ti:8000 | xargs kill -9 2>/dev/null
|
| 14 |
-
|
| 15 |
-
# 2. Parar Model Worker (porta 40000)
|
| 16 |
-
echo "🤖 Parando Model Worker..."
|
| 17 |
-
pkill -f "model_worker" 2>/dev/null
|
| 18 |
-
pkill -f "port 40000" 2>/dev/null
|
| 19 |
-
lsof -ti:40000 | xargs kill -9 2>/dev/null
|
| 20 |
-
|
| 21 |
-
# 3. Parar Controller (porta 10000)
|
| 22 |
-
echo "🎮 Parando Controller..."
|
| 23 |
-
pkill -f "controller.py" 2>/dev/null
|
| 24 |
-
pkill -f "port 10000" 2>/dev/null
|
| 25 |
-
lsof -ti:10000 | xargs kill -9 2>/dev/null
|
| 26 |
-
|
| 27 |
-
# 4. Parar processos gRPC (portas 50051-50053)
|
| 28 |
-
echo "🔄 Parando serviços gRPC..."
|
| 29 |
-
pkill -f "grpc" 2>/dev/null
|
| 30 |
-
lsof -ti:50051 | xargs kill -9 2>/dev/null
|
| 31 |
-
lsof -ti:50052 | xargs kill -9 2>/dev/null
|
| 32 |
-
lsof -ti:50053 | xargs kill -9 2>/dev/null
|
| 33 |
-
|
| 34 |
-
# 5. Parar processos Python relacionados ao projeto
|
| 35 |
-
echo "🐍 Parando processos Python relacionados..."
|
| 36 |
-
pkill -f "llama_omni2" 2>/dev/null
|
| 37 |
-
pkill -f "Qwen2.5" 2>/dev/null
|
| 38 |
-
|
| 39 |
-
# 6. Limpar portas que possam estar travadas
|
| 40 |
-
echo "🧹 Limpando portas travadas..."
|
| 41 |
-
for port in 8000 10000 40000 50051 50052 50053; do
|
| 42 |
-
if lsof -Pi :$port -sTCP:LISTEN -t >/dev/null 2>&1; then
|
| 43 |
-
echo " Liberando porta $port..."
|
| 44 |
-
lsof -ti:$port | xargs kill -9 2>/dev/null
|
| 45 |
-
fi
|
| 46 |
-
done
|
| 47 |
-
|
| 48 |
-
# 7. Verificar o que ainda está rodando
|
| 49 |
-
echo ""
|
| 50 |
-
echo "📊 Verificando processos restantes..."
|
| 51 |
-
REMAINING=$(ps aux | grep -E "(model_worker|controller|gradio|llama_omni2)" | grep -v grep | wc -l)
|
| 52 |
-
|
| 53 |
-
if [ "$REMAINING" -eq 0 ]; then
|
| 54 |
-
echo "✅ Todos os serviços foram parados com sucesso!"
|
| 55 |
-
else
|
| 56 |
-
echo "⚠️ Ainda existem $REMAINING processos rodando:"
|
| 57 |
-
ps aux | grep -E "(model_worker|controller|gradio|llama_omni2)" | grep -v grep
|
| 58 |
-
echo ""
|
| 59 |
-
echo "Forçando parada completa..."
|
| 60 |
-
ps aux | grep -E "(model_worker|controller|gradio|llama_omni2)" | grep -v grep | awk '{print $2}' | xargs kill -9 2>/dev/null
|
| 61 |
-
echo "✅ Parada forçada concluída!"
|
| 62 |
-
fi
|
| 63 |
-
|
| 64 |
-
# 8. Limpar arquivos de log temporários (opcional)
|
| 65 |
-
echo ""
|
| 66 |
-
read -p "Limpar logs antigos? (s/N): " -n 1 -r
|
| 67 |
-
echo
|
| 68 |
-
if [[ $REPLY =~ ^[Ss]$ ]]; then
|
| 69 |
-
echo "🗑️ Limpando logs..."
|
| 70 |
-
rm -f worker.log controller.log web_server.log 2>/dev/null
|
| 71 |
-
echo "✅ Logs limpos!"
|
| 72 |
-
fi
|
| 73 |
-
|
| 74 |
-
echo ""
|
| 75 |
-
echo "🎉 Sistema pronto para reinicialização!"
|
| 76 |
-
echo "Execute './start.sh' para iniciar novamente."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
streaming_latency_test.py
DELETED
|
@@ -1,262 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Teste de Latência de Streaming - Primeiro Chunk de Áudio
|
| 4 |
-
======================================================
|
| 5 |
-
Medir Time to First Audio Byte (TTFAB) e latência de streaming
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
import warnings
|
| 9 |
-
warnings.filterwarnings('ignore')
|
| 10 |
-
|
| 11 |
-
import os
|
| 12 |
-
import time
|
| 13 |
-
import torch
|
| 14 |
-
import whisper
|
| 15 |
-
import tempfile
|
| 16 |
-
from gtts import gTTS
|
| 17 |
-
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 18 |
-
import threading
|
| 19 |
-
import queue
|
| 20 |
-
|
| 21 |
-
class StreamingLatencyTester:
|
| 22 |
-
"""Testador de latência com foco no primeiro chunk"""
|
| 23 |
-
|
| 24 |
-
def __init__(self):
|
| 25 |
-
print("🎯 TESTE DE LATÊNCIA DE STREAMING")
|
| 26 |
-
print("=" * 40)
|
| 27 |
-
|
| 28 |
-
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 29 |
-
|
| 30 |
-
print("📦 Carregando modelos...")
|
| 31 |
-
start_load = time.time()
|
| 32 |
-
|
| 33 |
-
self.whisper = whisper.load_model("base")
|
| 34 |
-
self.tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-1.5B-Instruct")
|
| 35 |
-
self.model = AutoModelForCausalLM.from_pretrained(
|
| 36 |
-
"Qwen/Qwen2.5-1.5B-Instruct",
|
| 37 |
-
torch_dtype=torch.float16,
|
| 38 |
-
device_map="auto"
|
| 39 |
-
)
|
| 40 |
-
if self.tokenizer.pad_token is None:
|
| 41 |
-
self.tokenizer.pad_token = self.tokenizer.eos_token
|
| 42 |
-
|
| 43 |
-
load_time = time.time() - start_load
|
| 44 |
-
print(f"✅ Modelos carregados em {load_time:.2f}s\n")
|
| 45 |
-
|
| 46 |
-
def measure_stt_latency(self, audio_path):
|
| 47 |
-
"""Medir latência detalhada do STT"""
|
| 48 |
-
print("🎤 Medindo latência STT...")
|
| 49 |
-
|
| 50 |
-
times = {}
|
| 51 |
-
|
| 52 |
-
# 1. Carregamento do áudio
|
| 53 |
-
start = time.time()
|
| 54 |
-
speech = whisper.load_audio(audio_path)
|
| 55 |
-
times['audio_load'] = (time.time() - start) * 1000
|
| 56 |
-
|
| 57 |
-
# 2. Preprocessamento
|
| 58 |
-
start = time.time()
|
| 59 |
-
speech = whisper.pad_or_trim(speech)
|
| 60 |
-
mel = whisper.log_mel_spectrogram(speech)
|
| 61 |
-
times['preprocessing'] = (time.time() - start) * 1000
|
| 62 |
-
|
| 63 |
-
# 3. Transcrição
|
| 64 |
-
start = time.time()
|
| 65 |
-
result = self.whisper.transcribe(audio_path, language="pt")
|
| 66 |
-
transcription = result["text"].strip()
|
| 67 |
-
times['transcription'] = (time.time() - start) * 1000
|
| 68 |
-
|
| 69 |
-
times['stt_total'] = times['audio_load'] + times['preprocessing'] + times['transcription']
|
| 70 |
-
|
| 71 |
-
print(f" 📄 Load: {times['audio_load']:.0f}ms")
|
| 72 |
-
print(f" 🔧 Prep: {times['preprocessing']:.0f}ms")
|
| 73 |
-
print(f" 📝 STT: {times['transcription']:.0f}ms")
|
| 74 |
-
print(f" ⏱️ Total: {times['stt_total']:.0f}ms")
|
| 75 |
-
|
| 76 |
-
return transcription, times
|
| 77 |
-
|
| 78 |
-
def measure_llm_streaming(self, text):
|
| 79 |
-
"""Medir latência LLM com simulação de streaming"""
|
| 80 |
-
print("🧠 Medindo latência LLM...")
|
| 81 |
-
|
| 82 |
-
times = {}
|
| 83 |
-
|
| 84 |
-
# 1. Tokenização
|
| 85 |
-
start = time.time()
|
| 86 |
-
prompt = f"Responda brevemente: {text}"
|
| 87 |
-
inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)
|
| 88 |
-
times['tokenization'] = (time.time() - start) * 1000
|
| 89 |
-
|
| 90 |
-
# 2. Primeiro token (TTFT - Time to First Token)
|
| 91 |
-
start = time.time()
|
| 92 |
-
with torch.no_grad():
|
| 93 |
-
# Simular primeiro token
|
| 94 |
-
outputs = self.model.generate(
|
| 95 |
-
**inputs,
|
| 96 |
-
max_new_tokens=1, # Apenas primeiro token
|
| 97 |
-
temperature=0.7,
|
| 98 |
-
do_sample=True,
|
| 99 |
-
pad_token_id=self.tokenizer.eos_token_id
|
| 100 |
-
)
|
| 101 |
-
times['first_token'] = (time.time() - start) * 1000
|
| 102 |
-
|
| 103 |
-
# 3. Geração completa
|
| 104 |
-
start = time.time()
|
| 105 |
-
with torch.no_grad():
|
| 106 |
-
outputs = self.model.generate(
|
| 107 |
-
**inputs,
|
| 108 |
-
max_new_tokens=15,
|
| 109 |
-
temperature=0.7,
|
| 110 |
-
do_sample=True,
|
| 111 |
-
pad_token_id=self.tokenizer.eos_token_id
|
| 112 |
-
)
|
| 113 |
-
|
| 114 |
-
response = self.tokenizer.decode(
|
| 115 |
-
outputs[0][len(inputs.input_ids[0]):],
|
| 116 |
-
skip_special_tokens=True
|
| 117 |
-
).strip()
|
| 118 |
-
|
| 119 |
-
if '.' in response:
|
| 120 |
-
response = response.split('.')[0] + '.'
|
| 121 |
-
|
| 122 |
-
times['full_generation'] = (time.time() - start) * 1000
|
| 123 |
-
times['llm_total'] = times['tokenization'] + times['full_generation']
|
| 124 |
-
|
| 125 |
-
print(f" 🔤 Token: {times['tokenization']:.0f}ms")
|
| 126 |
-
print(f" ⚡ First: {times['first_token']:.0f}ms")
|
| 127 |
-
print(f" 📝 Full: {times['full_generation']:.0f}ms")
|
| 128 |
-
print(f" ⏱️ Total: {times['llm_total']:.0f}ms")
|
| 129 |
-
|
| 130 |
-
return response, times
|
| 131 |
-
|
| 132 |
-
def measure_tts_streaming(self, text):
|
| 133 |
-
"""Medir latência TTS com foco no primeiro chunk"""
|
| 134 |
-
print("🔊 Medindo latência TTS...")
|
| 135 |
-
|
| 136 |
-
times = {}
|
| 137 |
-
|
| 138 |
-
# 1. Inicialização TTS
|
| 139 |
-
start = time.time()
|
| 140 |
-
tts = gTTS(text=text, lang='pt', slow=False)
|
| 141 |
-
times['tts_init'] = (time.time() - start) * 1000
|
| 142 |
-
|
| 143 |
-
# 2. Geração inicial (primeiro chunk simulado)
|
| 144 |
-
start = time.time()
|
| 145 |
-
temp_file = tempfile.NamedTemporaryFile(suffix='.mp3', delete=False)
|
| 146 |
-
output_path = temp_file.name
|
| 147 |
-
temp_file.close()
|
| 148 |
-
tts.save(output_path)
|
| 149 |
-
times['first_chunk'] = (time.time() - start) * 1000
|
| 150 |
-
|
| 151 |
-
# 3. Conversão para WAV
|
| 152 |
-
start = time.time()
|
| 153 |
-
wav_path = output_path.replace('.mp3', '.wav')
|
| 154 |
-
os.system(f"ffmpeg -i {output_path} -ar 16000 {wav_path} -y -loglevel quiet")
|
| 155 |
-
times['conversion'] = (time.time() - start) * 1000
|
| 156 |
-
|
| 157 |
-
times['tts_total'] = times['tts_init'] + times['first_chunk'] + times['conversion']
|
| 158 |
-
|
| 159 |
-
print(f" 🎛️ Init: {times['tts_init']:.0f}ms")
|
| 160 |
-
print(f" 🎵 Chunk: {times['first_chunk']:.0f}ms")
|
| 161 |
-
print(f" 🔄 Conv: {times['conversion']:.0f}ms")
|
| 162 |
-
print(f" ⏱️ Total: {times['tts_total']:.0f}ms")
|
| 163 |
-
|
| 164 |
-
# Limpar
|
| 165 |
-
os.remove(output_path)
|
| 166 |
-
os.remove(wav_path)
|
| 167 |
-
|
| 168 |
-
return times
|
| 169 |
-
|
| 170 |
-
def measure_end_to_end_streaming(self, audio_path):
|
| 171 |
-
"""Medir latência end-to-end com foco em streaming"""
|
| 172 |
-
print(f"\n🚀 TESTE END-TO-END: {os.path.basename(audio_path)}")
|
| 173 |
-
print("-" * 50)
|
| 174 |
-
|
| 175 |
-
pipeline_start = time.time()
|
| 176 |
-
|
| 177 |
-
# 1. STT
|
| 178 |
-
transcription, stt_times = self.measure_stt_latency(audio_path)
|
| 179 |
-
print(f"📝 Transcrito: '{transcription}'")
|
| 180 |
-
|
| 181 |
-
# 2. LLM
|
| 182 |
-
response, llm_times = self.measure_llm_streaming(transcription)
|
| 183 |
-
print(f"💬 Resposta: '{response}'")
|
| 184 |
-
|
| 185 |
-
# 3. TTS
|
| 186 |
-
tts_times = self.measure_tts_streaming(response)
|
| 187 |
-
|
| 188 |
-
# Calcular métricas de streaming
|
| 189 |
-
time_to_first_token = stt_times['stt_total'] + llm_times['first_token']
|
| 190 |
-
time_to_first_audio = time_to_first_token + tts_times['first_chunk']
|
| 191 |
-
total_pipeline = time.time() - pipeline_start
|
| 192 |
-
|
| 193 |
-
print(f"\n📊 MÉTRICAS DE STREAMING:")
|
| 194 |
-
print(f" ⚡ Time to First Token: {time_to_first_token:.0f}ms")
|
| 195 |
-
print(f" 🎵 Time to First Audio: {time_to_first_audio:.0f}ms")
|
| 196 |
-
print(f" 🏁 Pipeline completo: {total_pipeline*1000:.0f}ms")
|
| 197 |
-
|
| 198 |
-
return {
|
| 199 |
-
"transcription": transcription,
|
| 200 |
-
"response": response,
|
| 201 |
-
"stt_times": stt_times,
|
| 202 |
-
"llm_times": llm_times,
|
| 203 |
-
"tts_times": tts_times,
|
| 204 |
-
"streaming_metrics": {
|
| 205 |
-
"time_to_first_token_ms": time_to_first_token,
|
| 206 |
-
"time_to_first_audio_ms": time_to_first_audio,
|
| 207 |
-
"total_pipeline_ms": total_pipeline * 1000
|
| 208 |
-
}
|
| 209 |
-
}
|
| 210 |
-
|
| 211 |
-
def main():
|
| 212 |
-
tester = StreamingLatencyTester()
|
| 213 |
-
|
| 214 |
-
# Testar com diferentes áudios
|
| 215 |
-
test_files = [
|
| 216 |
-
"test_audios/01_Olá.wav",
|
| 217 |
-
"test_audios/04_Bom_dia.wav",
|
| 218 |
-
"test_audios/05_Como_vai.wav"
|
| 219 |
-
]
|
| 220 |
-
|
| 221 |
-
results = []
|
| 222 |
-
|
| 223 |
-
for audio_file in test_files:
|
| 224 |
-
full_path = f"/workspace/llama-omni2-official/{audio_file}"
|
| 225 |
-
|
| 226 |
-
if os.path.exists(full_path):
|
| 227 |
-
result = tester.measure_end_to_end_streaming(full_path)
|
| 228 |
-
results.append(result)
|
| 229 |
-
else:
|
| 230 |
-
print(f"⚠️ Arquivo não encontrado: {audio_file}")
|
| 231 |
-
|
| 232 |
-
# Calcular médias
|
| 233 |
-
if results:
|
| 234 |
-
ttft_avg = sum(r['streaming_metrics']['time_to_first_token_ms'] for r in results) / len(results)
|
| 235 |
-
ttfa_avg = sum(r['streaming_metrics']['time_to_first_audio_ms'] for r in results) / len(results)
|
| 236 |
-
total_avg = sum(r['streaming_metrics']['total_pipeline_ms'] for r in results) / len(results)
|
| 237 |
-
|
| 238 |
-
print(f"\n🏆 RESUMO FINAL - LATÊNCIAS DE STREAMING")
|
| 239 |
-
print("=" * 50)
|
| 240 |
-
print(f"⚡ Time to First Token (TTFT): {ttft_avg:.0f}ms")
|
| 241 |
-
print(f"🎵 Time to First Audio (TTFA): {ttfa_avg:.0f}ms")
|
| 242 |
-
print(f"🏁 Pipeline Completo: {total_avg:.0f}ms")
|
| 243 |
-
|
| 244 |
-
print(f"\n🎯 AVALIAÇÃO:")
|
| 245 |
-
if ttfa_avg <= 500:
|
| 246 |
-
print("🚀 EXCELENTE: Latência muito baixa para primeiro áudio")
|
| 247 |
-
elif ttfa_avg <= 1000:
|
| 248 |
-
print("✅ BOM: Latência aceitável para primeiro áudio")
|
| 249 |
-
elif ttfa_avg <= 2000:
|
| 250 |
-
print("⚠️ MÉDIO: Latência perceptível mas usável")
|
| 251 |
-
else:
|
| 252 |
-
print("❌ ALTO: Latência alta para streaming")
|
| 253 |
-
|
| 254 |
-
print(f"\n💡 Para streaming real:")
|
| 255 |
-
print(f" - TTFT ideal: < 200ms")
|
| 256 |
-
print(f" - TTFA ideal: < 500ms")
|
| 257 |
-
print(f" - Atual TTFA: {ttfa_avg:.0f}ms")
|
| 258 |
-
|
| 259 |
-
return results
|
| 260 |
-
|
| 261 |
-
if __name__ == "__main__":
|
| 262 |
-
results = main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
system_prompt_v2.md
DELETED
|
@@ -1,94 +0,0 @@
|
|
| 1 |
-
# System Prompt Melhorado V2
|
| 2 |
-
|
| 3 |
-
## VERSÃO ATUAL (85% coerência):
|
| 4 |
-
```
|
| 5 |
-
Você é um assistente conversacional amigável.
|
| 6 |
-
REGRAS:
|
| 7 |
-
1. Responda APENAS a pergunta feita
|
| 8 |
-
2. Use entre 7-15 palavras
|
| 9 |
-
3. Termine com pontuação apropriada
|
| 10 |
-
4. Seja natural e direto
|
| 11 |
-
5. NÃO invente continuação da conversa
|
| 12 |
-
```
|
| 13 |
-
|
| 14 |
-
## VERSÃO MELHORADA (meta: 90%+ coerência):
|
| 15 |
-
```
|
| 16 |
-
Você é um assistente que fala APENAS português brasileiro.
|
| 17 |
-
|
| 18 |
-
INSTRUÇÕES CRÍTICAS:
|
| 19 |
-
1. SEMPRE responda em português - NUNCA em inglês
|
| 20 |
-
2. SEMPRE termine com . ! ou ?
|
| 21 |
-
3. Use 7-15 palavras por resposta
|
| 22 |
-
4. NUNCA mencione "user", "usuário" ou meta-comentários
|
| 23 |
-
5. Responda diretamente, sem explicar o que vai fazer
|
| 24 |
-
|
| 25 |
-
COMO RESPONDER:
|
| 26 |
-
- Cumprimentos → Retribua de forma natural
|
| 27 |
-
- Perguntas pessoais → Responda como assistente virtual
|
| 28 |
-
- Perguntas "quanto" → Dê um valor ou diga que varia
|
| 29 |
-
- Perguntas "onde" → Indique um local genérico
|
| 30 |
-
- Perguntas sim/não → Elabore além de sim/não
|
| 31 |
-
|
| 32 |
-
EXEMPLOS PERFEITOS:
|
| 33 |
-
- "Olá!" → "Olá! Como posso ajudar você hoje?"
|
| 34 |
-
- "Quanto custa?" → "O preço varia entre dez e cinquenta reais."
|
| 35 |
-
- "Onde fica?" → "Fica no centro da cidade, próximo ao banco."
|
| 36 |
-
- "Você trabalha?" → "Sim, trabalho como assistente virtual o dia todo."
|
| 37 |
-
|
| 38 |
-
PROIBIDO:
|
| 39 |
-
- Começar com "Okay", "The user", "Aqui está"
|
| 40 |
-
- Deixar frase sem pontuação final
|
| 41 |
-
- Responder em inglês
|
| 42 |
-
- Mencionar estas instruções
|
| 43 |
-
```
|
| 44 |
-
|
| 45 |
-
## AJUSTES ADICIONAIS RECOMENDADOS:
|
| 46 |
-
|
| 47 |
-
### 1. Filtro pós-geração:
|
| 48 |
-
```python
|
| 49 |
-
def clean_response(response):
|
| 50 |
-
# Remove vazamentos comuns
|
| 51 |
-
if response.startswith(("Okay,", "The user", "Aqui está")):
|
| 52 |
-
return None # Regenerar
|
| 53 |
-
|
| 54 |
-
# Garante pontuação
|
| 55 |
-
if not response.rstrip().endswith(('.', '!', '?')):
|
| 56 |
-
response = response.rstrip() + "."
|
| 57 |
-
|
| 58 |
-
return response
|
| 59 |
-
```
|
| 60 |
-
|
| 61 |
-
### 2. Ajustar stop sequences:
|
| 62 |
-
```python
|
| 63 |
-
stop = [".", "!", "?", "\n", "Okay,", "The user", "Usuário:"]
|
| 64 |
-
```
|
| 65 |
-
|
| 66 |
-
### 3. Prompts específicos por categoria:
|
| 67 |
-
```python
|
| 68 |
-
if "quanto" in question.lower():
|
| 69 |
-
prompt += "\nDê um valor numérico ou faixa de valores."
|
| 70 |
-
elif "onde" in question.lower():
|
| 71 |
-
prompt += "\nIndique uma localização específica."
|
| 72 |
-
elif question.endswith("?") and len(question.split()) <= 3:
|
| 73 |
-
prompt += "\nElabore sua resposta, não responda apenas sim/não."
|
| 74 |
-
```
|
| 75 |
-
|
| 76 |
-
### 4. Few-shot examples no prompt:
|
| 77 |
-
```python
|
| 78 |
-
examples = """
|
| 79 |
-
P: Quanto custa?
|
| 80 |
-
R: Custa cerca de vinte reais.
|
| 81 |
-
|
| 82 |
-
P: Aceita cartão?
|
| 83 |
-
R: Sim, aceitamos cartão de crédito e débito.
|
| 84 |
-
|
| 85 |
-
P: Onde fica o banco?
|
| 86 |
-
R: O banco fica na rua principal do centro.
|
| 87 |
-
"""
|
| 88 |
-
```
|
| 89 |
-
|
| 90 |
-
## RESULTADO ESPERADO:
|
| 91 |
-
- Eliminar 100% dos vazamentos em inglês (-6%)
|
| 92 |
-
- Reduzir respostas sem pontuação (-5%)
|
| 93 |
-
- Melhorar respostas específicas (-4%)
|
| 94 |
-
- **Meta: 95%+ de coerência mantendo <400ms**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test_100_questions_final.py
DELETED
|
@@ -1,401 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Teste COMPLETO: 100 perguntas com avaliação inteligente de coerência
|
| 4 |
-
Usa a configuração otimizada final com min_tokens
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
import os
|
| 8 |
-
import time
|
| 9 |
-
import sys
|
| 10 |
-
import json
|
| 11 |
-
|
| 12 |
-
os.environ['TRANSFORMERS_CACHE'] = '/tmp/hf_cache'
|
| 13 |
-
os.environ['HF_HOME'] = '/tmp/hf_cache'
|
| 14 |
-
|
| 15 |
-
print("=" * 70)
|
| 16 |
-
print("🎯 TESTE FINAL - 100 PERGUNTAS COM AVALIAÇÃO INTELIGENTE")
|
| 17 |
-
print("=" * 70)
|
| 18 |
-
|
| 19 |
-
# System prompt otimizado
|
| 20 |
-
SYSTEM_PROMPT = """Você é um assistente conversacional amigável.
|
| 21 |
-
|
| 22 |
-
REGRAS:
|
| 23 |
-
1. Responda APENAS a pergunta feita
|
| 24 |
-
2. Use entre 7-15 palavras
|
| 25 |
-
3. Termine com pontuação apropriada
|
| 26 |
-
4. Seja natural e direto
|
| 27 |
-
5. NÃO invente continuação da conversa"""
|
| 28 |
-
|
| 29 |
-
# 100 perguntas organizadas por categoria
|
| 30 |
-
QUESTIONS = {
|
| 31 |
-
"Cumprimentos": [
|
| 32 |
-
"Olá, como você está?",
|
| 33 |
-
"Bom dia!",
|
| 34 |
-
"Boa tarde!",
|
| 35 |
-
"Boa noite!",
|
| 36 |
-
"Oi, tudo bem?",
|
| 37 |
-
"Como vai você?",
|
| 38 |
-
"E aí, beleza?",
|
| 39 |
-
"Prazer em conhecê-lo",
|
| 40 |
-
"Até logo!",
|
| 41 |
-
"Tchau!"
|
| 42 |
-
],
|
| 43 |
-
"Identidade": [
|
| 44 |
-
"Qual é o seu nome?",
|
| 45 |
-
"Quem é você?",
|
| 46 |
-
"Você é humano?",
|
| 47 |
-
"Você é um robô?",
|
| 48 |
-
"Qual sua idade?",
|
| 49 |
-
"Quando você nasceu?",
|
| 50 |
-
"Você tem apelido?",
|
| 51 |
-
"Como devo te chamar?",
|
| 52 |
-
"Você é homem ou mulher?",
|
| 53 |
-
"Você tem sobrenome?"
|
| 54 |
-
],
|
| 55 |
-
"Origem": [
|
| 56 |
-
"De onde você é?",
|
| 57 |
-
"Onde você nasceu?",
|
| 58 |
-
"Em que país você vive?",
|
| 59 |
-
"Qual sua nacionalidade?",
|
| 60 |
-
"Você é brasileiro?",
|
| 61 |
-
"De que cidade você é?",
|
| 62 |
-
"Onde você mora?",
|
| 63 |
-
"Qual seu endereço?",
|
| 64 |
-
"Você mora no Brasil?",
|
| 65 |
-
"Em que estado você vive?"
|
| 66 |
-
],
|
| 67 |
-
"Família": [
|
| 68 |
-
"Você tem família?",
|
| 69 |
-
"Você tem irmãos?",
|
| 70 |
-
"Seus pais estão vivos?",
|
| 71 |
-
"Você é casado?",
|
| 72 |
-
"Tem filhos?",
|
| 73 |
-
"Quantos irmãos você tem?",
|
| 74 |
-
"Como está sua mãe?",
|
| 75 |
-
"Seu pai trabalha?",
|
| 76 |
-
"Você tem avós?",
|
| 77 |
-
"Tem namorada?"
|
| 78 |
-
],
|
| 79 |
-
"Trabalho": [
|
| 80 |
-
"Você trabalha?",
|
| 81 |
-
"Qual sua profissão?",
|
| 82 |
-
"Onde você trabalha?",
|
| 83 |
-
"Você gosta do seu trabalho?",
|
| 84 |
-
"Quanto você ganha?",
|
| 85 |
-
"Você tem chefe?",
|
| 86 |
-
"Trabalha em equipe?",
|
| 87 |
-
"Que horas você trabalha?",
|
| 88 |
-
"Você trabalha aos sábados?",
|
| 89 |
-
"Está de férias?"
|
| 90 |
-
],
|
| 91 |
-
"Rotina": [
|
| 92 |
-
"Que horas você acorda?",
|
| 93 |
-
"O que você come no café?",
|
| 94 |
-
"A que horas almoça?",
|
| 95 |
-
"Você dorme cedo?",
|
| 96 |
-
"Toma banho de manhã?",
|
| 97 |
-
"Como vai ao trabalho?",
|
| 98 |
-
"Você janta em casa?",
|
| 99 |
-
"Assiste TV à noite?",
|
| 100 |
-
"Faz exercícios?",
|
| 101 |
-
"Você estuda?"
|
| 102 |
-
],
|
| 103 |
-
"Preferências": [
|
| 104 |
-
"O que você gosta de comer?",
|
| 105 |
-
"Qual sua cor favorita?",
|
| 106 |
-
"Você gosta de música?",
|
| 107 |
-
"Prefere frio ou calor?",
|
| 108 |
-
"Gosta de praia?",
|
| 109 |
-
"Você bebe café?",
|
| 110 |
-
"Gosta de doce?",
|
| 111 |
-
"Prefere cão ou gato?",
|
| 112 |
-
"Você fuma?",
|
| 113 |
-
"Gosta de viajar?"
|
| 114 |
-
],
|
| 115 |
-
"Habilidades": [
|
| 116 |
-
"Você fala inglês?",
|
| 117 |
-
"Sabe cozinhar?",
|
| 118 |
-
"Você dirige?",
|
| 119 |
-
"Toca algum instrumento?",
|
| 120 |
-
"Sabe nadar?",
|
| 121 |
-
"Você dança?",
|
| 122 |
-
"Sabe cantar?",
|
| 123 |
-
"Fala outras línguas?",
|
| 124 |
-
"Você desenha?",
|
| 125 |
-
"Sabe programar?"
|
| 126 |
-
],
|
| 127 |
-
"Situações": [
|
| 128 |
-
"Você está ocupado?",
|
| 129 |
-
"Pode me ajudar?",
|
| 130 |
-
"Você está bem?",
|
| 131 |
-
"Está com fome?",
|
| 132 |
-
"Tem tempo agora?",
|
| 133 |
-
"Você está cansado?",
|
| 134 |
-
"Está feliz hoje?",
|
| 135 |
-
"Você está doente?",
|
| 136 |
-
"Precisa de algo?",
|
| 137 |
-
"Está com pressa?"
|
| 138 |
-
],
|
| 139 |
-
"Perguntas Gerais": [
|
| 140 |
-
"Que dia é hoje?",
|
| 141 |
-
"Que horas são?",
|
| 142 |
-
"Como está o tempo?",
|
| 143 |
-
"Vai chover?",
|
| 144 |
-
"Está frio?",
|
| 145 |
-
"Onde fica o banco?",
|
| 146 |
-
"Tem farmácia perto?",
|
| 147 |
-
"Quanto custa?",
|
| 148 |
-
"Aceita cartão?",
|
| 149 |
-
"Tem troco?"
|
| 150 |
-
]
|
| 151 |
-
}
|
| 152 |
-
|
| 153 |
-
def evaluate_coherence(question, response):
|
| 154 |
-
"""
|
| 155 |
-
Avalia se a resposta é coerente com a pergunta
|
| 156 |
-
Análise contextual inteligente, não baseada em keywords
|
| 157 |
-
"""
|
| 158 |
-
|
| 159 |
-
# Verificações básicas de qualidade
|
| 160 |
-
if not response or len(response.strip()) < 3:
|
| 161 |
-
return False, "Resposta vazia ou muito curta"
|
| 162 |
-
|
| 163 |
-
# Não deve conter vazamentos
|
| 164 |
-
if any(x in response for x in ["Usuário:", "Assistente:", "user said", "```"]):
|
| 165 |
-
return False, "Contém vazamento de conversa"
|
| 166 |
-
|
| 167 |
-
# Deve ter pontuação final
|
| 168 |
-
if not response.rstrip().endswith(('.', '!', '?')):
|
| 169 |
-
return False, "Sem pontuação final"
|
| 170 |
-
|
| 171 |
-
# Análise contextual por tipo de pergunta
|
| 172 |
-
q_lower = question.lower()
|
| 173 |
-
r_lower = response.lower()
|
| 174 |
-
|
| 175 |
-
# Cumprimentos
|
| 176 |
-
if any(x in q_lower for x in ["olá", "oi", "bom dia", "boa tarde", "boa noite", "tchau", "até"]):
|
| 177 |
-
if any(x in r_lower for x in ["olá", "oi", "bom", "boa", "bem", "tchau", "até", "prazer", "também"]):
|
| 178 |
-
return True, "Cumprimento apropriado"
|
| 179 |
-
return False, "Não respondeu ao cumprimento"
|
| 180 |
-
|
| 181 |
-
# Perguntas sobre identidade
|
| 182 |
-
if "nome" in q_lower or "quem" in q_lower:
|
| 183 |
-
if len(response) > 10: # Resposta elaborada
|
| 184 |
-
return True, "Identificação apropriada"
|
| 185 |
-
return False, "Resposta inadequada sobre identidade"
|
| 186 |
-
|
| 187 |
-
# Perguntas sim/não
|
| 188 |
-
if q_lower.startswith(("você", "tem", "está", "pode", "sabe", "gosta", "prefere", "fala")):
|
| 189 |
-
if len(response.split()) >= 3: # Não apenas "sim" ou "não"
|
| 190 |
-
return True, "Resposta elaborada"
|
| 191 |
-
return False, "Resposta muito simples para pergunta sim/não"
|
| 192 |
-
|
| 193 |
-
# Perguntas sobre localização
|
| 194 |
-
if any(x in q_lower for x in ["onde", "qual endereço", "que cidade"]):
|
| 195 |
-
if len(response) > 10:
|
| 196 |
-
return True, "Informação de localização"
|
| 197 |
-
return False, "Resposta inadequada sobre local"
|
| 198 |
-
|
| 199 |
-
# Perguntas sobre tempo
|
| 200 |
-
if any(x in q_lower for x in ["que horas", "que dia", "quando"]):
|
| 201 |
-
if len(response) > 8:
|
| 202 |
-
return True, "Informação temporal"
|
| 203 |
-
return False, "Resposta inadequada sobre tempo"
|
| 204 |
-
|
| 205 |
-
# Perguntas sobre quantidade
|
| 206 |
-
if q_lower.startswith("quantos") or "quanto" in q_lower:
|
| 207 |
-
if any(char.isdigit() for char in response) or any(x in r_lower for x in ["não tenho", "nenhum", "vários", "alguns", "muitos"]):
|
| 208 |
-
return True, "Resposta quantitativa"
|
| 209 |
-
return False, "Não respondeu quantidade"
|
| 210 |
-
|
| 211 |
-
# Para outras perguntas, verifica se tem conteúdo substancial
|
| 212 |
-
word_count = len(response.split())
|
| 213 |
-
if word_count >= 4:
|
| 214 |
-
return True, "Resposta com conteúdo adequado"
|
| 215 |
-
|
| 216 |
-
return False, "Resposta inadequada ou muito curta"
|
| 217 |
-
|
| 218 |
-
try:
|
| 219 |
-
from vllm import LLM, SamplingParams
|
| 220 |
-
|
| 221 |
-
print("\n⏳ Carregando modelo com vLLM...")
|
| 222 |
-
start_load = time.time()
|
| 223 |
-
|
| 224 |
-
# Usar o modelo Qwen3-4B (nova geração, não Qwen2.5!)
|
| 225 |
-
model = LLM(
|
| 226 |
-
model="Qwen/Qwen3-4B", # Modelo Qwen3 nova geração
|
| 227 |
-
trust_remote_code=True,
|
| 228 |
-
dtype="float16",
|
| 229 |
-
gpu_memory_utilization=0.90,
|
| 230 |
-
max_model_len=1024,
|
| 231 |
-
disable_log_stats=True,
|
| 232 |
-
download_dir="/tmp/models_cache"
|
| 233 |
-
)
|
| 234 |
-
|
| 235 |
-
print(f"✅ Modelo carregado em {time.time()-start_load:.1f}s")
|
| 236 |
-
|
| 237 |
-
# Configuração otimizada final
|
| 238 |
-
sampling_params = SamplingParams(
|
| 239 |
-
min_tokens=7,
|
| 240 |
-
max_tokens=20,
|
| 241 |
-
temperature=0.0,
|
| 242 |
-
stop=[".", "!", "?", "\n", "Usuário:"],
|
| 243 |
-
include_stop_str_in_output=True,
|
| 244 |
-
repetition_penalty=1.2
|
| 245 |
-
)
|
| 246 |
-
|
| 247 |
-
# Warm-up
|
| 248 |
-
print("\n🔥 Aquecimento...")
|
| 249 |
-
for i in range(3):
|
| 250 |
-
_ = model.generate(["teste"], SamplingParams(max_tokens=5, temperature=0))
|
| 251 |
-
|
| 252 |
-
# Teste das 100 perguntas
|
| 253 |
-
print("\n📊 Testando 100 perguntas...")
|
| 254 |
-
print("-" * 70)
|
| 255 |
-
|
| 256 |
-
all_results = []
|
| 257 |
-
category_results = {}
|
| 258 |
-
total_coherent = 0
|
| 259 |
-
total_latency = 0
|
| 260 |
-
|
| 261 |
-
question_number = 0
|
| 262 |
-
for category, questions in QUESTIONS.items():
|
| 263 |
-
print(f"\n📚 {category}:")
|
| 264 |
-
cat_coherent = 0
|
| 265 |
-
cat_latency = 0
|
| 266 |
-
|
| 267 |
-
for question in questions:
|
| 268 |
-
question_number += 1
|
| 269 |
-
|
| 270 |
-
# Gera resposta
|
| 271 |
-
prompt = f"{SYSTEM_PROMPT}\n\nUsuário: {question}\nAssistente:"
|
| 272 |
-
|
| 273 |
-
start = time.time()
|
| 274 |
-
outputs = model.generate([prompt], sampling_params)
|
| 275 |
-
latency = (time.time() - start) * 1000
|
| 276 |
-
|
| 277 |
-
response = outputs[0].outputs[0].text.strip() if outputs else ""
|
| 278 |
-
|
| 279 |
-
# Avalia coerência
|
| 280 |
-
is_coherent, reason = evaluate_coherence(question, response)
|
| 281 |
-
|
| 282 |
-
# Armazena resultado
|
| 283 |
-
result = {
|
| 284 |
-
"question": question,
|
| 285 |
-
"response": response,
|
| 286 |
-
"coherent": is_coherent,
|
| 287 |
-
"reason": reason,
|
| 288 |
-
"latency": latency
|
| 289 |
-
}
|
| 290 |
-
all_results.append(result)
|
| 291 |
-
|
| 292 |
-
if is_coherent:
|
| 293 |
-
total_coherent += 1
|
| 294 |
-
cat_coherent += 1
|
| 295 |
-
symbol = "✅"
|
| 296 |
-
else:
|
| 297 |
-
symbol = "❌"
|
| 298 |
-
|
| 299 |
-
total_latency += latency
|
| 300 |
-
cat_latency += latency
|
| 301 |
-
|
| 302 |
-
# Mostra progresso
|
| 303 |
-
print(f" [{question_number:3d}/100] {symbol} {latency:4.0f}ms | {question[:30]:30s}")
|
| 304 |
-
|
| 305 |
-
# Estatísticas da categoria
|
| 306 |
-
category_results[category] = {
|
| 307 |
-
"coherent": cat_coherent,
|
| 308 |
-
"total": len(questions),
|
| 309 |
-
"accuracy": (cat_coherent / len(questions)) * 100,
|
| 310 |
-
"avg_latency": cat_latency / len(questions)
|
| 311 |
-
}
|
| 312 |
-
print(f" → Categoria: {cat_coherent}/{len(questions)} ({cat_coherent*10}%) - {cat_latency/len(questions):.0f}ms média")
|
| 313 |
-
|
| 314 |
-
# Resultados finais
|
| 315 |
-
print("\n" + "=" * 70)
|
| 316 |
-
print("📊 RESULTADOS FINAIS - 100 PERGUNTAS")
|
| 317 |
-
print("=" * 70)
|
| 318 |
-
|
| 319 |
-
accuracy = (total_coherent / 100) * 100
|
| 320 |
-
avg_latency = total_latency / 100
|
| 321 |
-
|
| 322 |
-
print(f"\n✅ COERÊNCIA GERAL: {total_coherent}/100 ({accuracy:.1f}%)")
|
| 323 |
-
print(f"⏱️ LATÊNCIA MÉDIA: {avg_latency:.0f}ms")
|
| 324 |
-
|
| 325 |
-
print("\n📈 POR CATEGORIA:")
|
| 326 |
-
for cat, stats in category_results.items():
|
| 327 |
-
print(f" {cat:20s}: {stats['coherent']:2d}/10 ({stats['accuracy']:.0f}%) - {stats['avg_latency']:.0f}ms")
|
| 328 |
-
|
| 329 |
-
# Análise de problemas
|
| 330 |
-
incoherent = [r for r in all_results if not r["coherent"]]
|
| 331 |
-
if incoherent:
|
| 332 |
-
print(f"\n❌ RESPOSTAS INCOERENTES ({len(incoherent)}):")
|
| 333 |
-
reasons = {}
|
| 334 |
-
for r in incoherent:
|
| 335 |
-
reasons[r["reason"]] = reasons.get(r["reason"], 0) + 1
|
| 336 |
-
|
| 337 |
-
for reason, count in sorted(reasons.items(), key=lambda x: x[1], reverse=True):
|
| 338 |
-
print(f" • {reason}: {count} casos")
|
| 339 |
-
|
| 340 |
-
# Exemplos
|
| 341 |
-
print("\n📝 EXEMPLOS DE RESPOSTAS:")
|
| 342 |
-
|
| 343 |
-
# Mostra 5 boas e 5 ruins
|
| 344 |
-
coherent_examples = [r for r in all_results if r["coherent"]][:5]
|
| 345 |
-
incoherent_examples = [r for r in all_results if not r["coherent"]][:5]
|
| 346 |
-
|
| 347 |
-
if coherent_examples:
|
| 348 |
-
print("\n✅ RESPOSTAS COERENTES:")
|
| 349 |
-
for r in coherent_examples:
|
| 350 |
-
print(f" P: {r['question']}")
|
| 351 |
-
print(f" R: {r['response']}")
|
| 352 |
-
print(f" [{r['latency']:.0f}ms] {r['reason']}")
|
| 353 |
-
print()
|
| 354 |
-
|
| 355 |
-
if incoherent_examples:
|
| 356 |
-
print("\n❌ RESPOSTAS INCOERENTES:")
|
| 357 |
-
for r in incoherent_examples:
|
| 358 |
-
print(f" P: {r['question']}")
|
| 359 |
-
print(f" R: {r['response']}")
|
| 360 |
-
print(f" [{r['latency']:.0f}ms] Problema: {r['reason']}")
|
| 361 |
-
print()
|
| 362 |
-
|
| 363 |
-
# Conclusão
|
| 364 |
-
print("=" * 70)
|
| 365 |
-
print("🎯 CONCLUSÃO:")
|
| 366 |
-
|
| 367 |
-
if accuracy >= 80:
|
| 368 |
-
print(f" ✅ EXCELENTE! {accuracy:.0f}% de coerência")
|
| 369 |
-
elif accuracy >= 70:
|
| 370 |
-
print(f" ✅ BOM! {accuracy:.0f}% de coerência")
|
| 371 |
-
elif accuracy >= 60:
|
| 372 |
-
print(f" ⚠️ ACEITÁVEL. {accuracy:.0f}% de coerência")
|
| 373 |
-
else:
|
| 374 |
-
print(f" ❌ PRECISA MELHORAR. Apenas {accuracy:.0f}% de coerência")
|
| 375 |
-
|
| 376 |
-
if avg_latency < 600:
|
| 377 |
-
print(f" ✅ Latência ÓTIMA: {avg_latency:.0f}ms")
|
| 378 |
-
elif avg_latency < 1000:
|
| 379 |
-
print(f" ✅ Latência BOA: {avg_latency:.0f}ms")
|
| 380 |
-
else:
|
| 381 |
-
print(f" ⚠️ Latência ALTA: {avg_latency:.0f}ms")
|
| 382 |
-
|
| 383 |
-
# Salvar resultados
|
| 384 |
-
with open("/tmp/test_100_results.json", "w") as f:
|
| 385 |
-
json.dump({
|
| 386 |
-
"accuracy": accuracy,
|
| 387 |
-
"avg_latency": avg_latency,
|
| 388 |
-
"total_coherent": total_coherent,
|
| 389 |
-
"categories": category_results,
|
| 390 |
-
"all_results": all_results
|
| 391 |
-
}, f, indent=2, ensure_ascii=False)
|
| 392 |
-
|
| 393 |
-
print(f"\n💾 Resultados salvos em /tmp/test_100_results.json")
|
| 394 |
-
|
| 395 |
-
except Exception as e:
|
| 396 |
-
print(f"❌ Erro: {e}")
|
| 397 |
-
import traceback
|
| 398 |
-
traceback.print_exc()
|
| 399 |
-
sys.exit(1)
|
| 400 |
-
|
| 401 |
-
print("\n" + "=" * 70)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test_100_questions_final_v1.py
DELETED
|
@@ -1,413 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Teste FINAL - Versão 1 (MELHOR PERFORMANCE)
|
| 4 |
-
85% coerência com 376ms de latência
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
import os
|
| 8 |
-
import time
|
| 9 |
-
import sys
|
| 10 |
-
import json
|
| 11 |
-
|
| 12 |
-
os.environ['TRANSFORMERS_CACHE'] = '/tmp/hf_cache'
|
| 13 |
-
os.environ['HF_HOME'] = '/tmp/hf_cache'
|
| 14 |
-
|
| 15 |
-
print("=" * 70)
|
| 16 |
-
print("🎯 TESTE FINAL - CONFIGURAÇÃO V1 (MELHOR RESULTADO)")
|
| 17 |
-
print("=" * 70)
|
| 18 |
-
|
| 19 |
-
# System prompt V1 - simples e eficaz
|
| 20 |
-
SYSTEM_PROMPT = """Você é um assistente conversacional amigável.
|
| 21 |
-
|
| 22 |
-
REGRAS:
|
| 23 |
-
1. Responda APENAS a pergunta feita
|
| 24 |
-
2. Use entre 7-15 palavras
|
| 25 |
-
3. Termine com pontuação apropriada
|
| 26 |
-
4. Seja natural e direto
|
| 27 |
-
5. NÃO invente continuação da conversa"""
|
| 28 |
-
|
| 29 |
-
# 100 perguntas organizadas por categoria
|
| 30 |
-
QUESTIONS = {
|
| 31 |
-
"Cumprimentos": [
|
| 32 |
-
"Olá, como você está?",
|
| 33 |
-
"Bom dia!",
|
| 34 |
-
"Boa tarde!",
|
| 35 |
-
"Boa noite!",
|
| 36 |
-
"Oi, tudo bem?",
|
| 37 |
-
"Como vai você?",
|
| 38 |
-
"E aí, beleza?",
|
| 39 |
-
"Prazer em conhecê-lo",
|
| 40 |
-
"Até logo!",
|
| 41 |
-
"Tchau!"
|
| 42 |
-
],
|
| 43 |
-
"Identidade": [
|
| 44 |
-
"Qual é o seu nome?",
|
| 45 |
-
"Quem é você?",
|
| 46 |
-
"Você é humano?",
|
| 47 |
-
"Você é um robô?",
|
| 48 |
-
"Qual sua idade?",
|
| 49 |
-
"Quando você nasceu?",
|
| 50 |
-
"Você tem apelido?",
|
| 51 |
-
"Como devo te chamar?",
|
| 52 |
-
"Você é homem ou mulher?",
|
| 53 |
-
"Você tem sobrenome?"
|
| 54 |
-
],
|
| 55 |
-
"Origem": [
|
| 56 |
-
"De onde você é?",
|
| 57 |
-
"Onde você nasceu?",
|
| 58 |
-
"Em que país você vive?",
|
| 59 |
-
"Qual sua nacionalidade?",
|
| 60 |
-
"Você é brasileiro?",
|
| 61 |
-
"De que cidade você é?",
|
| 62 |
-
"Onde você mora?",
|
| 63 |
-
"Qual seu endereço?",
|
| 64 |
-
"Você mora no Brasil?",
|
| 65 |
-
"Em que estado você vive?"
|
| 66 |
-
],
|
| 67 |
-
"Família": [
|
| 68 |
-
"Você tem família?",
|
| 69 |
-
"Você tem irmãos?",
|
| 70 |
-
"Seus pais estão vivos?",
|
| 71 |
-
"Você é casado?",
|
| 72 |
-
"Tem filhos?",
|
| 73 |
-
"Quantos irmãos você tem?",
|
| 74 |
-
"Como está sua mãe?",
|
| 75 |
-
"Seu pai trabalha?",
|
| 76 |
-
"Você tem avós?",
|
| 77 |
-
"Tem namorada?"
|
| 78 |
-
],
|
| 79 |
-
"Trabalho": [
|
| 80 |
-
"Você trabalha?",
|
| 81 |
-
"Qual sua profissão?",
|
| 82 |
-
"Onde você trabalha?",
|
| 83 |
-
"Você gosta do seu trabalho?",
|
| 84 |
-
"Quanto você ganha?",
|
| 85 |
-
"Você tem chefe?",
|
| 86 |
-
"Trabalha em equipe?",
|
| 87 |
-
"Que horas você trabalha?",
|
| 88 |
-
"Você trabalha aos sábados?",
|
| 89 |
-
"Está de férias?"
|
| 90 |
-
],
|
| 91 |
-
"Rotina": [
|
| 92 |
-
"Que horas você acorda?",
|
| 93 |
-
"O que você come no café?",
|
| 94 |
-
"A que horas almoça?",
|
| 95 |
-
"Você dorme cedo?",
|
| 96 |
-
"Toma banho de manhã?",
|
| 97 |
-
"Como vai ao trabalho?",
|
| 98 |
-
"Você janta em casa?",
|
| 99 |
-
"Assiste TV à noite?",
|
| 100 |
-
"Faz exercícios?",
|
| 101 |
-
"Você estuda?"
|
| 102 |
-
],
|
| 103 |
-
"Preferências": [
|
| 104 |
-
"O que você gosta de comer?",
|
| 105 |
-
"Qual sua cor favorita?",
|
| 106 |
-
"Você gosta de música?",
|
| 107 |
-
"Prefere frio ou calor?",
|
| 108 |
-
"Gosta de praia?",
|
| 109 |
-
"Você bebe café?",
|
| 110 |
-
"Gosta de doce?",
|
| 111 |
-
"Prefere cão ou gato?",
|
| 112 |
-
"Você fuma?",
|
| 113 |
-
"Gosta de viajar?"
|
| 114 |
-
],
|
| 115 |
-
"Habilidades": [
|
| 116 |
-
"Você fala inglês?",
|
| 117 |
-
"Sabe cozinhar?",
|
| 118 |
-
"Você dirige?",
|
| 119 |
-
"Toca algum instrumento?",
|
| 120 |
-
"Sabe nadar?",
|
| 121 |
-
"Você dança?",
|
| 122 |
-
"Sabe cantar?",
|
| 123 |
-
"Fala outras línguas?",
|
| 124 |
-
"Você desenha?",
|
| 125 |
-
"Sabe programar?"
|
| 126 |
-
],
|
| 127 |
-
"Situações": [
|
| 128 |
-
"Você está ocupado?",
|
| 129 |
-
"Pode me ajudar?",
|
| 130 |
-
"Você está bem?",
|
| 131 |
-
"Está com fome?",
|
| 132 |
-
"Tem tempo agora?",
|
| 133 |
-
"Você está cansado?",
|
| 134 |
-
"Está feliz hoje?",
|
| 135 |
-
"Você está doente?",
|
| 136 |
-
"Precisa de algo?",
|
| 137 |
-
"Está com pressa?"
|
| 138 |
-
],
|
| 139 |
-
"Perguntas Gerais": [
|
| 140 |
-
"Que dia é hoje?",
|
| 141 |
-
"Que horas são?",
|
| 142 |
-
"Como está o tempo?",
|
| 143 |
-
"Vai chover?",
|
| 144 |
-
"Está frio?",
|
| 145 |
-
"Onde fica o banco?",
|
| 146 |
-
"Tem farmácia perto?",
|
| 147 |
-
"Quanto custa?",
|
| 148 |
-
"Aceita cartão?",
|
| 149 |
-
"Tem troco?"
|
| 150 |
-
]
|
| 151 |
-
}
|
| 152 |
-
|
| 153 |
-
def evaluate_coherence(question, response):
|
| 154 |
-
"""
|
| 155 |
-
Avalia se a resposta é coerente com a pergunta
|
| 156 |
-
Versão balanceada que obteve 85% de sucesso
|
| 157 |
-
"""
|
| 158 |
-
|
| 159 |
-
# Verificações básicas de qualidade
|
| 160 |
-
if not response or len(response.strip()) < 3:
|
| 161 |
-
return False, "Resposta vazia ou muito curta"
|
| 162 |
-
|
| 163 |
-
# Não deve conter vazamentos
|
| 164 |
-
if any(x in response for x in ["Usuário:", "Assistente:", "user said", "```"]):
|
| 165 |
-
return False, "Contém vazamento de conversa"
|
| 166 |
-
|
| 167 |
-
# Deve ter pontuação final
|
| 168 |
-
if not response.rstrip().endswith(('.', '!', '?')):
|
| 169 |
-
return False, "Sem pontuação final"
|
| 170 |
-
|
| 171 |
-
# Análise contextual por tipo de pergunta
|
| 172 |
-
q_lower = question.lower()
|
| 173 |
-
r_lower = response.lower()
|
| 174 |
-
|
| 175 |
-
# Cumprimentos
|
| 176 |
-
if any(x in q_lower for x in ["olá", "oi", "bom dia", "boa tarde", "boa noite", "tchau", "até"]):
|
| 177 |
-
if any(x in r_lower for x in ["olá", "oi", "bom", "boa", "bem", "tchau", "até", "prazer", "também"]):
|
| 178 |
-
return True, "Cumprimento apropriado"
|
| 179 |
-
return False, "Não respondeu ao cumprimento"
|
| 180 |
-
|
| 181 |
-
# Perguntas sobre identidade
|
| 182 |
-
if "nome" in q_lower or "quem" in q_lower:
|
| 183 |
-
if len(response) > 10: # Resposta elaborada
|
| 184 |
-
return True, "Identificação apropriada"
|
| 185 |
-
return False, "Resposta inadequada sobre identidade"
|
| 186 |
-
|
| 187 |
-
# Perguntas sim/não
|
| 188 |
-
if q_lower.startswith(("você", "tem", "está", "pode", "sabe", "gosta", "prefere", "fala")):
|
| 189 |
-
if len(response.split()) >= 3: # Não apenas "sim" ou "não"
|
| 190 |
-
return True, "Resposta elaborada"
|
| 191 |
-
return False, "Resposta muito simples para pergunta sim/não"
|
| 192 |
-
|
| 193 |
-
# Perguntas sobre localização
|
| 194 |
-
if any(x in q_lower for x in ["onde", "qual endereço", "que cidade"]):
|
| 195 |
-
if len(response) > 10:
|
| 196 |
-
return True, "Informação de localização"
|
| 197 |
-
return False, "Resposta inadequada sobre local"
|
| 198 |
-
|
| 199 |
-
# Perguntas sobre tempo
|
| 200 |
-
if any(x in q_lower for x in ["que horas", "que dia", "quando"]):
|
| 201 |
-
if len(response) > 8:
|
| 202 |
-
return True, "Informação temporal"
|
| 203 |
-
return False, "Resposta inadequada sobre tempo"
|
| 204 |
-
|
| 205 |
-
# Perguntas sobre quantidade
|
| 206 |
-
if q_lower.startswith("quantos") or "quanto" in q_lower:
|
| 207 |
-
if any(char.isdigit() for char in response) or any(x in r_lower for x in ["não tenho", "nenhum", "vários", "alguns", "muitos"]):
|
| 208 |
-
return True, "Resposta quantitativa"
|
| 209 |
-
return False, "Não respondeu quantidade"
|
| 210 |
-
|
| 211 |
-
# Para outras perguntas, verifica se tem conteúdo substancial
|
| 212 |
-
word_count = len(response.split())
|
| 213 |
-
if word_count >= 4:
|
| 214 |
-
return True, "Resposta com conteúdo adequado"
|
| 215 |
-
|
| 216 |
-
return False, "Resposta inadequada ou muito curta"
|
| 217 |
-
|
| 218 |
-
try:
|
| 219 |
-
from vllm import LLM, SamplingParams
|
| 220 |
-
|
| 221 |
-
print("\n⏳ Carregando modelo com vLLM...")
|
| 222 |
-
start_load = time.time()
|
| 223 |
-
|
| 224 |
-
model = LLM(
|
| 225 |
-
model="/tmp/Qwen3-4B",
|
| 226 |
-
trust_remote_code=True,
|
| 227 |
-
dtype="float16",
|
| 228 |
-
gpu_memory_utilization=0.90,
|
| 229 |
-
max_model_len=1024,
|
| 230 |
-
disable_log_stats=True
|
| 231 |
-
)
|
| 232 |
-
|
| 233 |
-
print(f"✅ Modelo carregado em {time.time()-start_load:.1f}s")
|
| 234 |
-
|
| 235 |
-
# Configuração V1 - a que obteve melhores resultados
|
| 236 |
-
print("\n🔧 Configuração V1 (Melhor Performance):")
|
| 237 |
-
print(" • min_tokens: 7")
|
| 238 |
-
print(" • max_tokens: 20")
|
| 239 |
-
print(" • temperature: 0.0")
|
| 240 |
-
print(" • repetition_penalty: 1.2")
|
| 241 |
-
print(" • stop sequences: ['.', '!', '?', '\\n', 'Usuário:']")
|
| 242 |
-
|
| 243 |
-
sampling_params = SamplingParams(
|
| 244 |
-
min_tokens=7,
|
| 245 |
-
max_tokens=20,
|
| 246 |
-
temperature=0.0,
|
| 247 |
-
stop=[".", "!", "?", "\n", "Usuário:"],
|
| 248 |
-
include_stop_str_in_output=True,
|
| 249 |
-
repetition_penalty=1.2
|
| 250 |
-
)
|
| 251 |
-
|
| 252 |
-
# Warm-up
|
| 253 |
-
print("\n🔥 Aquecimento...")
|
| 254 |
-
for i in range(3):
|
| 255 |
-
_ = model.generate(["teste"], SamplingParams(max_tokens=5, temperature=0))
|
| 256 |
-
|
| 257 |
-
# Teste das 100 perguntas
|
| 258 |
-
print("\n📊 Testando 100 perguntas...")
|
| 259 |
-
print("-" * 70)
|
| 260 |
-
|
| 261 |
-
all_results = []
|
| 262 |
-
category_results = {}
|
| 263 |
-
total_coherent = 0
|
| 264 |
-
total_latency = 0
|
| 265 |
-
|
| 266 |
-
question_number = 0
|
| 267 |
-
for category, questions in QUESTIONS.items():
|
| 268 |
-
print(f"\n📚 {category}:")
|
| 269 |
-
cat_coherent = 0
|
| 270 |
-
cat_latency = 0
|
| 271 |
-
|
| 272 |
-
for question in questions:
|
| 273 |
-
question_number += 1
|
| 274 |
-
|
| 275 |
-
# Gera resposta
|
| 276 |
-
prompt = f"{SYSTEM_PROMPT}\n\nUsuário: {question}\nAssistente:"
|
| 277 |
-
|
| 278 |
-
start = time.time()
|
| 279 |
-
outputs = model.generate([prompt], sampling_params)
|
| 280 |
-
latency = (time.time() - start) * 1000
|
| 281 |
-
|
| 282 |
-
response = outputs[0].outputs[0].text.strip() if outputs else ""
|
| 283 |
-
|
| 284 |
-
# Avalia coerência
|
| 285 |
-
is_coherent, reason = evaluate_coherence(question, response)
|
| 286 |
-
|
| 287 |
-
# Armazena resultado
|
| 288 |
-
result = {
|
| 289 |
-
"question": question,
|
| 290 |
-
"response": response,
|
| 291 |
-
"coherent": is_coherent,
|
| 292 |
-
"reason": reason,
|
| 293 |
-
"latency": latency
|
| 294 |
-
}
|
| 295 |
-
all_results.append(result)
|
| 296 |
-
|
| 297 |
-
if is_coherent:
|
| 298 |
-
total_coherent += 1
|
| 299 |
-
cat_coherent += 1
|
| 300 |
-
symbol = "✅"
|
| 301 |
-
else:
|
| 302 |
-
symbol = "❌"
|
| 303 |
-
|
| 304 |
-
total_latency += latency
|
| 305 |
-
cat_latency += latency
|
| 306 |
-
|
| 307 |
-
# Mostra progresso
|
| 308 |
-
print(f" [{question_number:3d}/100] {symbol} {latency:4.0f}ms | {question[:30]:30s}")
|
| 309 |
-
|
| 310 |
-
# Estatísticas da categoria
|
| 311 |
-
category_results[category] = {
|
| 312 |
-
"coherent": cat_coherent,
|
| 313 |
-
"total": len(questions),
|
| 314 |
-
"accuracy": (cat_coherent / len(questions)) * 100,
|
| 315 |
-
"avg_latency": cat_latency / len(questions)
|
| 316 |
-
}
|
| 317 |
-
print(f" → Categoria: {cat_coherent}/{len(questions)} ({cat_coherent*10}%) - {cat_latency/len(questions):.0f}ms média")
|
| 318 |
-
|
| 319 |
-
# Resultados finais
|
| 320 |
-
print("\n" + "=" * 70)
|
| 321 |
-
print("📊 RESULTADOS FINAIS - CONFIGURAÇÃO V1")
|
| 322 |
-
print("=" * 70)
|
| 323 |
-
|
| 324 |
-
accuracy = (total_coherent / 100) * 100
|
| 325 |
-
avg_latency = total_latency / 100
|
| 326 |
-
|
| 327 |
-
print(f"\n✅ COERÊNCIA GERAL: {total_coherent}/100 ({accuracy:.1f}%)")
|
| 328 |
-
print(f"⏱️ LATÊNCIA MÉDIA: {avg_latency:.0f}ms")
|
| 329 |
-
|
| 330 |
-
print("\n📈 POR CATEGORIA:")
|
| 331 |
-
for cat, stats in category_results.items():
|
| 332 |
-
bar = "█" * int(stats['accuracy'] / 10)
|
| 333 |
-
print(f" {cat:20s}: {bar:10s} {stats['coherent']:2d}/10 ({stats['accuracy']:.0f}%)")
|
| 334 |
-
|
| 335 |
-
# Análise de problemas
|
| 336 |
-
incoherent = [r for r in all_results if not r["coherent"]]
|
| 337 |
-
if incoherent:
|
| 338 |
-
print(f"\n❌ RESPOSTAS INCOERENTES ({len(incoherent)}):")
|
| 339 |
-
reasons = {}
|
| 340 |
-
for r in incoherent:
|
| 341 |
-
reasons[r["reason"]] = reasons.get(r["reason"], 0) + 1
|
| 342 |
-
|
| 343 |
-
for reason, count in sorted(reasons.items(), key=lambda x: x[1], reverse=True):
|
| 344 |
-
print(f" • {reason}: {count} casos")
|
| 345 |
-
|
| 346 |
-
# Exemplos
|
| 347 |
-
print("\n📝 EXEMPLOS DE RESPOSTAS:")
|
| 348 |
-
|
| 349 |
-
# Mostra 5 boas e 5 ruins
|
| 350 |
-
coherent_examples = [r for r in all_results if r["coherent"]][:5]
|
| 351 |
-
incoherent_examples = [r for r in all_results if not r["coherent"]][:5]
|
| 352 |
-
|
| 353 |
-
if coherent_examples:
|
| 354 |
-
print("\n✅ RESPOSTAS COERENTES:")
|
| 355 |
-
for r in coherent_examples:
|
| 356 |
-
print(f" P: {r['question']}")
|
| 357 |
-
print(f" R: {r['response']}")
|
| 358 |
-
print()
|
| 359 |
-
|
| 360 |
-
if incoherent_examples:
|
| 361 |
-
print("\n❌ RESPOSTAS INCOERENTES:")
|
| 362 |
-
for r in incoherent_examples:
|
| 363 |
-
print(f" P: {r['question']}")
|
| 364 |
-
print(f" R: {r['response']}")
|
| 365 |
-
print(f" Problema: {r['reason']}")
|
| 366 |
-
print()
|
| 367 |
-
|
| 368 |
-
# Conclusão
|
| 369 |
-
print("=" * 70)
|
| 370 |
-
print("🎯 CONCLUSÃO:")
|
| 371 |
-
|
| 372 |
-
if accuracy >= 85:
|
| 373 |
-
print(f" ✅ EXCELENTE! {accuracy:.0f}% de coerência")
|
| 374 |
-
elif accuracy >= 80:
|
| 375 |
-
print(f" ✅ BOM! {accuracy:.0f}% de coerência")
|
| 376 |
-
elif accuracy >= 70:
|
| 377 |
-
print(f" ⚠️ ACEITÁVEL. {accuracy:.0f}% de coerência")
|
| 378 |
-
else:
|
| 379 |
-
print(f" ❌ PRECISA MELHORAR. Apenas {accuracy:.0f}% de coerência")
|
| 380 |
-
|
| 381 |
-
if avg_latency < 400:
|
| 382 |
-
print(f" ✅ Latência ÓTIMA: {avg_latency:.0f}ms")
|
| 383 |
-
elif avg_latency < 600:
|
| 384 |
-
print(f" ✅ Latência BOA: {avg_latency:.0f}ms")
|
| 385 |
-
else:
|
| 386 |
-
print(f" ⚠️ Latência ALTA: {avg_latency:.0f}ms")
|
| 387 |
-
|
| 388 |
-
print("\n📊 HISTÓRICO DE TESTES:")
|
| 389 |
-
print(" V1 (original): 85% coerência, 376ms latência")
|
| 390 |
-
print(" V2 (restritivo): 84% coerência, 378ms latência")
|
| 391 |
-
print(" V3 (flexível): 72% coerência, 427ms latência")
|
| 392 |
-
print(f" V1 (atual): {accuracy:.0f}% coerência, {avg_latency:.0f}ms latência")
|
| 393 |
-
|
| 394 |
-
# Salvar resultados
|
| 395 |
-
with open("/tmp/test_100_final_results.json", "w") as f:
|
| 396 |
-
json.dump({
|
| 397 |
-
"version": "V1_FINAL",
|
| 398 |
-
"accuracy": accuracy,
|
| 399 |
-
"avg_latency": avg_latency,
|
| 400 |
-
"total_coherent": total_coherent,
|
| 401 |
-
"categories": category_results,
|
| 402 |
-
"all_results": all_results
|
| 403 |
-
}, f, indent=2, ensure_ascii=False)
|
| 404 |
-
|
| 405 |
-
print(f"\n💾 Resultados salvos em /tmp/test_100_final_results.json")
|
| 406 |
-
|
| 407 |
-
except Exception as e:
|
| 408 |
-
print(f"❌ Erro: {e}")
|
| 409 |
-
import traceback
|
| 410 |
-
traceback.print_exc()
|
| 411 |
-
sys.exit(1)
|
| 412 |
-
|
| 413 |
-
print("\n" + "=" * 70)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|