mirror of
https://github.com/mcloh/decision-models-benchmark.git
synced 2026-11-05 22:04:12 +00:00
Docs: cenário de execução e decisões vigentes, sem diário de bordo
- docs/05 → 05-cenario-de-execucao.md: onde cada etapa roda, isolamento, fluxo de uma execução e runtimes, sem detalhes de instância ou cotas. - docs/04 → 04-decisoes-de-projeto.md: decisões vigentes (Q1–Q14), sem revisões datadas nem conteúdo superado; Q15 incorporada à Q7. - Plano, desenho experimental e README com tom de roteiro (origem → trajeto → destino) em vez de registro de falhas. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
# 02 — Desenho experimental
|
||||
|
||||
> **Escopo do primeiro ciclo (revisão de 2026-10-07).** Não há agentes implementados. Por isso o ciclo avalia só a **classificação de intenções (D1)** no domínio de **atendimento de telefonia móvel**, com a taxonomia `data/taxonomy/intents-v1.yaml` (42 intenções em 13 domínios). Pedidos fora de escopo, enunciados emocionais e casos com várias intenções entram como exemplos de D1, com rótulo `sem_correspondencia` ou a intenção principal. D2 a D5 ficam descritas abaixo para os próximos ciclos. Os dados são **100% sintéticos** e a anotação é feita por **três modelos generativos** (ver §4).
|
||||
> **Escopo do primeiro ciclo.** O ciclo avalia a **classificação de intenções (D1)** no domínio de **atendimento de telefonia móvel**, com a taxonomia `data/taxonomy/intents-v1.yaml` (42 intenções em 13 domínios). Pedidos fora de escopo, enunciados emocionais e casos com várias intenções entram como exemplos de D1, com rótulo `sem_correspondencia` ou a intenção principal. D2 a D5 ficam descritas abaixo para os próximos ciclos, quando houver agentes implementados. Os dados são **100% sintéticos** e a anotação é feita por **três modelos generativos** (ver §4).
|
||||
|
||||
## 1. Hipóteses
|
||||
|
||||
- **H1 (acurácia).** Pelo menos um candidato atinge, em PT-BR, uma acurácia de roteamento (D1/D2) compatível com o uso no orquestrador. O critério é a não inferioridade em relação ao baseline LLM (margem de 3 p.p.); ver [04, Q5](04-questoes-em-aberto.md#q5--critérios-de-sucesso-pré-registrados).
|
||||
- **H1 (acurácia).** Pelo menos um candidato atinge, em PT-BR, uma acurácia de roteamento (D1/D2) compatível com o uso no orquestrador. O critério é a não inferioridade em relação ao baseline LLM (margem de 3 p.p.); ver [04, Q5](04-decisoes-de-projeto.md#q5--critérios-de-sucesso-pré-registrados).
|
||||
- **H2 (calibração).** As probabilidades e a confiança devolvidas permitem uma política de abstenção (encaminhar para desambiguação) que melhora a acurácia seletiva sem derrubar demais a cobertura.
|
||||
- **H3 (desempenho).** O candidato cabe no orçamento de latência por turno (p95 com lote 1) com um custo por mil decisões menor que o de uma chamada a um LLM generalista.
|
||||
|
||||
## 2. Tarefas de decisão
|
||||
|
||||
Cada tarefa tem **pergunta**, **opções com descrição** e **regra de decisão** (tarefa 21). No primeiro ciclo, cada questão tem **no máximo 16 opções**, incluindo `sem_correspondencia` (tarefas 22–23; limite do SemIf, ver Q2).
|
||||
Cada tarefa tem **pergunta**, **opções com descrição** e **regra de decisão** (tarefa 21). No primeiro ciclo, cada questão tem **no máximo 16 opções**, incluindo `sem_correspondencia` (tarefas 22–23; ver Q2).
|
||||
|
||||
### D1 — Classificação de intenção
|
||||
|
||||
@@ -81,11 +81,11 @@ Cada tarefa tem **pergunta**, **opções com descrição** e **regra de decisão
|
||||
|
||||
**Conversão para JEV.** `question` vira `questions.<task>.instructions` e `options` vira `criteria` (contrato `jev-compat-v1`, em [contracts/jev/schemas/](../contracts/jev/schemas/)). O `state` enviado é sempre o **texto serializado** descrito abaixo, nunca o objeto estruturado.
|
||||
|
||||
**Serialização do estado.** Alguns candidatos aceitam só texto, e não `state` estruturado. Por isso, uma **única regra de serialização** de `state` + `utterance` em texto vale para todos os adaptadores (um bloco "Contexto:" com o agente ativo, as sessões suspensas e os N últimos turnos, seguido de "Mensagem:"; N calculado como em [04, Q8](04-questoes-em-aberto.md#q8--turnos-no-state-e-orçamento-de-tokens)). A regra é versionada junto com a política de truncamento (tarefa 53). Um adaptador não pode usar informação de `state` que não esteja nessa serialização.
|
||||
**Serialização do estado.** Alguns candidatos aceitam só texto, e não `state` estruturado. Por isso, uma **única regra de serialização** de `state` + `utterance` em texto vale para todos os adaptadores (um bloco "Contexto:" com o agente ativo, as sessões suspensas e os N últimos turnos, seguido de "Mensagem:"; N calculado como em [04, Q8](04-decisoes-de-projeto.md#q8--turnos-no-state-e-orçamento-de-tokens)). A regra é versionada junto com a política de truncamento (tarefa 53). Um adaptador não pode usar informação de `state` que não esteja nessa serialização.
|
||||
|
||||
## 4. Dados
|
||||
|
||||
**Primeiro ciclo: pipeline sintético (`datagen/`), executado na VM.**
|
||||
**Primeiro ciclo: pipeline sintético (`datagen/`).**
|
||||
|
||||
| Etapa | Implementação | Tarefas |
|
||||
|-------|---------------|---------|
|
||||
@@ -94,7 +94,7 @@ Cada tarefa tem **pergunta**, **opções com descrição** e **regra de decisão
|
||||
| Anotação | `datagen.annotate`: três modelos (`openai.gpt-5.5`, `google.gemini-2.5-pro`, `xai.grok-4.3`) em rodízio por lote; dois anotam de forma independente, sem ver o rótulo de geração, e o terceiro desempata. Três rótulos distintos → exemplo excluído. Instruções: [06-guia-de-anotacao.md](06-guia-de-anotacao.md) | 29–32 |
|
||||
| Montagem | `datagen.build`: partição por lote de geração (3/2/2 lotes por intenção para teste/calibração/dev), conjuntos de 4, 8, 12 ou 16 opções simulando a elegibilidade (10% sem a intenção correta → `sem_correspondencia`), lint e congelamento com SHA-256 em `dmb-entrada` e `dmb-logs-imutaveis` | 33–36 |
|
||||
|
||||
**Conjunto d1-v1 (congelado em 2026-10-07):** 4.068 gerados → 4.057 após a preparação → 4054 após a anotação (62 por desempate; 3 excluídos). Partições: dev 1183, calibração 1127, teste 1744, com 43 rótulos em todas. Kappa entre anotadores de 0.98 a 0.99: a concordância muito alta sugere que parte dos casos sintéticos é fácil. Para o relatório, os cortes por perfil (erros, regionalismo, emocional, indireto) e por `gold_eligible=false` devem receber atenção.
|
||||
**Conjunto d1-v1:** 4.068 gerados → 4.057 após a preparação → 4054 após a anotação (62 por desempate; 3 excluídos). Partições: dev 1183, calibração 1127, teste 1744, com 43 rótulos em todas. Kappa entre anotadores de 0.98 a 0.99: a concordância muito alta sugere que parte dos casos sintéticos é fácil. Para o relatório, os cortes por perfil (erros, regionalismo, emocional, indireto) e por `gold_eligible=false` devem receber atenção.
|
||||
|
||||
Itens abaixo descrevem o desenho geral (válido também para dados reais em ciclos futuros).
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 03 — Plano de tarefas
|
||||
|
||||
As 78 microtarefas mantêm a numeração original. As linhas **↳** registram a adaptação de cada tarefa ao contexto de roteamento e orquestração ou uma dependência importante. As referências *Qn* apontam para as decisões de projeto em [04-questoes-em-aberto.md](04-questoes-em-aberto.md).
|
||||
As 78 microtarefas mantêm a numeração original. As linhas **↳** registram a adaptação de cada tarefa ao contexto de roteamento e orquestração ou uma dependência importante. As referências *Qn* apontam para as decisões de projeto em [04-decisoes-de-projeto.md](04-decisoes-de-projeto.md); o cenário de execução está em [05-cenario-de-execucao.md](05-cenario-de-execucao.md).
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -45,40 +45,38 @@ Legenda: `[x]` concluída · `[~]` em andamento ou parcial · `[ ]` pendente.
|
||||
## F0 — Infraestrutura OCI
|
||||
|
||||
- [x] **1.** Criar um compartment OCI dedicado ao benchmark.
|
||||
- ↳ `<compartimento-pai>/decision-models` (caminho em `.secrets/oci-settings.json`). Recursos em `us-chicago-1`. OCIDs em `.secrets/artefatos-OCI.md` (fora do Git).
|
||||
- ↳ `<compartimento-pai>/decision-models`. Catálogo de recursos fora do Git, em `.secrets/`.
|
||||
- [~] **2.** Aplicar as tags `projeto=benchmark-decisoes-ptbr`, `ambiente=benchmark` e `centro_custo=<definir>` aos recursos do projeto. *(Q13)*
|
||||
- ↳ Parcial: `projeto` e `ambiente` aplicadas como tags livres por `infra/oci/provision.py`. Falta `centro_custo`.
|
||||
- ↳ `projeto` e `ambiente` aplicadas como tags livres por `infra/oci/provision.py`. `centro_custo` não se aplica neste ciclo (Q13).
|
||||
- [ ] **3.** Criar um orçamento para o compartment.
|
||||
- ↳ **Em aberto por decisão (2026-10-07):** o uso está coberto por cota interna de engenharia. Não será criado no primeiro ciclo.
|
||||
- ↳ Não se aplica neste ciclo: o uso está coberto por cota interna de engenharia (Q13).
|
||||
- [ ] **4.** Configurar alertas de orçamento em 50%, 80% e 100% do valor aprovado.
|
||||
- ↳ Em aberto, porque depende da tarefa 3.
|
||||
- ↳ Não se aplica neste ciclo (depende da tarefa 3).
|
||||
- [x] **5.** Criar um projeto no OCI Data Science.
|
||||
- ↳ `benchmark-decisoes-ptbr`.
|
||||
- [~] **6.** Criar buckets no Object Storage para `entrada`, `artefatos`, `resultados` e `logs-imutaveis`.
|
||||
- ↳ Criados com o prefixo `dmb-` (nomes de bucket são únicos no namespace), privados e com versionamento.
|
||||
- ↳ Pendente: a regra de retenção (WORM) de `dmb-logs-imutaveis`, que depende do prazo de retenção (tarefa 77).
|
||||
- ↳ Buckets `dmb-*`, privados e com versionamento. A regra de retenção de `dmb-logs-imutaveis` é definida com a política de arquivamento (tarefa 77).
|
||||
- [x] **7.** Definir políticas IAM de menor privilégio para leitura dos dados, escrita dos resultados e execução dos jobs.
|
||||
- ↳ Sem acesso de administrador à tenancy, as políticas ficam no próprio compartment e não usam grupos nem dynamic groups.
|
||||
- ↳ Políticas no escopo do compartment, sem grupos nem dynamic groups, com principais identificados por condição.
|
||||
- ↳ `dmb-usuario-manager`: o usuário (filtrado por `request.user.id`) é *manager* das famílias data-science, object, virtual-network, logging, repos e generative-ai.
|
||||
- ↳ `dmb-jobs-menor-privilegio`: os jobs (`request.principal.type='datasciencejob'`) leem `dmb-entrada`/`dmb-artefatos`, escrevem em `dmb-resultados`/`dmb-logs-imutaveis`, usam logs e repos. Inclui a política de serviço do Data Science para a VCN.
|
||||
- [x] **8.** Definir uma VCN privada para os jobs, com acesso ao Object Storage por Service Gateway.
|
||||
- ↳ `dmb-vcn` 10.20.0.0/16, sub-rede privada `dmb-subnet-jobs`. A rota e a security list só permitem tráfego para serviços OCI pelo Service Gateway.
|
||||
- [x] **9.** Criar uma rota temporária de saída por NAT Gateway, só para a aquisição inicial de dependências e pesos.
|
||||
- ↳ Remover a rota logo após a tarefa 16 e passar pelo gate de egresso antes de qualquer execução. *(Q3)*
|
||||
- ↳ **Não foi necessário:** a aquisição (tarefas 13–16) rodou fora da VCN e enviou os artefatos direto ao bucket; os ambientes são montados por jobs de build com rede gerenciada. `infra/oci/nat.py` fica disponível.
|
||||
- ↳ Criar e ativar **só quando necessário**, com `infra/oci/nat.py on`. Para desligar: `nat.py off`, que fecha a rota e bloqueia o NAT. Na tarefa 78: `nat.py off --delete`.
|
||||
- ↳ A aquisição (tarefas 13–16) é feita fora da rede dos jobs, e os ambientes são montados por jobs de build. A VCN dos jobs não tem saída para a internet. *(Q3)*
|
||||
- ↳ `infra/oci/nat.py on|off|off --delete` abre e fecha uma saída temporária, se alguma aquisição futura precisar ser feita de dentro da VCN.
|
||||
- [x] **10.** Validar que os jobs acessam os buckets sem internet pública.
|
||||
- ↳ Validado em 2026-10-07 com jobs do Data Science em `dmb-subnet-jobs`. Os pesos do Rizzo (4,4 GB) desceram pela Service Gateway em 28 s, com SHA-256 conferido.
|
||||
- ↳ Jobs do Data Science em `dmb-subnet-jobs` baixam os artefatos pela Service Gateway, com SHA-256 conferido (4,4 GB em cerca de 30 s).
|
||||
|
||||
## F1 — Ambiente e artefatos
|
||||
|
||||
- [x] **11.** Criar uma imagem de contêiner base com Python, CUDA, PyTorch, Transformers, Hugging Face Hub e ferramentas de medição.
|
||||
- ↳ Incluir um exportador OpenTelemetry, para que a telemetria do harness tenha o mesmo formato da observabilidade do orquestrador.
|
||||
- ↳ **Substituído na prática por ambientes Python publicados no bucket** (`infra/ds/build_env.sh` → `dmb-artefatos/envs/<candidato>/`), porque os jobs do Data Science dispensam contêiner próprio. O CUDA vem das wheels `cu128` do PyTorch e o Python é o 3.11.9 base dos jobs.
|
||||
- ↳ `env/Dockerfile` com `--build-arg CANDIDATE=...`: **uma imagem por candidato**, porque o SemIf exige `transformers==5.17.0` e o GLiNER exige `transformers<5`. Os pesos ficam fora da imagem. Falta fazer o build e o push para o OCIR, na VM.
|
||||
- ↳ Implementado como **ambientes Python por candidato** publicados no bucket (`infra/ds/build_env.sh` → `dmb-artefatos/envs/<candidato>/`), executados nos jobs do Data Science. CUDA pelas wheels `cu128` do PyTorch; runtime llama.cpp para o Rizzo.
|
||||
- ↳ Um ambiente por candidato, porque o SemIf exige `transformers==5.17.0` e o GLiNER `transformers<5`. `env/Dockerfile` oferece o equivalente em contêiner (uma imagem por candidato).
|
||||
- [x] **12.** Fixar num manifesto as versões do sistema, dos drivers CUDA, do Python, das bibliotecas e da imagem de contêiner.
|
||||
- [x] **13.** Baixar os pesos, tokenizadores, revisões e licenças aprovados de cada candidato. *(Q4)*
|
||||
- ↳ `env/acquire.py`: todas as licenças são Apache-2.0 ou MIT (incluindo o Qwen3.5-4B). Rizzo: GGUF Q8_0 (padrão do projeto) + llama.cpp b11081. **Não foi preciso NAT**: a aquisição rodou fora da VCN e enviou direto ao bucket.
|
||||
- ↳ `env/acquire.py`: todas as licenças são Apache-2.0 ou MIT (incluindo o Qwen3.5-4B). Rizzo: GGUF Q8_0 (padrão do projeto) + llama.cpp b11081.
|
||||
- ↳ A aquisição falha se a licença estiver fora da allowlist de `config/benchmark.yaml`. O contexto efetivo de cada candidato é medido e registrado.
|
||||
- ↳ Incluir as dependências específicas de cada candidato (pacote GLiNER, runtime SemIf, interface Rizzo Flow) e a revisão fixada de `Qwen/Qwen3.5-4B`.
|
||||
- [x] **14.** Calcular o SHA-256 de cada artefato baixado.
|
||||
@@ -88,10 +86,9 @@ Legenda: `[x]` concluída · `[~]` em andamento ou parcial · `[ ]` pendente.
|
||||
- ↳ O gate de egresso foi aprovado nas execuções em sub-rede privada: huggingface.co, pypi.org e github.com ficaram inacessíveis.
|
||||
- [x] **18.** Carregar cada candidato só a partir dos artefatos locais.
|
||||
- ↳ Usar `HF_HUB_OFFLINE=1`, `TRANSFORMERS_OFFLINE=1` e um cache apontado para os artefatos verificados.
|
||||
- ↳ `harness/job.py` baixa do bucket só o que está no manifesto e confere o SHA-256. Os quatro candidatos já carregaram offline, a partir dos artefatos locais.
|
||||
- ↳ `harness/job.py` baixa do bucket só o que está no manifesto e confere o SHA-256; os quatro candidatos carregam offline.
|
||||
- [x] **19.** Fazer o job falhar se algum candidato tentar baixar arquivos durante a inferência.
|
||||
- ↳ Duas camadas: as variáveis de ambiente offline e o bloqueio de rede da VCN. Um teste negativo prova que a falha ocorre.
|
||||
- ↳ Camada de aplicação em `dmb/offline.py` (bloqueia conexões fora do loopback), com teste em `tests/test_offline.py`. O gate de egresso de `harness/job.py` cobre a camada de rede.
|
||||
- ↳ Camada de aplicação em `dmb/offline.py` (variáveis offline e bloqueio de conexões fora do loopback), com autoteste em cada execução e teste em `tests/test_offline.py`. Camada de rede pelo gate de egresso de `harness/job.py`.
|
||||
|
||||
## F2 — Tarefas de decisão e taxonomia
|
||||
|
||||
@@ -102,14 +99,14 @@ Legenda: `[x]` concluída · `[~]` em andamento ou parcial · `[ ]` pendente.
|
||||
- ↳ As descrições das opções seguem o estilo de um *agent card*: curtas e orientadas à capacidade do agente.
|
||||
- [x] **22.** Limitar o primeiro ciclo a no máximo 20 opções por questão, para garantir compatibilidade com o Laya. *(Q2)*
|
||||
- ↳ Os subconjuntos de opções por exemplo simulam o serviço de elegibilidade de intenções.
|
||||
- ↳ **Limite efetivo: 16 opções**, por causa do SemIf (Q2).
|
||||
- ↳ Limite comum: **16 opções** (Q2).
|
||||
- [x] **23.** Incluir uma opção explícita de `outro` ou `sem correspondência` quando a taxonomia não for exaustiva.
|
||||
|
||||
## F3 — Dados e anotação
|
||||
|
||||
- [x] **24.** Coletar textos PT-BR representativos do domínio de destino. *(Q7, Q15)*
|
||||
- [x] **24.** Coletar textos PT-BR representativos do domínio de destino. *(Q7)*
|
||||
- ↳ **100% sintéticos** (`datagen.generate`, na VM): 4.068 enunciados em 339 lotes (42 intenções × 7 perfis + 5 categorias fora de escopo × 9 lotes), geradores `llama-4-maverick` e `command-a` alternados.
|
||||
- ↳ A unidade é a **conversa com vários turnos**, não o enunciado isolado. Cada turno guarda o estado (agente ativo e sessões suspensas).
|
||||
- ↳ Para D2–D4 (ciclos futuros), a unidade é a conversa com vários turnos, com o estado de cada turno.
|
||||
- [x] **25.** Remover ou mascarar dados pessoais, segredos e identificadores que o benchmark não precisa.
|
||||
- [x] **26.** Separar os textos por domínio, comprimento, grau de ambiguidade e classe esperada.
|
||||
- ↳ Acrescentar a posição do turno e a presença de agente ativo ou de sessões suspensas.
|
||||
@@ -123,7 +120,7 @@ Legenda: `[x]` concluída · `[~]` em andamento ou parcial · `[ ]` pendente.
|
||||
- ↳ [06-guia-de-anotacao.md](06-guia-de-anotacao.md), usado literalmente pelos anotadores.
|
||||
- ↳ O guia cobre D1–D4 com exemplos-limite (por exemplo, quando um enunciado curto continua a tarefa do agente ativo e quando inicia um novo agente).
|
||||
- [x] **30.** Anotar cada exemplo com dois avaliadores independentes.
|
||||
- ↳ Avaliadores = modelos generativos em rodízio (`openai.gpt-5.5`, `google.gemini-2.5-pro`, `xai.grok-4.3`). *(Q15)*
|
||||
- ↳ Avaliadores = modelos generativos em rodízio (`openai.gpt-5.5`, `google.gemini-2.5-pro`, `xai.grok-4.3`). *(Q7)*
|
||||
- ↳ Resultado: 4057 anotados; 3992 consensos (98.4%); kappa por par entre 0.982 e 0.986; concordância com o rótulo de geração 97.9%. Relatório em `reports/datagen/annotation-report.json`.
|
||||
- [x] **31.** Resolver as discordâncias com a revisão de um terceiro avaliador.
|
||||
- [x] **32.** Registrar, para cada exemplo, a resposta de referência, a justificativa e a versão da taxonomia.
|
||||
@@ -134,20 +131,20 @@ Legenda: `[x]` concluída · `[~]` em andamento ou parcial · `[ ]` pendente.
|
||||
- [x] **34.** Impedir que textos derivados do mesmo caso apareçam em mais de uma partição.
|
||||
- [x] **35.** Congelar o conjunto de teste final antes de qualquer ajuste de parâmetros.
|
||||
- ↳ Registrar o hash do arquivo congelado e guardar uma cópia em `logs-imutaveis`.
|
||||
- ↳ Congelado em 2026-10-07: SHA-256 em `data/splits/MANIFEST.json`, cópias em `dmb-entrada` e `dmb-logs-imutaveis` (`splits/d1-v1/`), junto com os dados brutos da geração e da anotação.
|
||||
- ↳ SHA-256 em `data/splits/MANIFEST.json`, cópias em `dmb-entrada` e `dmb-logs-imutaveis` (`splits/d1-v1/`), junto com os dados brutos da geração e da anotação.
|
||||
|
||||
## F4 — Contrato JEV e adaptadores
|
||||
|
||||
- [x] **36.** Criar um formato canônico de entrada com `id`, `state`, `question`, `options`, `gold_label` e metadados.
|
||||
- ↳ Ver [02 §3](02-desenho-experimental.md#3-formato-canônico-de-entrada-tarefa-36), que inclui a **regra única de serialização** de `state`.
|
||||
- [x] **37.** Definir a versão do contrato JEV compatível a usar no benchmark: `POST /v1/systemone`, esquema de requisição, esquema de resposta e códigos de erro. *(Q1)*
|
||||
- ↳ Versão `jev-compat-v1`. Os esquemas já estão em `contracts/jev/schemas/`; falta revisar os códigos de erro no código dos adaptadores.
|
||||
- ↳ Versão `jev-compat-v1`, com esquemas em `contracts/jev/schemas/` e códigos de erro em `dmb/jev.py`.
|
||||
- [x] **38.** Definir o mapeamento obrigatório entre o contrato JEV e os tipos internos `choice`, `noul` e `score`.
|
||||
- [x] **39.** Definir a regra de conversão de probabilidades, confiança, níveis ordinais, opções descritas e campos de uso.
|
||||
- [x] **40.** Definir o comportamento padronizado para entradas inválidas, limite de contexto, timeout, opção desconhecida e indisponibilidade do modelo.
|
||||
- ↳ Na prática, uma falha do modelo de decisão vira desambiguação ou fallback no orquestrador. No benchmark, essas falhas contam como erro e são reportadas à parte.
|
||||
- [x] **41.** Criar testes de conformidade com requisições e respostas JEV congeladas.
|
||||
- ↳ `contracts/jev/fixtures/conformance.json` + `tests/` (35 testes).
|
||||
- ↳ `contracts/jev/fixtures/conformance.json` e `tests/`.
|
||||
- [x] **42.** Criar um adaptador JEV local para o Laya com o checkpoint multilíngue fixado.
|
||||
- [x] **43.** Criar um adaptador JEV local para o SemIf em modo `direct`, com a revisão do Qwen fixada.
|
||||
- [x] **44.** Criar um adaptador JEV local para o GLiNER Decide usando `classify_text`.
|
||||
@@ -160,7 +157,7 @@ Legenda: `[x]` concluída · `[~]` em andamento ou parcial · `[ ]` pendente.
|
||||
- [x] **50.** Validar que trocar de candidato não altera a requisição JEV nem o formato da resposta JEV.
|
||||
- [x] **51.** Implementar testes de sanidade com decisões triviais, opções permutadas e os três tipos de pergunta.
|
||||
- ↳ Incluir um teste de invariância à permutação das opções em D1 e D2 (a mesma escolha com outra ordem).
|
||||
- ↳ `harness/sanity.py`. Primeira rodada em CPU (2026-10-07): sem violações de contrato nos 4 candidatos. Acertos: SemIf 8/8, Laya 7/8, Rizzo 7/8, GLiNER 5/8. Invariância à permutação: 4/4 em todos. Os próximos testes rodam na OCI.
|
||||
- ↳ `harness/sanity.py`: os quatro candidatos passam em CPU e GPU, sem violações de contrato e com invariância à permutação 4/4 (`reports/sanity/oci/`).
|
||||
|
||||
## F5 — Controle de execução
|
||||
|
||||
@@ -172,9 +169,9 @@ Legenda: `[x]` concluída · `[~]` em andamento ou parcial · `[ ]` pendente.
|
||||
## F6 — Piloto
|
||||
|
||||
- [ ] **55.** Executar um piloto de 50 exemplos por candidato em CPU.
|
||||
- ↳ Sanidade em CPU (us-chicago-1, sub-rede privada) aprovada nos 4 candidatos: Laya 125 ms, GLiNER 194 ms, Rizzo Flow 7,3 s, SemIf 16,4 s (latência mediana). *(Q11)*
|
||||
- ↳ CPU: `VM.Standard.E4.Flex`, 8 OCPU, us-chicago-1, sub-rede privada. *(Q11)*
|
||||
- [ ] **56.** Executar um piloto de 50 exemplos por candidato em GPU.
|
||||
- ↳ GPU em **sa-saopaulo-1, A10.1**, uma por vez *(Q14)*. Sanidade em GPU: Laya 21 ms, GLiNER 16 ms, SemIf 66 ms (latência mediana). Rizzo Flow 62 ms, depois de compilar o llama.cpp com CUDA para a glibc da imagem dos jobs (`infra/ds/build_llama_cuda.sh`).
|
||||
- ↳ GPU: `VM.GPU.A10.1`, sa-saopaulo-1, execuções em fila. *(Q14)*
|
||||
- [ ] **57.** Medir memória de GPU, memória RAM, tempo de carga, tempo de aquecimento e falhas do piloto.
|
||||
- [ ] **58.** Ajustar apenas os parâmetros operacionais necessários para eliminar falhas de execução.
|
||||
|
||||
@@ -213,6 +210,7 @@ Legenda: `[x]` concluída · `[~]` em andamento ou parcial · `[ ]` pendente.
|
||||
## F10 — Relatório e encerramento
|
||||
|
||||
- [ ] **75.** Elaborar o relatório técnico com configuração, limitações, resultados e recomendação.
|
||||
- ↳ Ao final, reestruturar o `README.md`: (1) apresentação executiva do benchmark, (2) resultados em formato executivo, (3) método, (4) detalhes do plano, estrutura e documentos.
|
||||
- [ ] **76.** Anexar ao relatório os manifestos de artefatos, hashes, configurações, logs, testes de conformidade e comandos de reprodução.
|
||||
- [ ] **77.** Arquivar os conjuntos congelados e os resultados no Object Storage com retenção definida.
|
||||
- [ ] **78.** Desligar notebooks, jobs, endpoints temporários e o NAT Gateway que não sejam mais necessários.
|
||||
|
||||
163
docs/04-decisoes-de-projeto.md
Normal file
163
docs/04-decisoes-de-projeto.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# 04 — Decisões de projeto
|
||||
|
||||
As decisões do benchmark seguem um mesmo critério: preferir a **abordagem automatizada e verificável**. Isso significa regras determinísticas em código, gates que fazem o pipeline falhar, valores calculados a partir de medições e parâmetros pré-registrados em [config/benchmark.yaml](../config/benchmark.yaml). Nenhuma decisão é tomada caso a caso durante a execução, e os valores pré-registrados são congelados com hash na tarefa 62.
|
||||
|
||||
## Resumo
|
||||
|
||||
| # | Tema | Decisão | Mecanismo |
|
||||
|---|------|---------|-----------|
|
||||
| Q1 | Contrato | `POST /v1/systemone` público (`state` + `questions` tipadas → `answers` + `usage`), congelado como `jev-compat-v1` | JSON Schema em `contracts/jev/schemas/` e testes de conformidade |
|
||||
| Q2 | Número de opções | Até **16** por pergunta: 15 de domínio + `sem_correspondencia` | Validação de esquema e lint do conjunto de dados |
|
||||
| Q3 | Isolamento de rede | Aquisição fora da rede dos jobs; medições sem acesso a fontes externas | Gate de egresso, bloqueio no processo e verificação por SHA-256 |
|
||||
| Q4 | Interfaces e licenças | Uma interface por candidato (abaixo); só licenças Apache-2.0 ou MIT | A aquisição lê a licença no Hub e falha fora da lista permitida |
|
||||
| Q5 | Critérios de sucesso | Limite de latência fixo e não inferioridade em relação ao baseline | Avaliação automática a partir de `config/benchmark.yaml` |
|
||||
| Q6 | Lote de produção | Por candidato: o maior lote com p95 dentro do limite | Varredura de lotes |
|
||||
| Q7 | Dados e anotação | 100% sintéticos; anotação por três modelos generativos em rodízio | `datagen/` ([02 §4](02-desenho-experimental.md#4-dados)) |
|
||||
| Q8 | Contexto no `state` | Orçamento comum de tokens; D1 usa só o enunciado atual | `dmb.canonical.fit_state` |
|
||||
| Q9 | Baseline LLM | Incluído como referência, fora do ranking principal | Adaptador no mesmo contrato |
|
||||
| Q10 | Calibração | Temperature scaling por candidato e tarefa, só na partição de calibração | Ajuste por NLL e congelamento com hash |
|
||||
| Q11 | CPU | Medida com timeout; inviabilidade declarada por regra | p95 > timeout → `inviavel_cpu` |
|
||||
| Q12 | Tamanho do teste | ≥ 1.300 decisões D1 (análise de poder) | Recalculado na análise |
|
||||
| Q13 | Custos | Sem orçamento provisionado neste ciclo; custo estimado no relatório | Estimativa por cenário (tarefa 70) |
|
||||
| Q14 | Cenário de GPU | `VM.GPU.A10.1` em sa-saopaulo-1, execuções em fila | `infra/ds/gpu_queue.py` e réplica dos artefatos |
|
||||
|
||||
---
|
||||
|
||||
## Q1 — Contrato `jev-compat-v1`
|
||||
|
||||
Base: a forma pública do `POST /v1/systemone`, a mesma reproduzida pelas implementações abertas (OpenJev/SemIf).
|
||||
|
||||
**Requisição**
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "<model_id>",
|
||||
"state": "<texto> | <objeto JSON>",
|
||||
"questions": {
|
||||
"<nome>": {"type": "choice", "instructions": "...", "criteria": {"<opcao>": "<descrição>"}},
|
||||
"<nome>": {"type": "noul", "instructions": "...", "criteria": {"true": "...", "false": "..."}},
|
||||
"<nome>": {"type": "score", "instructions": "...", "criteria": ["<nível 0>", "<nível 1>", "..."]}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Resposta**
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "<model_id>",
|
||||
"answers": {
|
||||
"<nome>": {"type": "choice", "choice": "<opcao>", "confidence": 0.0, "probabilities": {"<opcao>": 0.0}},
|
||||
"<nome>": {"type": "noul", "noul": 0.0},
|
||||
"<nome>": {"type": "score", "score": 0.0, "confidence": 0.0,
|
||||
"probabilities": {"0": 0.0}, "legend": {"0": "<nível 0>"}}
|
||||
},
|
||||
"usage": {"input_tokens": 0, "output_tokens": 0},
|
||||
"metadata": {"latency_ms": 0.0, "model_id": "...", "run_id": "...", "adapter_version": "...",
|
||||
"diagnostics": {}}
|
||||
}
|
||||
```
|
||||
|
||||
**Comportamentos padronizados (tarefa 40)**
|
||||
|
||||
| Situação | Código | Contabilização |
|
||||
|----------|--------|----------------|
|
||||
| Requisição inválida | `422` | Erro do harness: o job falha |
|
||||
| Estado acima do contexto, depois do truncamento comum | `422` | Erro de decisão (`error_type=context`) |
|
||||
| Timeout | `504` | Erro de decisão (`error_type=timeout`) |
|
||||
| Opção devolvida fora de `criteria` ou com a ordem alterada | `502` | Erro de decisão (`error_type=unknown_option`) |
|
||||
| Modelo indisponível | `503` | Erro de execução |
|
||||
|
||||
**Mapeamento dos tipos (tarefas 38–39)**
|
||||
|
||||
- `choice` → D1, D2 e D5. A predição é `choice`; a distribuição é `probabilities`.
|
||||
- `noul` → D3 e as perguntas por intenção de D4. P(sim) = `noul`.
|
||||
- `score` → reservado para ciclos futuros.
|
||||
- **`confidence` não é calibrada** em nenhum candidato. As métricas de calibração e abstenção usam `max(probabilities)` depois da calibração da tarefa 61; a `confidence` nativa é só registrada.
|
||||
- Todas as distribuições recebem um piso numérico comum (1e-6) e são renormalizadas, preservando a ordem das opções.
|
||||
- `output_tokens` é sempre 0, porque não há geração.
|
||||
- `metadata.diagnostics` traz avisos do próprio modelo, por exemplo opções truncadas pelo orçamento interno do Laya.
|
||||
|
||||
## Q2 — Número de opções
|
||||
|
||||
O limite comum é de **16 opções**, incluindo `sem_correspondencia`. Esse é o máximo aceito pela interface de todos os candidatos: o SemIf usa as letras A–P, o Laya recomenda até cerca de 20 e o Rizzo aceita 26. Um exemplo com mais de 16 opções reprova o lint e impede o congelamento.
|
||||
|
||||
## Q3 — Isolamento de rede
|
||||
|
||||
- **Aquisição.** Pesos, tokenizadores, código-fonte e runtimes são obtidos fora da rede dos jobs e publicados no bucket de artefatos, com SHA-256 no manifesto. Os ambientes dos candidatos são montados por jobs de build e também publicados no bucket.
|
||||
- **Medições em CPU.** Rodam numa sub-rede privada que só alcança os serviços OCI pela Service Gateway. O gate de egresso tenta abrir conexão com hosts públicos (Hugging Face Hub, PyPI, GitHub) e só aprova se todas as tentativas falharem.
|
||||
- **Medições em GPU.** Rodam na rede gerenciada do serviço, com o isolamento garantido no processo (ver Q14).
|
||||
- **Em todas as medições:** variáveis offline, bloqueio de sockets fora do loopback e um autoteste registrado em cada resultado (`process_guard`).
|
||||
- `infra/oci/nat.py` permite abrir uma saída temporária por NAT, caso alguma aquisição futura precise ser feita de dentro da VCN.
|
||||
|
||||
## Q4 — Interfaces e licenças dos candidatos
|
||||
|
||||
| Candidato | Licença | Interface usada pelo adaptador | Contexto | Observação |
|
||||
|-----------|---------|--------------------------------|----------|------------|
|
||||
| Laya multilingual | Apache-2.0 | `laya.load(...).predict(state, questions)`, nativo no formato JEV | 1.024 tokens (até 8.192) | Pergunta e opções dividem um orçamento interno de cabeçalho (`head_max_len`). As probabilidades vêm descalibradas e arredondadas em 4 casas |
|
||||
| SemIf + Qwen3.5-4B | MIT (SemIf), Apache-2.0 (Qwen) | Modo `direct`: um forward pass lê os logits das opções declaradas | 8.192 tokens | Revisão do Qwen fixada no manifesto; bf16 |
|
||||
| GLiNER2.5-multi-Decide | Apache-2.0 | `classify_text` com rótulos descritos; distribuição completa pelo softmax do próprio modelo (`multi_label` com `class_act="softmax"` e limiar 0) | 4.096 tokens | A pergunta entra como prefixo do texto, porque `classify_text` não tem campo de instrução |
|
||||
| Rizzo Flow 4B | Apache-2.0 (base); dados de treino com componentes CC-BY-SA/CC-BY | Prompts `spark-decisions-v3` e leitura dos logits das letras de resposta, GGUF Q8_0 no llama.cpp | 2.048 tokens | Formato de prompt obrigatório; documentação só em inglês |
|
||||
|
||||
- A **lista de licenças permitidas** fica em `config/benchmark.yaml`. A aquisição lê `cardData.license` no Hub e falha fora da lista.
|
||||
- O **contexto efetivo** de cada candidato fica registrado no adaptador e no resumo de cada execução.
|
||||
|
||||
## Q5 — Critérios de sucesso pré-registrados
|
||||
|
||||
- **Desempenho (H3):** em GPU com lote 1, p95 ≤ **150 ms por decisão**. Em CPU, os valores são reportados como viabilidade.
|
||||
- **Acurácia (H1):** **não inferioridade em relação ao baseline LLM**, com margem de **3 p.p.**: o limite inferior do IC 95% (bootstrap pareado por grupo) da diferença precisa ser > −3 p.p. Sem baseline, vale o critério absoluto de macro F1 ≥ 0,80.
|
||||
- **Risco (H2):** no limiar escolhido na calibração, a taxa de **rota errada com confiança alta** precisa ser ≤ **2%**, com cobertura ≥ **80%**.
|
||||
|
||||
## Q6 — Lote de produção
|
||||
|
||||
A varredura de lotes {1, 2, 4, 8, 16, 32} em GPU define, por candidato, o maior lote com p95 por decisão dentro do limite. Lote 1 é o cenário principal, porque representa o caminho síncrono do orquestrador. O lote de produção serve para reportar vazão e custo.
|
||||
|
||||
## Q7 — Dados sintéticos e anotação por modelos generativos
|
||||
|
||||
- **Dados 100% sintéticos** no domínio de atendimento de telefonia móvel, incluindo pedidos fora de escopo e enunciados emocionais. São gerados por modelos de fornecedores diferentes dos anotadores (`meta.llama-4-maverick` e `cohere.command-a`).
|
||||
- **Preparação:** mascaramento de dados pessoais (CPF, CNPJ, cartão, telefone, e-mail, CEP), deduplicação exata e aproximada, e limites de comprimento.
|
||||
- **Anotação:** três modelos generativos de fornecedores distintos (`openai.gpt-5.5`, `google.gemini-2.5-pro` e `xai.grok-4.3`), em rodízio. Por lote, dois anotam de forma independente, sem ver o rótulo de geração, e o terceiro desempata. Três rótulos distintos excluem o exemplo.
|
||||
- **Partição** por lote de geração (`group_id`), para que paráfrases do mesmo lote nunca fiquem em partições diferentes.
|
||||
- **Limitação:** os rótulos de referência refletem o consenso de modelos generativos, não de pessoas. O relatório traz a concordância entre anotadores (kappa por par) e com o rótulo de geração.
|
||||
|
||||
## Q8 — Contexto no `state` e orçamento de tokens
|
||||
|
||||
- Todos os candidatos recebem o mesmo texto serializado (`state-text-v1`), com orçamento de **512 tokens** para o estado.
|
||||
- A regra de truncamento remove primeiro os turnos mais antigos e só corta o fim do enunciado se ele sozinho estourar o orçamento. O exemplo recebe `truncated=true`.
|
||||
- Em D1 não há histórico (`n_turns = 0`). O maior estado do conjunto d1-v1 tem menos de 120 tokens em qualquer tokenizador, então não há truncamento.
|
||||
- Para tarefas com histórico (D2–D4), N ∈ {0, 1, 2, 4} é definido como o maior valor em que ≥ 95% dos exemplos de desenvolvimento cabem no orçamento.
|
||||
|
||||
## Q9 — Baseline LLM
|
||||
|
||||
- Um LLM generalista do OCI Generative AI, com a versão fixada no manifesto e adaptador no mesmo contrato, com saída restrita às opções.
|
||||
- Quando há logprobs, as probabilidades vêm deles. Sem logprobs, a distribuição é one-hot e o baseline fica fora das métricas de calibração.
|
||||
- Usa a mesma serialização e os mesmos dados. Serve de referência para H1 e para custo, fora do ranking principal.
|
||||
|
||||
## Q10 — Calibração
|
||||
|
||||
- **Temperature scaling** por candidato e tarefa: um único parâmetro, ajustado por minimização de NLL na partição de calibração.
|
||||
- O calibrador só é aceito se reduzir o ECE em validação cruzada de 5 dobras por `group_id`. Caso contrário, mantém-se T = 1.
|
||||
- Os limiares de abstenção são escolhidos sobre as probabilidades calibradas, maximizando a cobertura com taxa de rota errada com confiança alta ≤ 2%.
|
||||
- Os parâmetros T e os limiares são versionados e congelados com hash na tarefa 62.
|
||||
|
||||
## Q11 — CPU
|
||||
|
||||
- Timeout de **10 s por decisão**.
|
||||
- Um candidato com p95 acima do timeout, ou com taxa de timeout acima de 5%, recebe `inviavel_cpu` e fica fora das rodadas finais em CPU. Os resultados em CPU são reportados como viabilidade, não como ranking.
|
||||
|
||||
## Q12 — Tamanho do conjunto de teste final
|
||||
|
||||
- Análise de poder para a comparação pareada (McNemar) entre dois candidatos em D1: diferença mínima detectável de 3 p.p., α = 0,05, poder de 0,80, discordância esperada de 0,15. Isso dá n ≈ **1.300 decisões**.
|
||||
- O teste d1-v1 tem **1.744 decisões**.
|
||||
- A análise recalcula o efeito mínimo detectável com a discordância observada e com a correção de agrupamento por `group_id`.
|
||||
|
||||
## Q13 — Custos
|
||||
|
||||
O uso deste ciclo está coberto por cota interna de engenharia, então não há orçamento nem alertas provisionados. O relatório traz o **custo estimado por mil decisões** em cada cenário (tarefa 70), calculado a partir do preço público do shape e da vazão medida.
|
||||
|
||||
## Q14 — Cenário de GPU
|
||||
|
||||
- Todas as medições de GPU usam o mesmo shape e a mesma região: `VM.GPU.A10.1` em **sa-saopaulo-1**, com execuções em fila (`infra/ds/gpu_queue.py`).
|
||||
- Pesos e ambientes são replicados do lado do servidor para `dmb-artefatos-gru` (`infra/oci/replicate.py`); os resultados ficam em `dmb-resultados-gru`.
|
||||
- Os jobs usam a rede gerenciada do serviço, e o isolamento é feito no processo (`DMB_ISOLATION=app`): variáveis offline, bloqueio de sockets e autoteste (`process_guard`) registrado em cada resultado.
|
||||
- As medições em CPU usam a sub-rede privada em us-chicago-1. CPU e GPU são reportadas em separado.
|
||||
@@ -1,182 +0,0 @@
|
||||
# 04 — Decisões de projeto (ex-questões em aberto)
|
||||
|
||||
Todas as questões foram resolvidas em 2026-10-07. O critério foi preferir a **abordagem automatizada e verificável**: regras determinísticas em código, gates que fazem o pipeline falhar, valores calculados a partir de medições e parâmetros pré-registrados em [config/benchmark.yaml](../config/benchmark.yaml). Ninguém precisa decidir nada caso a caso durante a execução. Os valores numéricos pré-registrados ficam congelados com hash no marco da tarefa 62.
|
||||
|
||||
## Resumo
|
||||
|
||||
| # | Questão | Decisão | Mecanismo automatizado |
|
||||
|---|---------|---------|------------------------|
|
||||
| Q1 | Contrato JEV | Esquema público do `POST /v1/systemone` (`state` + `questions` tipadas → `answers` + `usage`), congelado como `jev-compat-v1` | JSON Schema em `contracts/jev/schemas/` e testes de conformidade |
|
||||
| Q2 | 20 opções incluem o fallback? | Sim, e o limite comum cai para **16**: até 15 opções de domínio + `sem_correspondencia` (revisado: o SemIf aceita só 16) | Validação de esquema (`maxProperties: 16`) e lint do dataset |
|
||||
| Q3 | NAT × isolamento | Desassociar a rota NAT logo após a tarefa 16 | Gate de egresso: o pipeline só segue se a sonda de saída **falhar** |
|
||||
| Q4 | Interfaces e licenças | Interfaces mapeadas por candidato (abaixo). Só licenças da allowlist | O script de aquisição lê a licença nos metadados do Hub e falha fora da allowlist |
|
||||
| Q5 | Critérios de sucesso | SLO de latência fixo + não inferioridade relativa ao baseline | Avaliação automática dos critérios em `config/benchmark.yaml` |
|
||||
| Q6 | Lote de produção | Escolhido por candidato: o maior lote com p95 dentro do SLO | Varredura de lotes no piloto |
|
||||
| Q7 | Fonte dos dados | Reais anonimizados + sintéticos, com origem marcada | Geração sintética por template e LLM, anonimização automática, deduplicação |
|
||||
| Q8 | Turnos no `state` | O maior N ∈ {0,1,2,4} que cabe no orçamento comum de tokens | Cálculo sobre a partição de desenvolvimento |
|
||||
| Q9 | Baseline LLM | Incluído, fora do ranking principal | Adaptador JEV sobre o LLM Gateway |
|
||||
| Q10 | Calibração | Temperature scaling por candidato × tarefa, só na partição de calibração | Ajuste por NLL e congelamento com hash |
|
||||
| Q11 | Piloto em CPU | Mantido, com timeout. Inviabilidade declarada automaticamente | Regra p95 > timeout → "inviável em CPU" |
|
||||
| Q12 | Tamanho do teste | ≥ 1.300 decisões D1 no teste final (análise de poder) | `analysis/sample_size.py`, com recálculo do MDE |
|
||||
| Q14 | GPU | A10.1 em **sa-saopaulo-1**, um job por vez (limite da região). Sem VCN disponível lá: rede gerenciada + isolamento no processo (`DMB_ISOLATION=app`) | `infra/ds/gpu_queue.py`, `infra/oci/replicate.py`, autoteste `dmb.offline.self_test` |
|
||||
| Q15 | Dados e anotação | 100% sintéticos; anotação por três modelos generativos em rodízio | `datagen/` (ver [02 §4](02-desenho-experimental.md#4-dados)) |
|
||||
| Q13 | Centro de custo e orçamento | **Orçamento em aberto**: o uso está coberto por cota interna de engenharia. A estimativa de custo continua no relatório | A estimativa é calculada por script (tarefa 70) |
|
||||
|
||||
---
|
||||
|
||||
## Q14 — GPU em São Paulo (revisão de 2026-10-07)
|
||||
|
||||
- Em us-chicago-1 não há capacidade de A10, e A100 e V100 têm limite zero. Os jobs de GPU rodam em **sa-saopaulo-1, `VM.GPU.A10.1`**, com limite de **uma GPU por vez** (fila em `infra/ds/gpu_queue.py`).
|
||||
- Pesos e ambientes são replicados do lado do servidor para `dmb-artefatos-gru` (`infra/oci/replicate.py`); os resultados vão para `dmb-resultados-gru`.
|
||||
- O limite de VCNs da região está esgotado. Os jobs de GPU usam **rede gerenciada**, e o isolamento passa a valer no processo: variáveis offline, bloqueio de sockets fora do loopback e um **autoteste a cada execução** (`process_guard`). O gate de egresso registra que há saída de rede, mas não aborta (`DMB_ISOLATION=app`). É um desvio documentado em relação à tarefa 17. Se uma VCN for liberada em São Paulo, os jobs voltam para uma sub-rede privada.
|
||||
- Todas as medições de GPU usam o mesmo shape e a mesma região. As de CPU continuam em us-chicago-1.
|
||||
|
||||
## Q15 — Dados sintéticos e anotação por LLMs (revisão de 2026-10-07)
|
||||
|
||||
- Substitui Q7: os dados são 100% sintéticos, incluindo fora de escopo e enunciados emocionais. Não há dados reais neste ciclo.
|
||||
- A anotação humana (tarefas 30–31) é substituída por três modelos generativos de fornecedores distintos, em rodízio de papéis. O gerador nunca é um dos anotadores.
|
||||
- Limitação para o relatório: os rótulos de referência refletem o consenso de LLMs, não de pessoas. A concordância entre anotadores (kappa por par) e com o rótulo de geração é reportada.
|
||||
|
||||
## Q1 — Contrato JEV `jev-compat-v1`
|
||||
|
||||
Base: a forma pública do `POST /v1/systemone`, a mesma reproduzida pelas implementações abertas (OpenJev/SemIf).
|
||||
|
||||
**Requisição**
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "<model_id>",
|
||||
"state": "<texto> | <objeto JSON>",
|
||||
"questions": {
|
||||
"<nome>": {"type": "choice", "instructions": "...", "criteria": {"<opcao>": "<descrição>"}},
|
||||
"<nome>": {"type": "noul", "instructions": "...", "criteria": {"true": "...", "false": "..."}},
|
||||
"<nome>": {"type": "score", "instructions": "...", "criteria": ["<nível 0>", "<nível 1>", "..."]}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Resposta**
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "<model_id>",
|
||||
"answers": {
|
||||
"<nome>": {"type": "choice", "choice": "<opcao>", "confidence": 0.0, "probabilities": {"<opcao>": 0.0}},
|
||||
"<nome>": {"type": "noul", "noul": 0.0},
|
||||
"<nome>": {"type": "score", "score": 0.0, "confidence": 0.0,
|
||||
"probabilities": {"0": 0.0}, "legend": {"0": "<nível 0>"}}
|
||||
},
|
||||
"usage": {"input_tokens": 0, "output_tokens": 0}
|
||||
}
|
||||
```
|
||||
|
||||
**Erros:** `422` para requisição malformada, modelo desconhecido ou estado acima do contexto. `401` para autenticação, que não se aplica aos adaptadores locais. Comportamentos padronizados do benchmark (tarefa 40):
|
||||
|
||||
| Situação | Código | Contabilização |
|
||||
|----------|--------|----------------|
|
||||
| Requisição inválida | `422` | Erro do harness: falha o job |
|
||||
| Estado acima do contexto, depois do truncamento comum | `422` | Erro de decisão (`error_type=context`) |
|
||||
| Timeout (`timeout_ms` do cenário) | `504` | Erro de decisão (`error_type=timeout`) |
|
||||
| Opção devolvida fora de `criteria` | `502` | Erro de decisão (`error_type=unknown_option`) |
|
||||
| Modelo indisponível | `503` | Erro de execução: refaz até 3 vezes e depois falha o job |
|
||||
|
||||
**Mapeamento para os tipos internos (tarefas 38–39)**
|
||||
|
||||
- `choice` → D1, D2 e D5. A predição é `choice`; a distribuição é `probabilities`.
|
||||
- `noul` → D3 e as perguntas por intenção de D4. P(sim) = `noul`.
|
||||
- `score` → reservado. Não é usado no primeiro ciclo.
|
||||
- **`confidence` não é calibrada** em nenhuma implementação. Por isso, todas as métricas de calibração e abstenção usam `max(probabilities)` (ou `max(noul, 1−noul)`) **depois** da calibração da tarefa 61. A `confidence` nativa é só registrada.
|
||||
- Quando um candidato não expõe uma distribuição normalizada, o adaptador aplica softmax sobre os escores das opções e marca `metadata.probabilities_source = "normalized_scores"`.
|
||||
- `output_tokens` deve ser sempre 0, porque não há geração. Um valor diferente de 0 reprova o teste de conformidade.
|
||||
|
||||
## Q2 — Limite de opções
|
||||
|
||||
O limite inclui `sem_correspondencia`. **Revisão (2026-10-07):** o código do SemIf (`semif_phase1.core.LETTERS = "A…P"`) aceita no máximo 16 opções. O Laya recomenda até ~20 e o Rizzo aceita 26. O limite comum passa a ser **16: até 15 opções de domínio + 1**. O dataset é validado por esquema. Um exemplo com mais de 16 opções reprova o lint e impede o congelamento (tarefa 35).
|
||||
|
||||
## Q3 — NAT e isolamento de rede
|
||||
|
||||
Sequência automatizada, em que cada passo falha o pipeline se não for cumprido:
|
||||
|
||||
1. **9 → 16:** a rota NAT existe só durante a aquisição.
|
||||
2. **Fim da tarefa 16:** `infra/oci/nat.py off` remove a rota e o egresso `0.0.0.0/0` e bloqueia o NAT. O NAT só é criado ou ativado com `nat.py on`, quando for necessário.
|
||||
3. **Gate de egresso (tarefas 17 e 19):** um job de sonda tenta resolver e abrir conexão com hosts públicos (Hugging Face Hub, PyPI). O gate passa só se **todas** as tentativas falharem e se o acesso ao Object Storage pelo Service Gateway funcionar.
|
||||
4. **Toda execução de benchmark** roda o gate antes da inferência e grava o resultado no manifesto da execução.
|
||||
5. **Tarefa 78:** o NAT Gateway é destruído, e o gate confirma que nenhum recurso do compartment tem rota de saída.
|
||||
|
||||
## Q4 — Interfaces e licenças dos candidatos
|
||||
|
||||
| Candidato | Licença | Interface usada pelo adaptador | Contexto declarado | Observação |
|
||||
|-----------|---------|--------------------------------|--------------------|------------|
|
||||
| Laya multilingual | Apache-2.0 | `laya.load(...).predict(state, questions)`, nativamente no formato JEV | 1.024 tokens (default), até 8.192 | Recomenda até ~20 opções. Vem descalibrado: a calibração da tarefa 61 é obrigatória |
|
||||
| SemIf + Qwen3.5-4B | MIT (SemIf); Qwen: verificar no Hub | Modo `direct`: um forward pass lê os logits das opções declaradas | ~8k tokens | Revisão do Qwen fixada no manifesto |
|
||||
| GLiNER2.5-multi-Decide | Apache-2.0 | `classify_text(text, {"<q>": {"labels": {opcao: descrição}}})` | Não declarado: medir | Não tem `noul`/`score` nativos. `noul` vira o rótulo binário `{true,false}`. As probabilidades são normalizadas a partir dos escores |
|
||||
| Rizzo Flow 4B | Apache-2.0 (base); dados de treino com componentes CC-BY-SA/CC-BY | Prompt `spark-decisions-v3`, com leitura dos logits das letras de resposta | 2.048 tokens | **O formato de prompt é obrigatório**: o adaptador monta `spark-decisions-v3` a partir da serialização comum. Documentação só em inglês: o risco de idioma vai para o relatório |
|
||||
|
||||
Regras automatizadas:
|
||||
- A **allowlist de licenças** (`Apache-2.0`, `MIT`) fica em `config/benchmark.yaml`. O script da tarefa 13 lê `cardData.license` de cada repositório no Hub. Licença ausente ou fora da lista falha a aquisição, até haver aprovação explícita registrada no manifesto.
|
||||
- O **contexto efetivo** de cada candidato é medido por código: o mínimo entre `tokenizer.model_max_length`, `config.max_position_embeddings` e o limite declarado no adaptador. O resultado vai para o manifesto. Esse valor alimenta Q8.
|
||||
|
||||
## Q5 — Critérios de sucesso pré-registrados
|
||||
|
||||
Avaliados automaticamente pelo `analysis/` a partir de `config/benchmark.yaml`:
|
||||
|
||||
- **Desempenho (H3):** em GPU com lote 1, p95 ≤ **150 ms por decisão** e ≤ **300 ms por turno**. Em CPU os valores são só reportados.
|
||||
- **Acurácia (H1):** em D1 e D2, **não inferioridade em relação ao baseline LLM** com margem de **3 p.p.**: o limite inferior do IC 95% (bootstrap pareado por conversa) da diferença precisa ser > −3 p.p. Sem baseline disponível, vale o critério absoluto: macro F1 ≥ 0,80.
|
||||
- **Risco (H2):** no limiar escolhido na calibração, a taxa de **rota errada com confiança alta** precisa ser ≤ **2%**, com cobertura ≥ **80%**.
|
||||
|
||||
O critério relativo ao baseline dispensa um limiar absoluto arbitrário de acurácia, que dependeria da dificuldade do dataset.
|
||||
|
||||
## Q6 — Lote de produção
|
||||
|
||||
O piloto em GPU (tarefa 56) varre os lotes {1, 2, 4, 8, 16, 32}. O lote de produção de cada candidato é **o maior lote com p95 por decisão ≤ SLO**. O valor escolhido entra no manifesto e é congelado na tarefa 62. Lote 1 continua sendo o cenário principal. O lote de produção serve para reportar vazão e custo.
|
||||
|
||||
## Q7 — Fonte e preparação dos dados
|
||||
|
||||
- **Reais:** enunciados de conversas anonimizadas, se houver. **Sintéticos:** gerados a partir da taxonomia e dos templates adversariais (tarefa 27), com um LLM via gateway que **não** seja nenhum dos candidatos nem o baseline.
|
||||
- **Anonimização automática (tarefa 25):** regras para CPF, CNPJ, telefone, e-mail, CEP, números de cartão e protocolo, além de NER de pessoas e lugares, substituindo os achados por marcadores tipados (`<CPF>`, `<NOME>`). Uma amostra aleatória de 5% passa por verificação humana. Qualquer vazamento encontrado obriga a reprocessar o lote inteiro.
|
||||
- **Deduplicação:** MinHash/LSH para quase-duplicatas, dentro de cada fonte e entre fontes. Antes da partição, duplicatas recebem o mesmo `group_id`.
|
||||
- O campo `metadata.source` ∈ {`real`, `synthetic`} existe em todos os exemplos. As métricas saem por fonte e no agregado.
|
||||
- A anotação humana (tarefas 30–31) se mantém para todos os exemplos, inclusive os sintéticos. O rótulo de geração serve só como sugestão e é escondido dos anotadores.
|
||||
|
||||
## Q8 — Turnos no `state` e orçamento de tokens
|
||||
|
||||
1. **Orçamento comum** = o menor contexto efetivo medido entre os candidatos (Q4) − a reserva para a pergunta e as opções do maior exemplo.
|
||||
2. **N** = o maior valor em {0, 1, 2, 4} em que ≥ 95% dos exemplos de desenvolvimento cabem no orçamento sem truncar o enunciado atual. A contagem usa o tokenizador **mais verboso** entre os candidatos.
|
||||
3. A regra de truncamento (tarefa 53) remove primeiro os turnos mais antigos e nunca o enunciado atual. Se o enunciado sozinho estoura o orçamento, ele é truncado no fim e o exemplo recebe `truncated=true`.
|
||||
4. Uma ablação com N ∈ {0, 1, 2, 4} roda **só no desenvolvimento** e serve só para o relatório. Ela não altera a configuração congelada.
|
||||
|
||||
## Q9 — Baseline LLM
|
||||
|
||||
- Um LLM generalista disponível via LLM Gateway ou serviço gerenciado na OCI, com a versão fixada no manifesto.
|
||||
- Adaptador JEV próprio com saída restrita às opções. Quando houver logprobs, as probabilidades vêm deles. Quando não houver, a distribuição é one-hot com `probabilities_source="none"`, e o baseline fica fora das métricas de calibração.
|
||||
- Tem a mesma serialização, o mesmo truncamento e os mesmos dados. Fica **fora do ranking principal** e serve de referência para H1 e para o custo.
|
||||
- Por usar rede, é a **única exceção** ao isolamento. Roda num job separado, com egresso só para o endpoint do gateway, sem acesso aos artefatos dos candidatos.
|
||||
|
||||
## Q10 — Calibração
|
||||
|
||||
- **Temperature scaling** por candidato × tarefa de decisão: um único parâmetro, ajustado por minimização de NLL na partição de calibração. Para `noul`, o mesmo método é aplicado ao logit de P(sim).
|
||||
- O calibrador só é aceito se reduzir o ECE na própria partição de calibração (validação cruzada em 5 dobras por `group_id`). Se não reduzir, fica a identidade (T = 1).
|
||||
- Em seguida, os limiares de abstenção (tarefa 60) são escolhidos sobre as probabilidades calibradas, pelo critério de Q5: maximizar a cobertura com taxa de rota errada com confiança alta ≤ 2%.
|
||||
- Os parâmetros T e os limiares são versionados e congelados com hash na tarefa 62.
|
||||
|
||||
## Q11 — Piloto em CPU
|
||||
|
||||
- Timeout de **10 s por decisão** em CPU.
|
||||
- Regra automática: candidato com p95 > timeout ou com taxa de timeout > 5% no piloto recebe o status `inviavel_cpu` no manifesto e é excluído das rodadas finais em CPU. Os resultados em CPU dos demais são reportados como viabilidade, não como ranking.
|
||||
|
||||
## Q12 — Tamanho do conjunto de teste final
|
||||
|
||||
Análise de poder para a comparação pareada (teste de McNemar) entre dois candidatos em D1:
|
||||
|
||||
- Diferença mínima detectável d = 3 p.p., α = 0,05 (bilateral), poder = 0,80, proporção de discordância esperada p_d = 0,15:
|
||||
n ≈ (z₀.₉₇₅·√p_d + z₀.₈·√(p_d − d²))² / d² ≈ **1.300 decisões**.
|
||||
- Correção de agrupamento: n_final = n × (1 + (m − 1)·ICC), em que m é o número médio de turnos por conversa e o ICC é estimado na partição de desenvolvimento. Antes de medir, usar ICC = 0,05.
|
||||
- `analysis/sample_size.py` (a implementar) recalcula n com os valores observados. Se o teste congelado ficar abaixo do n necessário, o relatório troca a afirmação de significância pelo **MDE efetivo** do conjunto disponível.
|
||||
- D2–D4 usam o que estiver disponível, com IC por bootstrap por conversa.
|
||||
|
||||
## Q13 — Centro de custo e orçamento
|
||||
|
||||
> **Revisão (2026-10-07):** o orçamento e os alertas (tarefas 3–4) ficam **em aberto**, porque o uso está coberto por cota interna de engenharia. A tag `centro_custo` não é aplicada por enquanto. O texto abaixo vale só se for necessário criar um orçamento depois.
|
||||
|
||||
- `centro_custo` é variável Terraform **obrigatória e sem default**. O `terraform plan` falha enquanto ela não for informada, o que impede criar recursos sem a tag.
|
||||
- O valor do orçamento é calculado pelo script de estimativa, que multiplica as horas previstas por *shape* (piloto, dev, calibração, teste e repetições) pelo preço público da OCI e aplica um fator de contingência de 1,3. O resultado vai para `infra/oci/` como `budget_amount`. Os alertas de 50%, 80% e 100% (tarefa 4) são derivados desse valor.
|
||||
@@ -1,87 +0,0 @@
|
||||
# 05 — Ambiente de execução
|
||||
|
||||
A máquina local é **só IDE e repositório**. Todo script de benchmark roda na OCI (região `us-chicago-1`, compartment `decision-models`), no OCI Data Science.
|
||||
|
||||
## Componentes
|
||||
|
||||
| Componente | Rede | Uso |
|
||||
|------------|------|-----|
|
||||
| **Jobs de build** (`infra/ds/build_env.sh`) | Rede gerenciada do serviço, com internet | Criam o ambiente Python de cada candidato com as versões fixadas em `env/requirements/` e o publicam em `dmb-artefatos/envs/<candidato>/` (pacote + `latest.json` com SHA-256 + manifesto de versões) |
|
||||
| **Jobs de benchmark** (`infra/ds/run_job.sh` → `harness/job.py`) | Sub-rede privada `dmb-subnet-jobs`, **sem internet**, só Service Gateway | Gate de egresso, download dos pesos e do ambiente do bucket (SHA-256 conferido), inferência com bloqueio de rede no processo e envio dos resultados para `dmb-resultados/runs/<run_id>/` |
|
||||
| **Notebook `dmb-notebook-cpu`** | Rede gerenciada | Análise interativa dos resultados. Um notebook de GPU é criado só quando houver trabalho interativo em GPU |
|
||||
| Máquina local | — | Edição, testes unitários rápidos e disparo dos jobs (`infra/ds/jobs.py`) |
|
||||
|
||||
O código vai para cada job como artefato zip, gerado na hora a partir do repositório local, sem `.secrets/`, `models/` e venvs. Dentro do job, o processo pai usa o Python base do serviço (3.11.9, com SDK OCI e resource principal). A inferência roda no Python do ambiente do candidato.
|
||||
|
||||
## Comandos
|
||||
|
||||
```bash
|
||||
# job genérico (CPU flex ou GPU), com espera e logs
|
||||
PYTHONPATH=infra/oci .venv/bin/python infra/ds/jobs.py run --name <nome> --entrypoint <script> \
|
||||
[--shape VM.Standard.E4.Flex --ocpus 4 --memory 32 | --shape VM.GPU.A10.1] [--private] \
|
||||
[--env DMB_CANDIDATE=laya DMB_MODE=sanity DMB_DEVICE=auto] [--wait]
|
||||
|
||||
# build do ambiente de um candidato
|
||||
... jobs.py run --name build-laya --entrypoint infra/ds/build_env.sh --ocpus 4 --memory 32 --env DMB_CANDIDATE=laya
|
||||
|
||||
# sanidade de um candidato na sub-rede privada (CPU ou GPU)
|
||||
... jobs.py run --name sanity-laya --entrypoint infra/ds/run_job.sh --private \
|
||||
--env DMB_CANDIDATE=laya DMB_MODE=sanity --wait
|
||||
... jobs.py run --name sanity-laya-gpu --entrypoint infra/ds/run_job.sh --private --shape VM.GPU.A10.1 \
|
||||
--env DMB_CANDIDATE=laya DMB_MODE=sanity --wait
|
||||
```
|
||||
|
||||
## GPU (sa-saopaulo-1)
|
||||
|
||||
| Item | Valor |
|
||||
|------|-------|
|
||||
| Shape | `VM.GPU.A10.1`, **uma GPU por vez** (limite da região) — `infra/ds/gpu_queue.py` enfileira |
|
||||
| Dados | `dmb-artefatos-gru` (réplica server-side com `infra/oci/replicate.py`), resultados em `dmb-resultados-gru` |
|
||||
| Rede | Gerenciada (limite de VCNs da região esgotado). Isolamento no processo + autoteste `process_guard` (`DMB_ISOLATION=app`) |
|
||||
| Rizzo Flow em CUDA | O binário oficial do llama.cpp exige GLIBC 2.38; a imagem dos jobs tem 2.34. `infra/ds/build_llama_cuda.sh` compila o mesmo commit (`161755f`) com CUDA 12.8, sysroot glibc 2.17 e libstdc++ estática (maior GLIBC exigida: 2.27), publicado em `runtimes/rizzo_flow/cuda-latest.json` |
|
||||
|
||||
```bash
|
||||
PYTHONPATH=infra/oci:infra/ds .venv/bin/python infra/ds/gpu_queue.py sanity laya gliner semif rizzo_flow
|
||||
```
|
||||
|
||||
## Dados sintéticos (na VM)
|
||||
|
||||
```bash
|
||||
export DMB_OCI_AUTH=instance_principal DMB_COMPARTMENT_OCID=<ocid do compartment>
|
||||
python -m datagen.generate --out /data/runs/datagen-v1 # OCI Generative AI (us-chicago-1)
|
||||
python -m datagen.prepare --run /data/runs/datagen-v1
|
||||
python -m datagen.annotate --run /data/runs/datagen-v1
|
||||
python -m datagen.build --run /data/runs/datagen-v1 --freeze
|
||||
```
|
||||
|
||||
## Configuração local sensível
|
||||
|
||||
Fora do Git, em `.secrets/`: credenciais OCI, chaves SSH, o catálogo de artefatos com OCIDs (`artefatos-OCI.md`) e `oci-settings.json` (caminho do compartimento pai e CIDR autorizado para SSH; variáveis `DMB_PARENT_COMPARTMENT` e `DMB_SSH_CIDR` têm precedência). `tests/test_sensitive.py` falha se OCIDs, chaves, IPs públicos ou valores de `.secrets/oci-settings.json` aparecerem em arquivos versionados.
|
||||
|
||||
## VM de trabalho
|
||||
|
||||
| Item | Valor |
|
||||
|------|-------|
|
||||
| Instância | `instance-20261007-1206`: `VM.Standard.E6.Flex`, 8 OCPU (16 vCPU), 88 GB, Oracle Linux 9, sub-rede `dmb-subnet-dev` (SSH só do IP autorizado) |
|
||||
| Disco | Boot volume de 200 GB (raiz `/`) + block volume `dmb-compute-vol` de 1 TB (iSCSI, XFS) montado em **`/data`**: `models/`, `envs/`, `runs/`, `repo/` |
|
||||
| Acesso à OCI | Instance principal (política `dmb-workloads`), sem chave de API na VM |
|
||||
| Preparação | `infra/vm/bootstrap.sh` (Python 3.12, git, rsync, SDK OCI em `~/.venv-oci`) |
|
||||
|
||||
A VM roda o trabalho de CPU que não precisa do isolamento de rede dos jobs: preparação de dados, análise e orquestração. Do lado local:
|
||||
|
||||
```bash
|
||||
infra/vm/remote.sh sync # envia o repositório para /data/repo (sem .secrets, models, venvs)
|
||||
infra/vm/remote.sh run '<comando>' # sincroniza e executa na VM
|
||||
infra/vm/remote.sh ssh # shell na VM
|
||||
```
|
||||
|
||||
A VM foi criada pelo console: o lançamento de instâncias pela API, com a chave do usuário, é negado neste compartment (`404` em `LaunchInstance`). Por isso `infra/oci/provision.py` só registra a VM e seus volumes no catálogo, sem tentar criá-la. O `env/Dockerfile` (uma imagem por candidato) fica como alternativa caso se queira contêineres próprios (BYOC/OCIR).
|
||||
|
||||
## Divisão do trabalho
|
||||
|
||||
| Trabalho | Onde |
|
||||
|----------|------|
|
||||
| Execuções que valem como medição (piloto, dev, calibração, teste final) | Jobs do Data Science na sub-rede privada, em CPU (`VM.Standard.E4.Flex`) ou GPU (`VM.GPU.A10.1`) |
|
||||
| Builds de ambiente | Jobs com rede gerenciada |
|
||||
| Preparação de dados, análise, relatórios | VM (`/data`) ou notebook |
|
||||
| Edição e disparo | Máquina local |
|
||||
77
docs/05-cenario-de-execucao.md
Normal file
77
docs/05-cenario-de-execucao.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# 05 — Cenário de execução
|
||||
|
||||
O benchmark roda inteiro na Oracle Cloud Infrastructure (OCI). A máquina local serve só para editar o código, versionar e disparar as execuções.
|
||||
|
||||
## Visão geral
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
HF[Hugging Face Hub<br/>e repositórios-fonte] -->|aquisição com SHA-256| ART[(Object Storage<br/>artefatos)]
|
||||
ART --> BUILD[Jobs de build<br/>ambientes por candidato]
|
||||
BUILD --> ART
|
||||
GENAI[OCI Generative AI] --> DATA[Pipeline de dados<br/>VM de trabalho]
|
||||
DATA --> ENT[(Object Storage<br/>partições congeladas)]
|
||||
ART --> CPU[Jobs de medição CPU<br/>sub-rede privada]
|
||||
ART --> GPU[Jobs de medição GPU<br/>A10]
|
||||
ENT --> CPU
|
||||
ENT --> GPU
|
||||
CPU --> RES[(Object Storage<br/>resultados)]
|
||||
GPU --> RES
|
||||
RES --> REP[reports/ no repositório]
|
||||
```
|
||||
|
||||
| Etapa | Onde roda | O que produz |
|
||||
|-------|-----------|--------------|
|
||||
| Aquisição de artefatos | Fora da rede dos jobs (`env/acquire.py`) | Pesos, tokenizadores, código-fonte e runtimes nas revisões fixadas, no bucket `dmb-artefatos`, com SHA-256 em `env/artifacts-manifest.json` |
|
||||
| Ambientes dos candidatos | Jobs de build do OCI Data Science, com acesso à internet (`infra/ds/build_env.sh`) | Um ambiente Python por candidato (versões de `env/requirements/`), publicado em `dmb-artefatos/envs/<candidato>/` com manifesto de versões |
|
||||
| Dados sintéticos | VM de trabalho, chamando o OCI Generative AI (`datagen/`) | Partições congeladas em `data/splits/` e em `dmb-entrada` / `dmb-logs-imutaveis` |
|
||||
| Medições em CPU | Jobs do OCI Data Science em **us-chicago-1**, `VM.Standard.E4.Flex` com 8 OCPU e 64 GB, em sub-rede privada sem saída para a internet | Predições e resumos em `dmb-resultados` |
|
||||
| Medições em GPU | Jobs do OCI Data Science em **sa-saopaulo-1**, `VM.GPU.A10.1`, um por vez | Predições e resumos em `dmb-resultados-gru` |
|
||||
| Análise e relatórios | VM de trabalho ou notebook do Data Science | `reports/` no repositório |
|
||||
|
||||
## Isolamento durante as medições
|
||||
|
||||
Toda inferência roda **sem acesso a fontes externas**:
|
||||
|
||||
- **Artefatos verificados.** O job baixa do bucket só o que está no manifesto e confere o SHA-256 de cada arquivo e do ambiente do candidato.
|
||||
- **Bloqueio no processo** (`dmb/offline.py`). Variáveis offline do Hugging Face e bloqueio de conexões fora do loopback. Um autoteste a cada execução (`process_guard`) prova que o bloqueio está ativo.
|
||||
- **Bloqueio na rede (CPU).** A sub-rede privada só alcança os serviços OCI pela Service Gateway. O gate de egresso de `harness/job.py` confirma que nenhum host público responde.
|
||||
- **GPU.** Os jobs usam a rede gerenciada do serviço. O isolamento vem do bloqueio no processo e do autoteste (`DMB_ISOLATION=app`). Pesos e ambientes ficam em `dmb-artefatos-gru`, uma réplica server-side do bucket principal (`infra/oci/replicate.py`).
|
||||
|
||||
## Fluxo de uma execução
|
||||
|
||||
1. `infra/ds/jobs.py` (CPU) ou `infra/ds/gpu_queue.py` (GPU) empacota o repositório num zip, sem `.secrets/` e sem `models/`, e cria o job.
|
||||
2. `harness/job.py` (processo pai, Python base do serviço): gate de egresso, download e verificação dos pesos e do ambiente.
|
||||
3. O processo filho, no ambiente do candidato, roda `harness.sanity` ou `harness.evaluate` com o bloqueio de rede ativo.
|
||||
4. O processo pai envia predições, resumos e logs para o bucket de resultados.
|
||||
5. `infra/ds/collect.py` traz os resumos para `reports/<etapa>/`, com uma tabela comparativa.
|
||||
|
||||
## Runtimes por candidato
|
||||
|
||||
| Candidato | Runtime |
|
||||
|-----------|---------|
|
||||
| Laya, GLiNER | PyTorch 2.10 (wheels `cu128`), transformers 4.57 |
|
||||
| SemIf | PyTorch 2.10 (wheels `cu128`), transformers 5.17 (exigência do SemIf) |
|
||||
| Rizzo Flow | llama.cpp b11081 (commit fixado pelo Rizzo). CPU: binário oficial. GPU: o mesmo commit compilado para a imagem dos jobs (`infra/ds/build_llama_cuda.sh`, CUDA 12.8) |
|
||||
|
||||
## Comandos principais
|
||||
|
||||
```bash
|
||||
# medição em CPU (sub-rede privada)
|
||||
PYTHONPATH=infra/oci .venv/bin/python infra/ds/jobs.py run --name <nome> --entrypoint infra/ds/run_job.sh \
|
||||
--private --ocpus 8 --memory 64 --env DMB_CANDIDATE=<candidato> DMB_MODE=evaluate DMB_SPLIT=dev [--wait]
|
||||
|
||||
# medição em GPU (fila em sa-saopaulo-1)
|
||||
PYTHONPATH=infra/oci:infra/ds .venv/bin/python infra/ds/gpu_queue.py evaluate laya gliner semif rizzo_flow \
|
||||
--env DMB_SPLIT=dev
|
||||
|
||||
# resultados para o repositório
|
||||
PYTHONPATH=infra/oci .venv/bin/python infra/ds/collect.py --split dev --dest reports/<etapa>
|
||||
|
||||
# pipeline de dados (na VM de trabalho, a partir da máquina local)
|
||||
infra/vm/remote.sh run 'python -m datagen.generate --out /data/runs/datagen-v1' # depois prepare, annotate, build
|
||||
```
|
||||
|
||||
## Configuração local sensível
|
||||
|
||||
Ficam fora do Git, em `.secrets/`: credenciais OCI e do GitHub, chaves SSH, o catálogo de recursos com OCIDs e `oci-settings.json` (compartimento pai e CIDR autorizado para SSH). O teste `tests/test_sensitive.py` falha se OCIDs, chaves, IPs públicos ou valores de `.secrets/` aparecerem em arquivos versionados.
|
||||
Reference in New Issue
Block a user