TuBrief
구독 채널
비디오
커뮤니티

Configurações de Engenharia para Salvar LLMs Locais que Morrem no Docker do Mac

TuBrief 편집팀
2026년 8월 14일
0
Computing/Software

원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.

Português한국어EnglishEspañol中文العربيةDeutschFrançaisहिन्दीРусскийBahasa Indonesia日本語

관련 영상

PewDiePie agora é engenheiro de software...6:29

PewDiePie agora é engenheiro de software...

Better Stack

커뮤니티의 다른 글

사내 시스템에 llm api 붙일 때 마주하는 현실적인 한계와 대응법

2026년 9월 13일

레거시 백엔드에 GPT-6 Astra 붙일 때 예산 승인과 보안 통과를 먼저 끝내는 법이 있습니다

2026년 9월 13일

에이전트끼리 대화하다 6천만 원 청구서가 나오는 이유

2026년 9월 13일

사내 RAG 벡터 검색에 Okta 권한 필터를 직접 거는 방법

2026년 9월 13일

브라우저 에이전트에게 내 구글 계정을 통째로 넘기면 안 되는 이유

2026년 9월 12일

Apple Won the AI Race

2026년 9월 12일

댓글 (0)

Log in to leave a comment

아직 작성된 글이 없습니다

© 2026 . All rights reserved.

TuBrief
구독 채널
비디오
커뮤니티
로그인

Configurações de Engenharia para Salvar LLMs Locais que Morrem no Docker do Mac

Ao baixar o código de um espaço de trabalho de IA de código aberto e executá-lo no Docker do Mac, você esbarra em um obstáculo logo no primeiro prompt. Embora você certamente esteja usando um chip da série M, apenas os ventiladores giram barulhentamente e uma velocidade desastrosa de pouco mais de 10 tokens por segundo é registrada. Isso acontece porque a camada de virtualização do Docker Desktop falha em passar a GPU Apple Silicon (Metal) para dentro do contêiner, fazendo com que o sistema recaia sobre o processamento bruto da CPU.

Se você rodar isso combinando um backend FastAPI e um banco de vetor local sem resolver esse gargalo, o contêiner vai cair devido a vazamentos de memória e esgotamento de conexões. Aqui estão quatro configurações que você precisa ajustar para transformar o código que você pegou apenas olhando o número de estrelas do GitHub em um nível de produção rodando no seu ambiente local.

Causa do Fallback de CPU e Rota de Contorno de Aceleração de Hardware

O Docker Desktop no macOS roda em cima de uma VM Linux leve. Não existe uma função para repassar diretamente a API da GPU Metal do host para dentro do contêiner do SO convidado Linux. O Ollama ou llama.cpp dentro do contêiner falham na alocação de VRAM e silenciosamente alternam para o processamento de CPU. É por isso que um modelo Llama 3 8B que atingia de 40 a 80 t/s de forma nativa despenca para níveis de 21,45 t/s na avaliação de prompts e 12,17 t/s na geração de tokens assim que entra no Docker.

`
[Mecanismo de Ocorrência de Fallback de CPU dentro do Contêiner de Virtualização]
+-----------------------------------------------------------------------+
| Docker Container (Linux Guest OS) |
| +---------------------+ |
| | Local LLM Engine | --(VRAM Allocation Req)--> [VirtGPU Missing] |
| +---------------------+ | |
| | v |
| +<--(Fallback to CPU Execution)--- [Silent slog.Debug] |
+-------------|---------------------------------------------------------+
v
[Apple Silicon Host CPU (ARM Neon/DotProd)] -> Ocorre queda de velocidade

`

A configuração padrão de memória compartilhada (shm) também é um problema. Com a cota padrão de 64MB, ocorre um Erro de Barramento (Bus Error) imediato durante cálculos de tensores em grande escala. Abra o arquivo ~/.docker/daemon.json para expandir a memória compartilhada e liberar os limites de recursos.

`json
{
"builder": {
"gc": {
"defaultKeepStorage": "20GB",
"enabled": true
}
},
"experimental": false,
"default-shm-size": "8g"
}

`

`yaml
version: '3.8'

services:
llm-inference-engine:
image: ollama/ollama:latest
container_name: local_llm_engine
shm_size: '16gb'
ipc: host
deploy:
resources:
limits:
cpus: '8.0'
memory: 24G
reservations:
cpus: '4.0'
memory: 12G
ports:
- "11434:11434"
volumes:
- ollama_storage:/root/.ollama

volumes:
ollama_storage:

`

Para extrair a aceleração de GPU de dentro do contêiner, você precisa migrar para o Podman, utilizando o monitor de máquina virtual libkrun e o driver krunkit. Trata-se de um método que encaminha as requisições de computação Vulkan de dentro do contêiner para a API Metal do macOS host (Virtio-GPU Venus). Isso eleva a velocidade de processamento para até 75% em comparação com o Metal nativo.

  1. Instale o krunkit e o Podman via Homebrew.
    brew tap slp/krunkit && brew install krunkit podman
  2. Inicie uma máquina de 8 núcleos e 32 GB de RAM baseada no provedor libkrun.
    export CONTAINERS_MACHINE_PROVIDER="libkrun" && podman machine init --cpus 8 --memory 32768 && podman machine start
  3. Verifique se o driver foi anexado dentro da VM.
    podman machine ssh "ls -la /dev/dri"

`dockerfile
FROM fedora:40

RUN dnf -y install dnf-plugins-core &&
dnf -y copr enable slp/mesa-krunkit fedora-40-aarch64 &&
dnf -y install mesa-vulkan-drivers vulkan-loader vulkan-tools &&
dnf -y downgrade mesa-vulkan-drivers.aarch64 --repo copr:copr.fedorainfracloud.org:slp:mesa-krunkit &&
dnf clean all

ENV GGML_VULKAN=1

`

Se você precisar usar apenas o cálculo de CPU devido a regulamentações de infraestrutura, utilize o formato de quantização Q4_0_4_4 ajustado para as instruções vetoriais ARMv8.4-A DotProduct e Neon. Isso protege a velocidade de avaliação de prompts em até 50,63 t/s, reduzindo o tempo de processamento para menos da metade em comparação com a execução padrão de CPU no Docker.

Método de Configuração de Runtime Avaliação de Prompt (t/s) Geração de Tokens (t/s) Dificuldade de Construção Características
Docker Desktop (CPU Padrão) ~21,45 ~12,17 Baixa Fallback de CPU devido a restrições de virtualização
Docker Container (ARM Q4_0_4_4) ~50,63 ~14,01 Média Utilização de instruções vetoriais ARM Neon
Podman + libkrun (Vulkan Venus) ~75% do Nativo ~75% do Nativo Alta Aceleração Virtio-GPU dentro do contêiner
Host Native Metal + Container 100% Nativo (40~80) 100% Nativo Média Estrutura de chamada direta ao motor do host

Rastreamento de Vazamento de Memória no FastAPI e Recuperação de Fluxos Interrompidos

Backends de código aberto frequentemente apresentam um gerenciamento de estado de tarefas assíncronas ou um tratamento do coletor de lixo (GC) do CPython bastante precários. À medida que as solicitações se acumulam, a memória incha até que o processo morre. Utilize o tracemalloc para identificar os pontos que consomem memória.

`python
import gc
import tracemalloc
tracemalloc.start()

def log_memory_snapshot():
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[Memory Debug] Top 5 Allocations:")
for stat in top_stats[:5]:
print(stat)

gc.collect()

`

É necessário purgar a memória à força na etapa de gerenciamento de processos. Defina um ciclo de vida para que o worker do Gunicorn devolva a memória automaticamente e reinicie a cada 1.000 requisições processadas.

`bash
gunicorn
--workers 4
--worker-class uvicorn.workers.UvicornWorker
--bind 0.0.0.0:8000
--max-requests 1000
--max-requests-jitter 100
--timeout 120
--keep-alive 5
--preload-app
app.main:app

`

Adicione --max-requests-jitter 100 para evitar que todos os 4 workers morram e revivam simultaneamente causando perda de solicitações, e encerre trabalhos sem resposta com um timeout de 120 segundos.

Se um gerador assíncrono continuar rodando em loop quando o usuário fechar o navegador no meio de uma resposta, os recursos computacionais serão desperdiçados. Verifique request.is_disconnected() para sair do loop imediatamente caso a conexão seja interrompida.

`python
import asyncio
import gc
import logging
from typing import AsyncGenerator
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import StreamingResponse

app = FastAPI(title="Production AI Workspace Backend")
logger = logging.getLogger("stream_logger")

async def robust_llm_token_stream(prompt: str, request: Request) -> AsyncGenerator[str, None]:
try:
for token_idx in range(2000):
if await request.is_disconnected():
logger.warning(f"[Stream Aborted] Client disconnected at step {token_idx}.")
break

        await asyncio.sleep(0.01)
        yield f"event: message\ndata: {{"id": {token_idx}, "text": "chunk_{token_idx} "}}\n\n"

except asyncio.CancelledError:
    logger.info("[Stream Cancelled] Task cancelled by ASGI server.")
    raise
except Exception as err:
    logger.error(f"[Stream Error] Error during streaming: {str(err)}")
    raise
finally:
    logger.info("[Stream Cleanup] Releasing context and triggering GC.")
    gc.collect()

@app.post("/api/v1/chat/stream")
async def chat_stream_endpoint(request: Request, body: dict):
prompt = body.get("prompt", "")
if not prompt:
raise HTTPException(status_code=400, detail="Prompt string is missing.")

return StreamingResponse(
    robust_llm_token_stream(prompt, request),
    media_type="text/event-stream",
    headers={
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "X-Accel-Buffering": "no"
    }
)

`

Fixação de Dependências e Pipeline de Build Offline

É comum que uma única atualização secundária no upstream quebre a construção do contêiner local. Previna adulterações de pacotes e conflitos de versão usando um requirements.txt com hashes SHA-256 embutidos.

`plaintext
fastapi==0.110.0 --hash=sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
uvicorn==0.28.0 --hash=sha256:2c6a81387e0037a34ca6a7f8087796030c79eb299f116515822ee483c6604e43
pydantic==2.6.4 --hash=sha256:d82e212f451f2d6c19f5a5e3a89369322e70e1781297587786411516279f64a5
sqlalchemy==2.0.28 --hash=sha256:a611116c2bb45f8f3077e6822ec3a37b384ff6b9a84d4dd88a0e8eb876b5cf12

`

Para evitar falhas no download do pip ao reconstruir o Docker em ambientes offline (como redes corporativas de segurança ou dentro de aviões), baixe previamente os binários Wheel em um diretório local.

`bash
pip wheel --wheel-dir=./wheels_repository -r requirements.txt

`

`dockerfile
FROM python:3.11-slim

WORKDIR /app

COPY ./wheels_repository /app/wheels_repository
COPY requirements.txt .

RUN pip install --no-cache-dir --no-index --find-links=/app/wheels_repository -r requirements.txt

COPY . .

CMD ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", "-c", "gunicorn.conf.py", "app.main:app"]

`

Dar um pull completo nas alterações do repositório Git do upstream fará com que suas configurações locais cuidadosamente tunadas sejam perdidas. Separe uma branch de otimização e traga apenas os commits necessários via cherry-pick.

`bash
git remote add upstream https://github.com/opensource-ai-workspace/workspace.git
git fetch upstream
git checkout -b feature/local-mac-optimization
git cherry-pick

`

Controle de Gargalo de Conexões de DB e Roteamento de Rede Isolada

Se múltiplos agentes dispararem busca por embeddings, consulta de memória e registro de conversas de uma só vez, o pool de conexões do PostgreSQL se esgotará rapidamente. Com base na fórmula de cálculo de conexões (Nextconn=extCPUcoresimes2+extnumberofspindlesN_{ ext{conn}} = ext{CPU cores} imes 2 + ext{number of spindles}Nextconn​=extCPUcoresimes2+extnumberofspindles) para um ambiente de disco único Apple Silicon de 8 núcleos, limite o pool do motor para baixo de 17.

`python
from sqlalchemy.ext.asyncio import create_async_engine

DATABASE_URL = "postgresql+asyncpg://postgres:password@localhost:6432/ai_workspace"

engine = create_async_engine(
DATABASE_URL,
pool_size=15,
max_overflow=5,
pool_timeout=10,
pool_recycle=300,
pool_pre_ping=True
)

`

O fenômeno em que agentes ficam segurando a sessão do DB enquanto aguardam os resultados da inferência da LLM é cortado com o uso do Transaction Pooling do PgBouncer.

`ini
[databases]
ai_workspace = host=postgres_db port=5432 dbname=ai_workspace auth_user=postgres

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = plain
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
idle_transaction_timeout = 60
max_client_conn = 1000
default_pool_size = 20
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3

`

`
+-------------------------------------------------------------------------------+
| Docker Internal Isolated Network (internal: true) |
| |
| +--------------------+ +--------------------+ +-----------------+ |
| | AI Workspace App | ---> | PgBouncer Proxy | ---> | PostgreSQL DB | |
| | (FastAPI Backend) | | (Port 6432) | | (Port 5432) | |
| +--------------------+ +--------------------+ +-----------------+ |
| | | |
| | [pool_mode = transaction] |
| | [idle_tx_timeout = 60s] |
| v |
| +--------------------+ |
| | Local Vector DB | (Bloqueio de comunicação com a internet externa) |
| | (Qdrant) | |
| +--------------------+ |
+-------------------------------------------------------------------------------+

`

Para eliminar riscos de vazamento de dados, adicione internal: true à rede do contêiner para bloquear comunicações com nuvens externas. A conexão com o runtime Metal Ollama de alta velocidade rodando diretamente na máquina host é feita apenas pelo gateway host.docker.internal.

`yaml
version: '3.8'

services:
backend-app:
build: .
environment:
- DB_HOST=pgbouncer
- DB_PORT=6432
- VECTOR_DB_HOST=vector-db
- OLLAMA_HOST=http://host.docker.internal:11434
networks:
- isolated_local_net
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "8000:8000"

pgbouncer:
image: edoburu/pgbouncer:latest
environment:
- DB_HOST=postgres_db
- DB_PORT=5432
- DB_USER=postgres
- DB_PASSWORD=secret
- POOL_MODE=transaction
networks:
- isolated_local_net
depends_on:
- postgres_db

postgres_db:
image: postgres:16-alpine
environment:
- POSTGRES_DB=ai_workspace
- POSTGRES_PASSWORD=secret
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- isolated_local_net

vector-db:
image: qdrant/qdrant:v1.9.0
volumes:
- qdrant_data:/qdrant/storage
networks:
- isolated_local_net

networks:
isolated_local_net:
driver: bridge
internal: true

volumes:
pgdata:
qdrant_data:

`

Ao contornar o gargalo de virtualização da GPU com o Podman ou bridge do host, e controlando o ciclo de vida por meio de reciclagem de workers e proxy de DB, cria-se um ambiente de desenvolvimento independente que não cai mesmo em laptops equipados com chips da série M.