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
Files changed (50) hide show
  1. .claude/commands/benchmark.md +61 -0
  2. .claude/commands/debug-pipeline.md +92 -0
  3. .claude/commands/optimize-model.md +48 -0
  4. .claude/commands/start-webrtc.md +26 -0
  5. .claude/commands/test-latency.md +21 -0
  6. ANALISE_MODIFICACOES.md +0 -128
  7. CLAUDE.md +0 -322
  8. COMMUNICATION_LATENCY_ANALYSIS_REPORT.md +0 -302
  9. DUAL_MODEL_USAGE.md +0 -83
  10. INSTALLATION_GUIDE.md +0 -133
  11. PACKAGES_INSTALLED.md +0 -74
  12. PLANO_INTEGRACAO_REALTIMETTS.md +0 -194
  13. PLANO_TROCA_LLM_TTS_EXTERNO.md +0 -261
  14. README.md +0 -394
  15. README_INSTALLATION.md +0 -184
  16. RELATORIO_VIABILIDADE_TROCA_QWEN_MULTILINGUE.md +0 -302
  17. SPEECH_PROJECTOR_ANALYSIS.md +0 -110
  18. analyze_generated_audio.py +0 -227
  19. benchmark_20q_gpu_final.py +0 -312
  20. coherence_test_results.json +0 -82
  21. communication_analysis_report.py +0 -388
  22. communication_latency_test.py +0 -370
  23. create_real_speech.py +0 -100
  24. create_test_audio.py +0 -94
  25. docs/A1_VOCABULARY_CONTROL_TECHNIQUES.md +0 -587
  26. download_llama_omni2.py +0 -25
  27. download_official_model.py +0 -13
  28. generate_test_audios.py +0 -81
  29. gtts_test_results.json +0 -82
  30. installed_packages.txt +0 -246
  31. llama_omni2_integration/__init__.py +0 -1
  32. llama_omni2_integration/constants.py +0 -9
  33. llama_omni2_integration/omni2_speech_arch.py +0 -201
  34. llama_omni2_integration/qwen2_speech_model.py +0 -155
  35. llama_omni2_integration/qwen2_speech_model_fixed.py +0 -294
  36. llama_omni2_integration/speech_encoder/__init__.py +0 -1
  37. llama_omni2_integration/speech_encoder/builder.py +0 -9
  38. llama_omni2_integration/speech_encoder/speech_encoder.py +0 -26
  39. llama_omni2_integration/speech_projector/__init__.py +0 -1
  40. llama_omni2_integration/speech_projector/builder.py +0 -9
  41. llama_omni2_integration/speech_projector/speech_projector.py +0 -30
  42. load_speech_projector.py +0 -184
  43. webrtc_server_gpu_vllm.py → server.py +0 -0
  44. simple_speech_chat_torchcompiled.py +0 -230
  45. start.sh +51 -246
  46. stop.sh +0 -76
  47. streaming_latency_test.py +0 -262
  48. system_prompt_v2.md +0 -94
  49. test_100_questions_final.py +0 -401
  50. 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
- [![arXiv](https://img.shields.io/badge/arXiv-2505.02625-b31b1b.svg?logo=arXiv)](https://arxiv.org/abs/2505.02625)
6
- [![code](https://img.shields.io/badge/Github-Code-keygen.svg?logo=github)](https://github.com/ictnlp/LLaMA-Omni2)
7
- [![models](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging_Face-Models-blue.svg)](https://huggingface.co/collections/ICTNLP/llama-omni-67fdfb852c60470175e36e9c)
8
- [![dataset](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging_Face-Dataset-blue.svg)](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 do Servidor - LLaMA-Omni2
4
- # ================================================
5
- # Inicia o sistema de conversação por voz
6
  #
7
 
8
  set -e
9
 
10
- echo "🚀 INICIALIZANDO SERVIDOR LLAMA-OMNI2"
11
- echo "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "="
 
 
 
 
12
 
13
- # Verificar se estamos no diretório correto (removido check de simple_speech_chat.py)
14
- if [ ! -d "llama_omni2" ]; then
15
- echo "❌ Erro: Execute este script no diretório raiz do projeto"
 
16
  exit 1
17
  fi
18
 
19
- # Função para cleanup
20
- cleanup() {
21
- echo ""
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 "🖥️ Verificando GPU..."
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, usando CPU')
65
  "
66
 
67
- # Selecionar modo de operação
68
- echo ""
69
- echo "🎯 MODOS DISPONÍVEIS:"
70
- echo "1) Servidor WebRTC Unificado (Otimizado - Baixa Latência)"
71
- echo "2) Servidor Oficial (3 componentes)"
72
- echo "3) Testes de Áudio"
73
- echo ""
74
-
75
- read -p "Escolha o modo (1-3) [1]: " MODE
76
- MODE=${MODE:-1}
77
 
78
- case $MODE in
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
- os.makedirs('models', exist_ok=True)
 
 
 
 
 
 
 
 
 
104
 
105
- try:
106
- snapshot_download(
107
- 'Qwen/Qwen2.5-1.5B-Instruct',
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 "🎉 EXECUÇÃO FINALIZADA"
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)