Spaces:
Build error
Build error
File size: 7,743 Bytes
8b16063 d7cf2e1 8b16063 b51f255 8b16063 5fb7e04 647f24c 989af85 5fb7e04 f66e7a8 68e1f2f d7cf2e1 6b73089 8b16063 d7cf2e1 5fb7e04 8b16063 d7cf2e1 5fb7e04 d501229 8b16063 647f24c 8b16063 d7cf2e1 68e1f2f 3a19604 989af85 3a19604 647f24c d7cf2e1 647f24c d7cf2e1 647f24c d7cf2e1 647f24c d7cf2e1 6105e2a 647f24c d7cf2e1 68e1f2f d7cf2e1 5107215 d7cf2e1 d501229 647f24c 5fb7e04 91c107d d7cf2e1 d501229 d7cf2e1 d501229 647f24c 91c107d 647f24c 8b16063 d501229 5fb7e04 647f24c 5fb7e04 647f24c 5fb7e04 8b16063 647f24c 5fb7e04 d7cf2e1 5fb7e04 647f24c 8b16063 647f24c 99671cb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | # aduc_framework/managers/llama_scout_manager.py
#
# Versão 21.0.0 (Literal Function Replica)
# Implementa a função `generate_text_from_image` exatamente como no script
# da Meta para garantir 100% de fidelidade.
import yaml
import os
import logging
import torch
from PIL import Image
from typing import List, Callable
# --- IMPORTAÇÕES EXATAS DO SCRIPT DA META ---
from transformers import MllamaForConditionalGeneration, MllamaProcessor
from transformers import AutoTokenizer, AutoModelForCausalLM
from ..tools.hardware_manager import hardware_manager
logger = logging.getLogger(__name__)
# --- A FUNÇÃO ORIGINAL DA META, AGORA DENTRO DO NOSSO ARQUIVO ---
def generate_text_from_image(
model, processor, image, prompt_text: str,
temperature: float = 0.6, top_p: float = 0.9, max_new_tokens=2048
):
"""Generate text from image using model"""
# 1. Garante que a imagem está no formato correto
processed_image = image.convert("RGB")
# 2. Constrói a estrutura da conversa
conversation = [
{"role": "user", "content": [{"type": "image"}, {"type": "text", "text": prompt_text}]},
]
# 3. Usa o `apply_chat_template` para criar o prompt
prompt = processor.apply_chat_template(
conversation, add_generation_prompt=True, tokenize=False
)
# 4. O processador combina a imagem e o texto
inputs = processor(
processed_image, prompt,
text_kwargs={"add_special_tokens": False},
return_tensors="pt"
).to(model.device)
# 5. O modelo gera a resposta
output = model.generate(
**inputs,
temperature=temperature,
top_p=top_p,
max_new_tokens=max_new_tokens,
do_sample=True # Necessário para usar temperature e top_p
)
# 6. Decodifica e limpa a resposta
full_response = processor.decode(output[0])
clean_response = full_response[len(prompt):]
if clean_response.endswith("<|eot_id|>"):
clean_response = clean_response[:-len("<|eot_id|>")].strip()
return clean_response.strip()
# -----------------------------------------------------------------
class LlamaScoutManager:
"""
Uma casca fina que carrega os modelos e chama a função de inferência
oficial da Meta.
"""
def __init__(self, config: dict):
self.hf_token = os.getenv("HF_TOKEN")
if not self.hf_token: raise ValueError("HF_TOKEN é necessário.")
multimodal_id = config['multimodal_model_id']
helper_id = config['helper_model_id']
logger.info(f"LLAMA SCOUT (Literal Replica): Carregando Cinegrafista: {multimodal_id}...")
self.multimodal_processor = MllamaProcessor.from_pretrained(multimodal_id, token=self.hf_token)
self.multimodal_model = MllamaForConditionalGeneration.from_pretrained(
multimodal_id,
torch_dtype=torch.bfloat16,
use_safetensors=True,
device_map="auto",
token=self.hf_token
)
logger.info("LLAMA SCOUT (Literal Replica): Cinegrafista (Llama 3.2 Vision) carregado.")
logger.info(f"LLAMA SCOUT (Literal Replica): Carregando Diretor: {helper_id}...")
self.helper_tokenizer = AutoTokenizer.from_pretrained(helper_id, token=self.hf_token)
self.helper_model = AutoModelForCausalLM.from_pretrained(
helper_id,
torch_dtype=torch.bfloat16,
device_map="auto",
token=self.hf_token,
attn_implementation="flash_attention_2"
)
logger.info("LLAMA SCOUT (Literal Replica): Diretor (Llama 3.1 8B) carregado.")
@torch.inference_mode()
def analyze_sequence(self, image_list: List[Image.Image], question: str, progress_callback: Callable = None) -> str:
if not image_list: return "Nenhuma imagem fornecida."
# A lógica agora é simples: chamar a função replicada.
# Nós ainda usamos nossa lógica de "chunking" se necessário.
if len(image_list) == 1:
if progress_callback: progress_callback(0.2, f"Analisando 1 imagem com a função oficial...")
return generate_text_from_image(self.multimodal_model, self.multimodal_processor, image_list[0], question)
# Para múltiplas imagens, ainda precisamos da nossa lógica de orquestração.
else:
if progress_callback: progress_callback(0.1, f"Múltiplas imagens detectadas. Analisando em chunks...")
# A API do modelo funciona melhor com uma imagem, então vamos analisar uma por uma
# e depois pedir ao nosso Diretor para resumir.
partial_analyses = []
for i, image in enumerate(image_list):
progress = 0.1 + (i / len(image_list)) * 0.8
if progress_callback: progress_callback(progress, f"Analisando imagem {i+1}/{len(image_list)}...")
# Criamos uma pergunta específica para cada imagem
chunk_question = f"Esta é a imagem {i+1} de uma sequência. {question}"
analysis = generate_text_from_image(self.multimodal_model, self.multimodal_processor, image, chunk_question)
partial_analyses.append(analysis)
return self._summarize_with_helper(partial_analyses, question, progress_callback)
@torch.inference_mode()
def _summarize_with_helper(self, partial_texts: List[str], original_question: str, progress_callback: Callable) -> str:
if progress_callback: progress_callback(0.9, "Síntese com o Diretor 8B (Local)...")
combined_partials = "\n\n---\n\n".join(f"Análise da Imagem {i+1}:\n{text}" for i, text in enumerate(partial_texts))
prompt = (f"Você é um diretor de cinema. Sua visão é: '{original_question}'. "
f"Seu cinegrafista enviou os seguintes relatórios, um para cada imagem de uma sequência: {combined_partials}. "
"Sintetize esses relatórios em uma única resposta final, coesa e poderosa, "
"que atenda à sua visão original. Responda diretamente, sem mencionar os relatórios.")
messages = [{"role": "user", "content": prompt}]
input_ids = self.helper_tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_tensors="pt").to(self.helper_model.device)
outputs = self.helper_model.generate(input_ids, max_new_tokens=2048, do_sample=False)
response = self.helper_tokenizer.decode(outputs[0][len(input_ids[0]):], skip_special_tokens=True)
return response.strip()
# (Placeholder e instanciação singleton permanecem iguais)
class LlamaScoutPlaceholder:
def __init__(self, reason: str = "Motivo desconhecido"):
logger.error(f"LlamaScoutManager não inicializado. Razão: {reason}. Placeholder em uso.")
self.reason = reason
def analyze_sequence(self, *args, **kwargs):
return f"ERRO: Especialista Llama Scout indisponível. Razão: {self.reason}"
try:
with open("config.yaml", 'r') as f: config = yaml.safe_load(f)
llama_scout_config = config['specialists'].get('llama_scout')
if llama_scout_config and llama_scout_config.get('gpus_required', 0) > 0:
hardware_manager.allocate_gpus('LlamaScout', llama_scout_config['gpus_required'])
llama_scout_manager_singleton = LlamaScoutManager(config=llama_scout_config)
logger.info("Especialista de Análise (Original Recipe) pronto.")
else:
llama_scout_manager_singleton = LlamaScoutPlaceholder("Não habilitado na config.yaml")
except Exception as e:
logger.critical(f"Falha CRÍTICA ao inicializar o LlamaScoutManager (Local): {e}", exc_info=True)
llama_scout_manager_singleton = LlamaScoutPlaceholder(reason=str(e)) |