Skip to content

[P3][packaging][detection][seed] DL para quem usa Docker: pacote /extras montado a partir do wheelhouse próprio (tokenizers e safetensors cp314t, torch CPU) #2024

Description

@FabioLeitao

U: U3 — sem pressão de prazo.
G: G2 — o README anuncia "ML and optional DL", mas a imagem canônica publicada não inclui o DL; quem usa Docker fica sem ele, em silêncio.
Depende de: #1451 (opção de config para modelo local e revisão, identidade no recibo, falha alta), #1452 (degraded-mode). Sem código antes do PLAN e da decisão do operador.
Decisões do operador (30/set):

  1. as dependências do DL são criadas no wheelhouse próprio;
  2. musl e no-AVX: DL limitado e documentado; a limitação é aceita;
  3. qualquer sidecar tem licença própria e zero trust bidirecional.

Correções (30/set), erros meus: (a) a primeira versão dizia que torch e onnxruntime não têm wheel cp314t: têm; eu ignorei a tag de ABI. (b) a segunda versão propunha "em processo na imagem × sidecar" sem ter lido docs/DOCKER_SETUP.md: a rota do Docker já está documentada (/extras). Esta versão parte dela.

Contexto (verificado por gh api, PyPI e índice CPU do PyTorch, 30/set)

  • O ML já está na imagem (scikit-learn==1.9.1, numpy, scipy, joblib com hash no requirements.txt). Não executei a imagem.
  • A imagem é CPython 3.14t (cp314t) em runtime distroless (ADR-0091: uma imagem só, ~309 MB). docs/DOCKER_SETUP.md diz que ela é "lean on purpose": "no fat image, no image matrix, no FROM our image + rebuild", sem shell nem pip. O extra dl está na coluna "via runtime mount".
  • O mecanismo já existe (/extras): o cliente monta um pacote de wheels ABI-compatível, -v extras-pack:/extras:ro (uid 65532), com PYTHONPATH=/extras:/app. python main.py --check-extras mostra cada extra por origem (imagem × /extras), e um pacote com ABI errada falha alto na partida.
  • Cadeia do DL contra o cp314t:
Pacote Situação
torch 2.14.0 cp314t: sim. PyPI (Linux x86_64 555 MB) e índice CPU do PyTorch (2.14.0+cpu, 196 MB, medido por HEAD, sem baixar). O wheel do PyPI para Linux puxa dependências CUDA; o do índice CPU não
onnxruntime 1.30.0 cp314t: sim, Linux x86_64 (24 MB)
tokenizers 0.23.2 só abi3, sem cp314t
safetensors 0.8.0 só abi3, sem cp314t
sentence-transformers 6.1.0 puro; exige torch>=2.2 (o extra onnx não remove o torch)
transformers, huggingface-hub puros
hf-xet, numpy, scipy, scikit-learn, regex, pillow, pyyaml cp314t: sim

Wheels abi3 não carregam em build free-threaded (regra do CPython; confirmar com pip install --dry-run num 3.14t). O que falta para pip install --target ./extras-pack 'data-boar[dl]' funcionar num host cp314t são tokenizers e safetensors em cp314t; sem eles o pip cai em build de Rust na máquina do cliente.

  • Invariante já escrito no repo: PLAN_PACKAGING_EXTRAS.md: "degrade LOUD and FN-first, never silent"; para o [dl], "never omit embeddings in silence". O código atual descarta o erro (_ = str(model_err), core/dl_backend.py).
  • data-boar no PyPI (1.7.4.post12, requires-python >=3.12) já tem o extra dl, então fora do Docker pipx install "data-boar[dl]" funciona no host (não executei).

Proposta (síntese do Claude, a confirmar)

  1. Rota principal do Docker: o pacote /extras do dl, sem mexer na imagem. O pacote é construído a partir do wheelhouse, offline: pip install --target ./extras-pack --no-index --find-links <wheelhouse> ....
    • Cuidado de reprodutibilidade: /extras vem antes de /app no PYTHONPATH, então o que estiver no pacote sobrepõe o que está na imagem. O pacote deve conter só o delta (torch, sentence-transformers, tokenizers, safetensors e o que faltar), construído com um arquivo de constraints igual ao requirements.txt da imagem, para não trocar numpy/scikit-learn pinados. Conferir por origem no --check-extras. (Raciocínio pela mecânica do PYTHONPATH; não executei.)
  2. Wheelhouse próprio (mesmo canal hospedado em data-boar-site, com SHA256SUMS e índice simple/): construir tokenizers e safetensors para cp314t (Rust, maturin; commit fixado por SHA, sem rede, código lido) e espelhar o torch CPU (+cpu) cp314t com hash conferido. Não compilar o torch do zero.
  3. Spike com go/no-go num Python 3.14t: import sentence_transformers, encode() de um lote fixo com o GIL ligado e desligado, comparar os vetores com o caminho atual, medir tamanho do pacote e latência. Nada disso foi executado.
  4. Camadas de execução do encoder (recomendado + fallback, sem coroado):
    • v1, recomendado: torch CPU + sentence-transformers no pacote /extras (paridade exata com o caminho atual; tamanho do pacote não medido);
    • sentence-transformers com backend=onnx: sem ganho de tamanho, o torch continua obrigatório;
    • ONNX Runtime direto + tokenizers (ORT 24 MB): pacote enxuto, mas exige exportar o MiniLM para ONNX, escrever o pooling e um teste de paridade (fp32 deve ficar muito próximo; int8 muda os vetores);
    • Latent Lynx (Rust, candle): hoje é scaffold. O embedder padrão é um mock (SHA-256 → 384 floats) que não é o MiniLM; o candle real fica atrás de feature e exige --model-dir (sem download silencioso); não há modo "só vetores". Candidato de longo prazo, com retreino da cabeça e teste de paridade.
  5. Sidecar só se o spike pedir (torch cp314t com GIL desligado, ou tamanho). Se adotado, vale a regra do operador: licença própria e zero trust bidirecional:
    • o sidecar só processa payload depois de validar um grant assinado do Boar (escopo, capacidade, expiração), como o handshake.py do Stoat já faz do lado dele ("no attestation, no transform — fail closed");
    • o Boar valida o sidecar antes de enviar o primeiro byte: produto e licença, versão, identidade do encoder (nome, revisão, sha256), e recusa o encoder mock;
    • nenhum lado confia no dado do outro: o Boar valida dimensão (384) e valores finitos; o sidecar valida tamanho e taxa dos pedidos e não loga payload;
    • isto já existe no Boar (correção 30/set, erro meu anterior): o SDK bidirecional de atestação mútua — feat(sdk): #865 bidirecional/zero-trust — wiring do contrato simétrico + boar_fast_filter como 1º plugin full-fledged (dogfood, 1.8.x) #1116 (CLOSED), ADR-0087, core/sdk/mutual_attestation.py (MeshRole host/guest, trust_is_acceptable_for_guest, challenge/response Ed25519, Safe-Hold em peer adulterado) — já faz os dois sentidos ("host contém plugin ruim ↔ plugin recusa host adulterado"). O sidecar de DL reusa esse contrato (PLAN_SDK_BIDIRECTIONAL.md, PLAN_PLUGIN_SDK_CONTRACT.md/epic(sdk): formalizar Boar Plugin SDK — contrato agnóstico versionado + fronteira L1/L2/L3 + fail-graceful (plugin = superfície do sidecar) #865), não inventa handshake novo. O que falta é só o item específico do encoder: o Boar recusar um sidecar que se identifique como mock e exigir a identidade do modelo (nome, revisão, sha256) no payload de atestação.
  6. musl e no-AVX: DL limitado e documentado (decisão do operador): degrada em voz alta para regex + ML, como o PLAN_PACKAGING_EXTRAS já manda. O ML continua nessas plataformas pelo wheelhouse x86-64-v1.
  7. Air-gap: três artefatos verificados por hash no destino (courier em desenho no ecossistema): a imagem salva (docker save → tar com sha256; conferir se o digest do manifesto se preserva no docker load, senão registrar o digest nas notas do release), o pacote /extras e os pesos do modelo.
  8. Supply chain: builds de Rust/PyO3 de terceiros e espelho de binário do PyTorch com pin por hash, SBOM e proveniência (security/release: implement generate-sbom/check-sbom/emit-provenance reference wrappers (split from #1950) #1966) e licenças conferidas. O safetensors (formato sem execução de código) é o argumento de segurança a favor.

Não fazer

Segunda tag da imagem do Boar; imagem "fat" com DL; baixar pesos em runtime; compilar o torch do zero; qualquer LLM no caminho crítico.

Limites e riscos (nada disto foi medido)

Tamanho do pacote; latência e throughput por lote; paridade entre CPUs (float); comportamento do torch cp314t com o GIL desligado (tier pago, ADR-0091); piso de CPU x86-64-v2+ da imagem; licença do modelo a conferir antes de redistribuir; formato dos pesos (preferir safetensors, conferir).

Critérios de aceite

  • Criar docs/plans/PLAN_DL_DOCKER_AND_WHEELHOUSE.md com <!-- plans-hub-summary: ... --> e ligá-lo de PLAN_WHEELHOUSE_DISTRIBUTION.md.
  • Executar python scripts/plans_hub_sync.py --write.
  • Adicionar entry em docs/plans/PLANS_TODO.md.
  • Spike em 3.14t registrado (import, encode(), GIL ligado e desligado, paridade dos vetores, tamanho, latência), com go/no-go explícito.
  • Receita do wheelhouse para tokenizers e safetensors cp314t, com SHA256SUMS.
  • Receita do pacote /extras do dl (só o delta, com constraints da imagem), documentada em docs/DOCKER_SETUP.md.
  • --check-extras ([P1][1.8.x][packaging][ci] EXTRAS_MANIFEST.json + --check-extras + guarda no docker-image-smoke.sh — impedir a divergência artefato × pyproject de voltar #1401) reporta o DL por origem (/extras) e distingue "ausente" de "presente sem pesos".
  • Teste: sem DL utilizável, dl: UNAVAILABLE no recibo e no log, e o resultado é idêntico ao de hoje.
  • Limitação de musl e no-AVX documentada em docs/TROUBLESHOOTING.md.
  • README sem prometer DL para a imagem padrão; docs/CLAIMS.yml com o claim "ML/DL" por perfil.

Fila e PR

Uma issue; no máximo 1 PR (plano, receita do wheelhouse e do pacote, spike); nunca uma PR por item. Publicar o asset do wheelhouse é ação do operador. Prioridade e milestone são sugestão; o sequenciamento é do operador.

Relacionado: #1451, #1452, #1059, #1401, #1822, #1966, #2023, #1116 (SDK bidirecional, ADR-0087), #865 (contrato do SDK), ADR-0091.

🤖 Registrado por Claude (Opus 4.8, T14, auditor RO), a pedido do operador em 30/set/2026. Verificado por gh api, PyPI e índice do PyTorch; nada executado nem baixado. Implementação: Cursor. Tom recomendatório.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P3Low — backlog / nice-to-haveenhancementNew feature or request

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions