Spaces:
Running on Zero
Running on Zero
Download app.py from ConectaPrimoAI/conecta-primo-gpu: direct link, hf CLI and curl.
- Browser
- Download file 5.4 kB
-
https://huggingface.co/spaces/ConectaPrimoAI/conecta-primo-gpu/resolve/main/app.py
- Command line
-
hf download hf://spaces/ConectaPrimoAI/conecta-primo-gpu/app.py
-
curl -L -o app.py https://huggingface.co/spaces/ConectaPrimoAI/conecta-primo-gpu/resolve/main/app.py
5.4 kB
| # -*- coding: utf-8 -*- | |
| """ | |
| A CASCA GRADIO DO ADAPTER ZEROGPU — o que o Hugging Face executa. | |
| Este arquivo é fino de propósito: toda a lógica mora em `nucleo.py` (testado | |
| no repositório, sem Gradio). Aqui só se faz o que EXIGE o ambiente do Space: | |
| - decorar a geração com `@spaces.GPU(duration=...)` — é o decorator que | |
| faz o ZeroGPU alocar a placa durante a chamada, e a duração é POR TIPO | |
| (IMAGE_GPU_SECONDS etc., lidas no nucleo); | |
| - expor as funções como API do Gradio (`api_name=...`), que é o único | |
| transporte que um Space ZeroGPU oferece: POST → event_id → SSE. Os | |
| yields do gerador viram eventos `generating` (o progresso do app); | |
| - manter a PÁGINA do Space inofensiva: quem abrir a URL num navegador vê | |
| um aviso, não um formulário que gasta quota — gerar exige o segredo, e a | |
| conferência roda ANTES do decorator de GPU ser invocado. | |
| Fora do Space (sem `spaces` instalado) o decorator vira identidade e o | |
| WORKER_MOCK=1 permite subir a casca inteira num notebook para teste. | |
| """ | |
| from __future__ import annotations | |
| import gradio as gr | |
| import nucleo | |
| try: | |
| import spaces | |
| _GPU = spaces.GPU | |
| except ImportError: # fora do Hugging Face: sem decorator | |
| def _GPU(*a, **k): | |
| def envolve(fn): | |
| return fn | |
| return envolve | |
| def _fazer_endpoint(tipo: str): | |
| """Uma função por tipo, porque a DURAÇÃO da GPU é do decorator e vídeo | |
| não pode viver com o prazo de imagem (abortaria no meio, com a quota já | |
| gasta).""" | |
| def _na_gpu(params_json: str): | |
| yield from nucleo.gerar(tipo, params_json) | |
| def endpoint(segredo: str, params_json: str): | |
| # A porta fica AQUI FORA: recusar antes de `_na_gpu` significa que a | |
| # chamada sem segredo não aloca GPU e não consome quota de ninguém. | |
| recusa = nucleo.autenticar(segredo) | |
| if recusa: | |
| yield {"erro": recusa} | |
| return | |
| # O DOWNLOAD dos pesos roda AQUI FORA da GPU: é rede, não CUDA, e | |
| # dentro do @spaces.GPU ele queimava o prazo — a duração declarada só | |
| # precisa cobrir carregar-do-disco + inferência. | |
| yield {"etapa": "Baixando o modelo…", "progress": None} | |
| falha = nucleo.preparar(tipo) | |
| if falha: | |
| # erro_gpu, e não erro: download que falhou (repo errado, rede, | |
| # Hub fora) é ESTE runtime não conseguindo atender agora — o | |
| # backend degrada para o gerador local em vez de devolver a | |
| # falha à pessoa. Medido: o 404 de um id de modelo errado saía | |
| # como erro do pedido e a música morria sem o fallback. | |
| yield {"erro_gpu": falha} | |
| return | |
| # O try daqui não é redundância do try do nucleo: a ALOCAÇÃO do | |
| # @spaces.GPU acontece ANTES de o corpo rodar, e é ali que nascem os | |
| # erros de quota e de duração do ZeroGPU. Sem esta cerca eles | |
| # estouravam dentro do Gradio, que os engole (show_error desligado) — | |
| # o backend recebia "o Space abortou a chamada: None", sem motivo | |
| # nenhum para agir. Medido na primeira geração real. | |
| try: | |
| yield from _na_gpu(params_json) | |
| except BaseException as e: | |
| if isinstance(e, (KeyboardInterrupt, SystemExit)): | |
| raise | |
| if nucleo.e_erro_de_gpu(e): | |
| yield {"erro_gpu": f"ZeroGPU indisponível agora: {str(e)[:200]}"} | |
| else: | |
| yield {"erro": f"{type(e).__name__}: {str(e)[:300]}"} | |
| return endpoint | |
| def _saude(segredo: str): | |
| recusa = nucleo.autenticar(segredo) | |
| if recusa: | |
| return {"erro": recusa} | |
| return nucleo.saude() | |
| with gr.Blocks(title="Conecta Primo — GPU") as demo: | |
| # A página humana diz o mínimo. Nada aqui dispara geração: os botões nem | |
| # existem — só a API, e ela pede o segredo. | |
| gr.Markdown("## Conecta Primo — worker de GPU\n" | |
| "Este Space atende **somente** o backend do aplicativo, " | |
| "autenticado por segredo. Não há nada para usar nesta página.") | |
| segredo_in = gr.Textbox(visible=False) | |
| params_in = gr.Textbox(visible=False) | |
| saida = gr.JSON(visible=False) | |
| # gr.api registra função como endpoint puro (Gradio >= 4.44). Os botões | |
| # invisíveis são a forma equivalente que funciona em toda versão 4/5 — | |
| # ficam os dois? Não: DOIS caminhos para a mesma função é o defeito de | |
| # sempre. Fica o dos componentes, que é o estável. | |
| for _tipo, _nome in (("image", "gerar_imagem"), ("voice", "gerar_fala"), | |
| ("music", "gerar_musica"), ("video", "gerar_video")): | |
| _btn = gr.Button(visible=False) | |
| _btn.click(_fazer_endpoint(_tipo), inputs=[segredo_in, params_in], | |
| outputs=[saida], api_name=_nome) | |
| _btn_saude = gr.Button(visible=False) | |
| _btn_saude.click(_saude, inputs=[segredo_in], outputs=[saida], | |
| api_name="saude") | |
| if __name__ == "__main__": | |
| # Fila do Gradio LIGADA com concorrência 1: no ZeroGPU cada chamada é uma | |
| # alocação de GPU, e duas em paralelo é OOM ou quota dobrada — a mesma | |
| # decisão do MAX_CONCURRENT_JOBS=1 do worker CUDA, no dialeto do Gradio. | |
| demo.queue(default_concurrency_limit=1, max_size=16) | |
| demo.launch(show_api=False) | |