<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="pt-BR">
    <title>Brenner Cruvinel - nest</title>
    <subtitle>AI researcher and product designer building at the edge of computer science and mental health.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://brennercruvinel.blog/tags/nest/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://brennercruvinel.blog"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-09-12T00:00:00+00:00</updated>
    <id>https://brennercruvinel.blog/tags/nest/atom.xml</id>
    <entry xml:lang="pt-BR">
        <title>Validando no Disco o Benchmark Que Escrevi de Madrugada</title>
        <published>2026-09-12T00:00:00+00:00</published>
        <updated>2026-09-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            Brenner Cruvinel
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://brennercruvinel.blog/blog/validando-no-disco-o-benchmark-que-escrevi-de-madrugada/"/>
        <id>https://brennercruvinel.blog/blog/validando-no-disco-o-benchmark-que-escrevi-de-madrugada/</id>
        
        <content type="html" xml:base="https://brennercruvinel.blog/blog/validando-no-disco-o-benchmark-que-escrevi-de-madrugada/">&lt;p&gt;abri uma sessão nova, sem contexto nenhum, e perguntei como se eu fosse alguém que acabou de clonar o repo: como usa o nest, ele tem interface? o primeiro comando que a resposta sugeriu devolveu command not found na minha própria máquina. o segundo mostrou um modelo padrão que eu tinha certeza de ter trocado semanas atrás. dois furos que nenhum teste pega, achados em cinco minutos, e foi aí que percebi que o assunto da semana não era compressão.&lt;/p&gt;
&lt;p&gt;o benchmark era só o lugar onde deu pra ver. então antes de publicar qualquer número da &lt;a href=&quot;https://brennercruvinel.blog/blog/nest-archiver-de-cartas-de-magic/&quot;&gt;série&lt;/a&gt;, rodei um dia inteiro de validate: cada número contra o arquivo em disco.&lt;/p&gt;
&lt;h2 id=&quot;o-que-estava-errado&quot;&gt;o que estava errado&lt;/h2&gt;
&lt;p&gt;a primeira coisa que não bateu nem era número. rodei um strings no arquivo de release e apareceu &quot;Spellbook MTG corpus&quot;. o arquivo se chama mtgdataset. o rename de uma semana atrás, que trocou o nome do app do felipe pelo nome genérico em todos os arquivos de texto, nunca entrou no binário. o lock ao lado do release descrevia um arquivo com o nome novo enquanto o arquivo tinha o nome velho dentro, e nenhuma ferramenta acusou, porque a validação confere o arquivo consigo mesmo. refiz os dois releases com o nome certo dentro, e os bytes da mídia saíram idênticos, o que pelo menos provou que o encode é reproduzível.&lt;/p&gt;
&lt;p&gt;os tamanhos dos três builds mais agressivos estavam em mebibyte com rótulo de gigabyte. o du fala em MiB e eu colei como GB. a diferença é de cinco por cento por linha, mas o ratio herda o erro: o build de busca que eu tinha anunciado como 7,8 vezes menor que a fonte é 7,5. o avif que eu disse ser 20% menor que o recomendado é 13%. nada muda de conclusão, e mesmo assim me deu vergonha, porque é o tipo de erro que eu apontaria num relatório dos outros.&lt;/p&gt;
&lt;p&gt;o mais estranho foi o manifest do build em avif, que registra uma fonte de 21 GB. a fonte tem 4. fiquei sem entender até dividir pelo número de cartas: dá o peso de uma imagem descomprimida, que é o que o encoder recebe no meio do caminho. o backend contou o intermediário em vez do jpeg. uma linha pra corrigir, um teste pra escrever, e até lá o ratio daquele manifest era invenção.&lt;/p&gt;
&lt;p&gt;e tem o que não dá mais pra verificar. o primeiro build completo eu apaguei quando o disco encheu, então a frase &quot;os três builds compartilham o mesmo hash&quot; hoje tem dois terços conferíveis. o experimento do capcut e a matriz de reprints não deixaram artefato, só texto. eu mesmo mandei apagar, pra liberar espaço. espaço em disco é mais barato que confiança e eu devia saber disso.&lt;/p&gt;
&lt;h2 id=&quot;as-outras-vezes-que-eu-li-o-resumo-e-nao-o-arquivo&quot;&gt;as outras vezes que eu li o resumo e não o arquivo&lt;/h2&gt;
&lt;p&gt;o resumo das 200 cartas. pedi o dataset inteiro, recebi um resumo que falava do corpus como se fosse o corpus, e era um piloto. percebi com reunião marcada. o agente não mentiu, ele resumiu o escopo sem avisar, e eu li o resumo em vez do disco. desde então toda entrega de build vem com a validação do arquivo real, e este post existe pelo mesmo motivo.&lt;/p&gt;
&lt;p&gt;os paths da minha máquina. dias depois do benchmark achei o caminho da minha home em dezenas de arquivos que iam pro github, dentro de manifest, lock, spec. o fix foi uma variável de ambiente e um script que reescreve os sidecars antes de commitar. e antes disso o agente tinha querido guardar preferências minhas num arquivo local de memória. pedi pra apagar. se o fluxo depende do que ele lembra da minha máquina, não roda no próximo repo, e não roda na máquina de quem clonar.&lt;/p&gt;
&lt;p&gt;o repo de cinco giga. em algum momento perguntei como engenheiro de ml lida com isso, porque o github não aceita. a resposta que eu adotei: nenhum byte de imagem entra no git. cada amostra é uma lista de ids com a regra que gerou ela, e quem tem o sqlite reconstrói a amostra exata. os .nest vão pro hugging face com hash do lado, e o repo guarda o lock, o manifest limpo e o hash. o relatório inteiro é gerado dos json de cada experimento, e um check no ci falha se o markdown estiver diferente do json. eu não consigo mais colar número errado num relatório sem o ci reclamar, o que é o tipo de proteção que eu precisava contra mim mesmo.&lt;/p&gt;
&lt;p&gt;os commits. o agente prefere um commit gordo no fim do dia. eu pedi dezesseis, separados, com descrição, porque a história tem que ser legível pra quem chega depois. e os planos: rejeitei três seguidos porque tratavam a comparação de modelos como rodapé de um build já decidido. a lista de modelos que hoje existe no nest só existe porque eu insisti que pesquisa vira feature, nunca rodapé.&lt;/p&gt;
&lt;p&gt;o que esses episódios têm em comum é um só: em todos eu aceitei uma camada entre mim e o arquivo. resumo, manifest, memória, commit gordo. e a camada tava errada e não avisou.&lt;/p&gt;
&lt;h2 id=&quot;o-que-eu-vou-tentar-da-proxima-vez&quot;&gt;o que eu vou tentar da próxima vez&lt;/h2&gt;
&lt;p&gt;uma coisa que me deixou mais de boa, no meio do validate, foi descobrir que os dois achados que eu mais gostei já tinham nome. a redundância entre imagens de uma coleção, que o inter explora, se chama set redundancy desde uma tese de 1996, e o truque de enfileirar as imagens como pseudo-vídeo tem uma década de literatura. e a distinção entre qualidade visual, estabilidade do vetor e utilidade da busca, que eu achei que tinha inventado, é a área que os padrões chamam de coding for machines, onde &quot;distância no espaço de features não prevê acurácia da tarefa&quot; é resultado conhecido. eu não descobri nada. confirmei duas coisas num corpus onde ninguém tinha olhado, e isso me deixa mais tranquilo pra publicar do que se fosse novidade.&lt;/p&gt;
&lt;p&gt;o arquivo de 1,3 GB já está no repo do pasta.cards esperando a tela que vai usar ele. o que falta do benchmark cabe num parágrafo: medir a busca com o wemm, buildar o corpus de reprints inteiro, decidir se o avif vira padrão. nenhuma dessas três tem data marcada.&lt;/p&gt;
&lt;p&gt;o que eu vou tentar é o mais barato de tudo: abrir uma sessão limpa como usuário antes de cada release e digitar o primeiro comando do readme. cinco minutos, e achou dois furos que uma semana de agente não achou. não sei se vou lembrar de fazer. mas agora está escrito, e eu confiro mais o que está escrito do que o que eu lembro.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="pt-BR">
        <title>Subindo o CRF Até a Busca Bugar</title>
        <published>2026-09-05T00:00:00+00:00</published>
        <updated>2026-09-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            Brenner Cruvinel
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://brennercruvinel.blog/blog/subindo-o-crf-ate-a-busca-bugar/"/>
        <id>https://brennercruvinel.blog/blog/subindo-o-crf-ate-a-busca-bugar/</id>
        
        <content type="html" xml:base="https://brennercruvinel.blog/blog/subindo-o-crf-ate-a-busca-bugar/">&lt;p&gt;a pergunta da &lt;a href=&quot;https://brennercruvinel.blog/blog/no-final-do-dia-ta-a-mesma-coisa-1-3-gb/&quot;&gt;madrugada anterior&lt;/a&gt; era simples de fazer e eu demorei pra fazer. o app tem o jpeg de cada carta em cache pra exibir. o .nest não precisa ser bonito, precisa responder qual carta é. então o crf, o ajuste de qualidade do codec, pode subir até o vetor bugar, e o vetor buga bem depois do olho. se isso fosse verdade, valia até pensar em duas camadas: um thumb dentro do nest pra busca, e a carta inteira sob demanda quando o app precisar mostrar.&lt;/p&gt;
&lt;p&gt;eu tinha um jeito de medir isso, ou achava que tinha. o gate de qualidade do nest tem dois lados: uma métrica visual, que compara o frame decodificado com a fonte, e a drift do vetor, que mede o quanto o vetor do clip se move entre a imagem original e a decodificada. a ideia do drift é que uma imagem pode parecer boa e ainda assim ter mudado de lugar no espaço de busca. eu tinha confiança nessa métrica. era a minha, eu tinha insistido nela no plano, e o motivo era bom: na dermatoscopia ela tinha pego um caso em que a imagem passava no olho e a busca por texto clínico caía pela metade.&lt;/p&gt;
&lt;h2 id=&quot;o-gate-reprovou-a-ladder-inteira&quot;&gt;o gate reprovou a ladder inteira&lt;/h2&gt;
&lt;p&gt;tirei o lado visual do gate e deixei só o drift, com o floor que o nest usa desde a dermatoscopia. mandei o crf subir numa ladder de crf 40 a 60. o gate reprovou a ladder inteira, inclusive o primeiro, e caiu no mais conservador da ladder porque não tinha pra onde ir. o drift já estava abaixo do floor no crf 40 e só piorava. e a métrica visual, que eu tinha tirado do gate mas continuava calculando, contava a mesma história por outro ângulo: a partir do crf cinquenta ela dava valor negativo em algumas cartas, coisa que eu nem sabia que ela fazia.&lt;/p&gt;
&lt;p&gt;o arquivo que o gate entregou, no crf 40, o mais baixo da ladder, ficou perto de um giga, quatro vezes menor que a fonte. já era menor que o build recomendado, e o gate tinha reprovado ele também, só não tinha pra onde cair.&lt;/p&gt;
&lt;p&gt;pela lógica do gate, nenhum desses arquivos servia. buildei um no crf cinquenta mesmo assim, sem gate, só pra ver o tamanho. meio giga. sete vezes e meia menor que a fonte, e as cartas viraram borrão, o texto de regra ilegível, a arte reconhecível só de longe. um arquivo que eu não mostraria pra ninguém.&lt;/p&gt;
&lt;p&gt;aí rodei a métrica que interessa mesmo, e que eu não tinha rodado antes por preguiça de montar: a busca por nome. cem buscas do tipo &quot;a arte da carta tal&quot;, nos cinco arquivos que eu tinha do corpus completo, do sem perda de 3,6 GB até o borrão de meio giga. a primeira rodada deu zero acerto em dois dos cinco arquivos, o sem perda e o recomendado, e por um minuto eu achei que tinha quebrado a busca. era o bench: ele casava o resultado pelo número de ordem da carta no stream, e os dois arquivos com ordenação por similaridade têm outra ordem. o nest tem um id de conteúdo por item justamente pra isso, e quando o bench passou a casar por ele os cinco arquivos voltaram a responder.&lt;/p&gt;
&lt;p&gt;e o acerto ficou dentro do ruído em todos. o de meio giga acha a carta certa tão bem quanto o de 1,3 GB, e os dois ficam dentro do ruído do sem perda.&lt;/p&gt;
&lt;h2 id=&quot;a-busca-sobrevive-a-muito-mais-compressao-do-que-o-olho&quot;&gt;a busca sobrevive a muito mais compressão do que o olho&lt;/h2&gt;
&lt;p&gt;fiquei com isso na tela um tempo. drift de cosseno mede se o vetor se moveu. busca mede se o vetor ainda acha. eu estava usando a primeira como se fosse a segunda, e ela teria vetado um arquivo que funciona. o vetor se move, sim, e continua mais perto da carta certa do que de qualquer outra. a distância que ele anda não importa se os vizinhos dele não mudam. um gate que preste pra esse uso precisa de hit@k como floor, não de cosseno.&lt;/p&gt;
&lt;p&gt;isso virou um perfil no nest, retrieval-only, que deixa escrito no manifest que o arquivo serve busca e nunca exibição. e o avif, que no benchmark tinha ficado uns 12% menor que o av1 na mesma qualidade, confirmou no corpus inteiro: 13% menor que o build recomendado. é o candidato a virar o backend padrão pra stills, e eu ainda não decidi, porque o avif custa quatro a dez vezes mais tempo de build pra embedar.&lt;/p&gt;
&lt;p&gt;tem uma terceira métrica que eu rodei junto e que não vale nada, e vale dizer por quê. a identidade: dar a própria imagem como busca e ver se ela acha ela mesma. deu cem por cento em todos os arquivos, até no borrão. parece ótimo e não diz nada, porque a busca é a imagem que gerou o frame, e o controle sem compressão também dá cem. serve só pra provar que o codec não destruiu o dado. eu deixei ela no bench pra ninguém confundir estabilidade de pipeline com utilidade, que foi exatamente a confusão que eu fiz com o drift.&lt;/p&gt;
&lt;p&gt;a métrica é fraca e eu sei. são cem buscas com um modelo que acerta pouco nesse corpus, o clip, que só olha a arte. &quot;não caiu&quot; quer dizer &quot;não consegui medir queda&quot;, não &quot;é igual&quot;. e o wemm, que lê o nome impresso na carta e acerta nove em dez, pode sofrer muito mais com o borrão do que o clip, porque o que ele lê é exatamente o que o crf destrói primeiro. não medi. é a próxima coisa, junto com o corpus de reprints inteiro, que é onde o inter tem o que prever.&lt;/p&gt;
&lt;p&gt;o que me ficou dessa semana foi menos sobre compressão e mais sobre o que eu escolho medir. eu tinha uma métrica que eu mesmo desenhei, que parecia rigorosa, e ela respondia uma pergunta que eu não estava fazendo. e antes de publicar qualquer um desses números, eu queria validar cada um contra o disco, porque relatório escrito de madrugada erra.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="pt-BR">
        <title>No Final do Dia Tá a Mesma Coisa, 1,3 GB</title>
        <published>2026-09-03T00:00:00+00:00</published>
        <updated>2026-09-03T00:00:00+00:00</updated>
        
        <author>
          <name>
            Brenner Cruvinel
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://brennercruvinel.blog/blog/no-final-do-dia-ta-a-mesma-coisa-1-3-gb/"/>
        <id>https://brennercruvinel.blog/blog/no-final-do-dia-ta-a-mesma-coisa-1-3-gb/</id>
        
        <content type="html" xml:base="https://brennercruvinel.blog/blog/no-final-do-dia-ta-a-mesma-coisa-1-3-gb/">&lt;p&gt;eu tinha certeza. ordenar as cartas por similaridade antes de encodar ia multiplicar a compressão, porque frame vizinho parecido é exatamente o que codec de vídeo gosta. tinha escrito nas apostas, tinha repetido pro agente, e o &lt;a href=&quot;https://brennercruvinel.blog/blog/encoder-ignorou-o-tune-still-desde-o-primeiro-build/&quot;&gt;benchmark&lt;/a&gt; tinha me devolvido uma linha estranha: a variante com inter ficou maior que a sem. a configuração estava errada, scene cut detection ligado, e carta diferente atrás de carta diferente é um scene cut por frame. o encoder gastava ciclo procurando uma cena que não existia. desliguei e montei um segundo corpus, quase três mil reprints reais da mesma arte, agrupadas.&lt;/p&gt;
&lt;p&gt;aí sim. com predição em grupos de dezesseis frames o arquivo ficou 29% menor que sem predição. grupos de dezesseis ganharam de um keyframe só pro stream inteiro, o que me surpreendeu, e ainda deixam qualquer carta acessível decodificando no máximo dezesseis frames. e um detalhe que eu não esperava: embaralhar as reprints em vez de agrupar custou só dois pontos. parte do ganho vem da moldura que toda carta compartilha, não da arte repetida. o codec estava aproveitando o chrome, como eu tinha desconfiado no primeiro dia, e isso valia pra qualquer ordem.&lt;/p&gt;
&lt;h2 id=&quot;buildando-o-corpus-inteiro-de-madrugada&quot;&gt;buildando o corpus inteiro de madrugada&lt;/h2&gt;
&lt;p&gt;com 29% na mão, buildei o corpus completo com ordenação por similaridade e predição ligada, e do lado o mesmo corpus sem nada disso, pra comparar. e buildei com a mídia dentro do arquivo, porque a essa hora eu já não aceitava mp4 do lado do banco. antes de chegar nisso eu abri a pasta de um build intermediário e tinha .build, .cache, .forge-state, .tmp, lock, manifest, tudo do lado de um .nest de poucos megas. pra que isso? perguntei, e a resposta honesta era que cada peça tinha motivo e nenhuma tinha que estar ali quando o build termina. a mídia dentro do arquivo era a metade da resposta, a outra era limpar a saída.&lt;/p&gt;
&lt;p&gt;o .nest é um container de seções, cada uma com seu hash. a seção nova é a mais simples de todas: uma tabela de posições e os bytes dos vídeos, crus, colados um atrás do outro. um mp4 continua mp4, nada é recodificado pra caber. abrir o arquivo não toca na mídia, o sistema só mapeia o trecho quando alguém pede uma carta, e dá pra exportar o mp4 original de volta com o hash checado antes de gravar.&lt;/p&gt;
&lt;p&gt;essa seção não entra no hash de conteúdo do nest, e isso importa mais do que parece. o hash de conteúdo é calculado sobre o que está dentro do arquivo, não sobre a embalagem, e é ele que faz uma citação do nest apontar pra um pedaço de conhecimento em vez de pra um lugar no disco. o mesmo corpus buildado com a mídia dentro e com a mídia fora dá o mesmo hash. custou três por cento: os índices, o texto das cartas e os vetores somam uns quarenta megas em cima de 1,3 GB de vídeo.&lt;/p&gt;
&lt;p&gt;os builds rodaram de madrugada, e a madrugada foi ruim. dois morreram com o disco cheio, porque o macos estava tirando snapshot de sistema em ondas, comendo e soltando giga sem aviso, e a certa altura sobraram cento e trinta megas livres. e no meio disso eu li um resumo do agente dizendo que o corpus tinha 200 cartas. eu tinha reunião com o cliente em poucas horas.&lt;/p&gt;
&lt;p&gt;&quot;sério que você usou 200 cartas de demo? tenho reunião com o cliente e não tenho o dataset ainda.&quot; mandei rodar a validação no arquivo de 1,3 GB. os 38 mil blobs bateram um a um. o corpus estava inteiro, o resumo é que mostrava o piloto, e eu tinha lido o resumo em vez do disco. depois do encoder que não tava usando o tune que o manifest dizia, era a segunda vez na semana que a coisa que eu li não era a coisa que estava lá.&lt;/p&gt;
&lt;h2 id=&quot;no-final-do-dia&quot;&gt;no final do dia&lt;/h2&gt;
&lt;p&gt;o build com ordenação ficou do lado do build simples. 1,3 GB os dois, diferença de nada. mandei pro agente às quatro da manhã: no final do dia tá a mesma coisa né, 1,3 GB.&lt;/p&gt;
&lt;p&gt;o motivo estava no manifest, dessa vez certo. o nest decide por segmento se usa predição ou não, encodando uma amostra dos dois jeitos. no corpus real, de carta única, o probe vetou a predição em todos os segmentos. não tem o que prever: a carta seguinte é outra carta, com outra arte, e o chrome compartilhado não paga o custo. o ganho de 29% só existe no corpus de reprints, e esse eu ainda não buildei inteiro.&lt;/p&gt;
&lt;p&gt;teve uma parte pior, que eu só entendi depois. numa versão anterior do probe, ele decidia só por bytes, e num build da amostra escolheu predição em todos os segmentos porque empatava em tamanho. a qualidade mediana caiu dezesseis pontos. o encoder comprime frame previsto com quantização mais grossa, e em carta única o frame previsto é um keyframe pior disfarçado. bytes iguais não são qualidade igual, e o probe agora mede os dois lados, aceita perder até dois pontos, e grava o motivo quando veta. a matriz de 29% também compara só bytes, então o número é real mas não é qualidade equivalente. deixo isso dito aqui porque eu mesmo esqueci disso por um dia.&lt;/p&gt;
&lt;h2 id=&quot;e-se-o-arquivo-nao-precisar-ser-bonito&quot;&gt;e se o arquivo não precisar ser bonito&lt;/h2&gt;
&lt;p&gt;o que eu levo da madrugada não é o 1,3 GB. é que a minha intuição sobre codec estava errada duas vezes, e que eu leio resumo quando devia ler arquivo. agora eu só ligo ordenação quando o corpus tem near-dup, e nenhum build entra sem a validação do arquivo real rodada junto.&lt;/p&gt;
&lt;p&gt;e ficou uma pergunta que me pareceu mais promissora do que qualquer ordenação. o gate de qualidade do nest trava o codec num nível conservador porque as métricas visuais nunca passam em carta pequena com letra miúda. mas o app do felipe tem o jpeg de cada carta em cache pra exibir. o .nest só precisa responder qual carta. se o arquivo não precisa ser bonito, até onde dá pra subir o ajuste antes da busca reclamar?&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="pt-BR">
        <title>O Encoder Ignorou o tune=still Desde o Primeiro Build</title>
        <published>2026-09-02T00:00:00+00:00</published>
        <updated>2026-09-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            Brenner Cruvinel
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://brennercruvinel.blog/blog/encoder-ignorou-o-tune-still-desde-o-primeiro-build/"/>
        <id>https://brennercruvinel.blog/blog/encoder-ignorou-o-tune-still-desde-o-primeiro-build/</id>
        
        <content type="html" xml:base="https://brennercruvinel.blog/blog/encoder-ignorou-o-tune-still-desde-o-primeiro-build/">&lt;p&gt;o sintoma foi um número que não batia. eu tinha uma bateria de encoders rodando em paralelo: dezenove configurações entre av1, avif, x264, x265, webp e jpeg xl com perda. até vvc entrou, mais por curiosidade do que por esperança. cada uma calibrada pra mesma qualidade visual, pra saber se o av1 do nest era o mais compacto ou só o mais próximo de casa. um dos agentes reproduziu o build do nest na mão, com os mesmos parâmetros, e o arquivo saiu com outro tamanho. mesma carta, mesmo crf, mesmo preset, bytes diferentes. eu li isso de manhã e minha primeira reação foi que o agente tinha errado o comando.&lt;/p&gt;
&lt;h2 id=&quot;o-probe-falhava-e-ninguem-avisava&quot;&gt;o probe falhava e ninguém avisava&lt;/h2&gt;
&lt;p&gt;não tinha errado. o forge pede pro encoder o tune=still, o modo de foto parada, que é o certo pra um stream onde cada frame é uma foto independente. antes de pedir, ele roda um probe pra ver se o encoder aceita esse modo. o probe rodava sem um parâmetro que o modo exige, falhava, e o código caía num modo genérico sem avisar ninguém. o manifest gravava a spec, que dizia &quot;still&quot;, não o que o encoder tinha feito. desde o &lt;a href=&quot;https://brennercruvinel.blog/blog/nest-archiver-de-cartas-de-magic/&quot;&gt;primeiro build&lt;/a&gt; eu vinha lendo um manifest que não batia com o binário, e o único lugar que registrava o modo era o lugar errado.&lt;/p&gt;
&lt;p&gt;a diferença mediu onze pontos de qualidade visual por dez por cento a mais de bytes, no mesmo crf. o build antigo era o pior ponto da curva: menor, mas feio de um jeito que não compensa. e ninguém teria achado olhando o vídeo, porque a cara do frame no player não diz qual tune foi usado. só bytes contra bytes de um encode independente. e foi por acaso: a bateria de encoders existia pra responder outra pergunta, e o encode manual do baseline era só o ponto de referência dela. se eu tivesse calibrado a bateria contra o manifest em vez de contra um encode feito do zero, o bug teria continuado invisível, e todos os números da série teriam saído dez por cento otimistas em bytes e onze pontos pessimistas em qualidade. agora o manifest grava o modo que o encoder de fato usou, e o probe passa o parâmetro certo.&lt;/p&gt;
&lt;p&gt;e mesmo depois do fix tinha uma segunda camada. o svt-av1 tem dois modos que dá pra chamar de &quot;still&quot;: um otimizado pra uma métrica de similaridade estrutural, que foi o que o probe consertada passou a escolher, e outro chamado de qualidade de imagem, que só funciona em stream todo de keyframe. o segundo rende mais um ponto de qualidade por um por cento de bytes. pouco, mas é o modo certo pra foto parada, e o build recomendado do corpus inteiro saiu com ele. o manifest agora diz qual dos dois, em número, porque &quot;still&quot; sozinho já me enganou uma vez.&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;variante nas mesmas 2048 cartas&lt;/th&gt;&lt;th&gt;tamanho&lt;/th&gt;&lt;th&gt;qualidade visual&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;jpeg xl repack, sem perda&lt;/td&gt;&lt;td&gt;1,12x menor&lt;/td&gt;&lt;td&gt;igual à fonte&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;av1 do primeiro build&lt;/td&gt;&lt;td&gt;3,3x menor&lt;/td&gt;&lt;td&gt;52&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;av1 no tune=still&lt;/td&gt;&lt;td&gt;3,0x menor&lt;/td&gt;&lt;td&gt;63&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;av1 com inter&lt;/td&gt;&lt;td&gt;2,5x menor&lt;/td&gt;&lt;td&gt;62&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;avif&lt;/td&gt;&lt;td&gt;5,5x menor&lt;/td&gt;&lt;td&gt;43&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;a linha do avif parece a vencedora e não é. está num ponto muito mais baixo da curva de qualidade, não dá pra comparar pelo mesmo número. calibrado pra qualidade igual à do av1, ele fica uns 12% menor, e isso deixou uma pergunta aberta sobre qual backend usar pra stills. e o inter, que era minha aposta favorita, ficou maior que o tune=still. isso eu explico no próximo post, porque a configuração estava errada e o problema era outro.&lt;/p&gt;
&lt;p&gt;o resto da bateria de dezenove eu não vou tabelar, mas dois achados ficaram comigo. o x264, codec de 2003, foi o mais rápido dentro da janela de qualidade por uma margem enorme, e o vvc, o codec mais novo da lista, custou cem vezes o tempo do x264 pra ficar no meio da tabela. e dois presets diferentes do svt-av1 cuspiram streams byte a byte idênticos, porque a versão que eu uso mapeia os dois pro mesmo modo interno quando o stream é todo de keyframe. mais um caso de parâmetro que eu passava e o encoder ignorava, dessa vez documentado. e o jpeg xl com perda, que eu esperava competir, só compete em qualidade alta, bem acima da janela que interessa pra tile de deck. é um codec bom pra um alvo que esse corpus não pede.&lt;/p&gt;
&lt;h2 id=&quot;o-capcut-tava-jogando-carta-fora&quot;&gt;o capcut tava jogando carta fora&lt;/h2&gt;
&lt;p&gt;eu tinha escrito nas &lt;a href=&quot;https://brennercruvinel.blog/blog/apostas-antes-de-comprimir-as-cartas/&quot;&gt;apostas&lt;/a&gt; que acelerar o vídeo cortava bytes, porque tinha feito isso no capcut e o arquivo saiu com metade do tamanho. reproduzi programaticamente: acelerar sessenta vezes, exportar a trinta frames por segundo em h.264, do jeito que o editor faz. o arquivo saiu com metade dos bytes. e com metade das cartas.&lt;/p&gt;
&lt;p&gt;o export amostra a linha do tempo acelerada e descarta o que cai entre um frame e outro. das 2048 cartas, 993 sumiram. o que sobrou foi re-encodado em h.264 em cima de av1 que já tinha perda. o capcut não comprimiu nada, jogou metade do dataset fora e chamou de otimização, e eu olhei o tamanho do arquivo e acreditei. mudar só o fps, sem acelerar, não muda um byte. é anotação de tempo no container, como o ffprobe já tinha me dito no primeiro dia e eu não tinha registrado direito.&lt;/p&gt;
&lt;p&gt;a aposta errada rendeu um bug certo. com fps diferente de um no manifest, o código que busca frame pelo índice pedia um instante que não existia, porque assumia que ordinal e timestamp eram a mesma coisa. corrigido, e testado com o fps que eu nunca deveria ter mudado. o ganho real que o capcut apontava, redundância entre frames vizinhos, existe. só que ele se mede com inter, sem perder carta, e é o assunto da madrugada seguinte.&lt;/p&gt;
&lt;h2 id=&quot;o-que-mais-o-manifest-esta-me-contando-errado&quot;&gt;o que mais o manifest está me contando errado&lt;/h2&gt;
&lt;p&gt;fechei o dia com duas coisas validadas por hash e uma dúvida maior do que quando comecei. o modo do encoder era uma constante que eu achava que estava ligada, e estava desligada desde o começo. o capcut era um número que eu achava que era compressão, e era perda de dado. os dois estavam num manifest ou numa exportação que eu tinha lido e aceitado sem checar contra nada.&lt;/p&gt;
&lt;p&gt;se o encoder não tava fazendo o que o manifest dizia, a pergunta que ficou foi o que mais eu estava lendo em resumo em vez de validar no disco. eu não sabia ainda que a resposta ia chegar de madrugada, com reunião marcada.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="pt-BR">
        <title>Minhas Apostas Antes de Comprimir as Cartas</title>
        <published>2026-09-01T00:00:00+00:00</published>
        <updated>2026-09-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            Brenner Cruvinel
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://brennercruvinel.blog/blog/apostas-antes-de-comprimir-as-cartas/"/>
        <id>https://brennercruvinel.blog/blog/apostas-antes-de-comprimir-as-cartas/</id>
        
        <content type="html" xml:base="https://brennercruvinel.blog/blog/apostas-antes-de-comprimir-as-cartas/">&lt;p&gt;a compressão não pode ter perda, mesmo que a gente tenha que inventar. eu escrevi essa frase no plano, com essa firmeza, depois do &lt;a href=&quot;https://brennercruvinel.blog/blog/nest-archiver-de-cartas-de-magic/&quot;&gt;primeiro build&lt;/a&gt;. duas linhas depois eu dizia que vídeo lossless com as cartas ordenadas por similaridade era um caminho, e mais acima tinha descartado o jpegtran porque &quot;sem perda não conta&quot;. só fui perceber quando sentei pra escrever as apostas e tive que definir o que eu ia medir, e medir exige dizer contra o quê. sem perda em relação a quê? eu não tinha resposta. eu tinha usado a mesma palavra pra três coisas e não tinha notado.&lt;/p&gt;
&lt;h2 id=&quot;por-que-zstd-nao-morde-jpeg&quot;&gt;por que zstd não morde jpeg&lt;/h2&gt;
&lt;p&gt;um jpeg já passou por três etapas que esgotam a redundância que um compressor genérico sabe achar. a imagem vira coeficientes de frequência, os coeficientes de alta frequência são arredondados (é aí que a perda acontece), e o resultado passa por um codificador de entropia. o que sai é um fluxo de bits onde os padrões repetidos já foram comidos. o zstd procura exatamente padrão repetido. é como tentar dobrar um papel que já foi dobrado até o limite: não fica menor, só amassa.&lt;/p&gt;
&lt;p&gt;então sem perda em cima de jpeg tem três sentidos, e só um merece o nome. o primeiro é repackar o jpeg de um jeito que a operação inversa devolve o arquivo original byte a byte. o jpeg xl faz isso: pega os coeficientes já arredondados e reescreve com um codificador de entropia melhor, guardando o suficiente pra desfazer. a perda do jpeg continua lá, mas a embalagem fica mais apertada.&lt;/p&gt;
&lt;p&gt;o segundo é re-salvar o mesmo jpeg com jpegtran ou jpegoptim. os pixels ficam idênticos e os bytes mudam. sem perda pra quem olha, não pra quem confere o hash. e se a fonte já veio otimizada, como os arquivos do scryfall, o ganho tende a zero.&lt;/p&gt;
&lt;p&gt;o terceiro é o que eu estava propondo sem saber: decodificar os pixels e recomprimir com outro codec sem perda, png, webp, ou vídeo em ffv1. parece o sentido mais puro e é o pior. o novo codec recebe pixels que carregam o ruído do arredondamento do jpeg como se fosse sinal, e gasta bit pra representar esse ruído com fidelidade. o resultado tem tudo pra ficar maior que o jpeg de origem, não menor. eu escrevi isso e ainda não acreditava, por isso a aposta.&lt;/p&gt;
&lt;p&gt;tem uma quarta coisa, que não é um sentido e sim um buraco. existe um paper de 2016, da microsoft research, que comprime coleções de fotos jpeg sem perda em cima de trinta por cento, montando um pseudo-vídeo direto no domínio dos coeficientes, sem decodificar pixel nenhum. é exatamente o que a ideia inventada queria ser, feito do jeito certo. só que não existe implementação aberta, e eu não ia escrever um codec naquela semana. então o que eu podia medir era o teto com as ferramentas que existem. o teto de verdade, se aquele paper reproduz, está mais alto, e eu quero deixar isso dito antes de dar meu número.&lt;/p&gt;
&lt;h2 id=&quot;o-que-eu-apostei-e-por-que&quot;&gt;o que eu apostei e por quê&lt;/h2&gt;
&lt;p&gt;anotei antes de rodar porque da última vez que publiquei um número sem aposta escrita tive que corrigir dois dias depois, e corrigir número publicado é pior do que errar aposta em público.&lt;/p&gt;
&lt;p&gt;vídeo av1 com perda comprime bem mais que jpeg solto. sim, foi o que o primeiro build mostrou e o que eu tinha visto na dermatoscopia. quanto mais parecidas as cartas vizinhas, mais o vídeo ganha. sim, e com força: o banco tem 100 mil impressões pra 52 mil ilustrações, quase metade das cartas repete uma arte que já existe em outra, e todas dividem a mesma moldura. se existe ganho de vídeo, ele mora aí.&lt;/p&gt;
&lt;p&gt;acelerar o vídeo corta bytes. eu tinha jogado um dos mp4 no capcut, acelerado sessenta vezes, e o arquivo saiu com metade do tamanho. aposto que sim, e se for verdade é a coisa mais barata da lista. tar mais zstd ganha alguma coisa? acho que não, pelo motivo da seção de cima. e a ordenação por similaridade vale também pra cartas únicas? acho que não, já vi ordenação não fazer nada em outro dataset, mas a moldura compartilhada me deixa em dúvida.&lt;/p&gt;
&lt;p&gt;e a que eu mais queria: existe sem perda melhor que jpeg, &quot;mesmo inventando&quot;. depois de escrever os três sentidos mudei de ideia no meio da aposta. o candidato passou a ser o jpeg xl, e o vídeo sem perda ordenado por similaridade, a ideia inventada, vai perder. as duas que eu mais queria que fossem verdade, o capcut e o sem perda, eram as que eu menos conseguia defender. isso costuma ser um sinal.&lt;/p&gt;
&lt;p&gt;a bateria sem perda rodou na mesma noite, dez gerações em cima das mesmas 2048 cartas. tar mais zstd deu um pra um, cravado. re-salvar com jpegtran rendeu menos de um por cento, os jpeg do scryfall já vêm otimizados. o teto foi o repack em jpeg xl, uns 11% menor, com os 2048 arquivos originais reconstruídos e validados por hash. subir o esforço do encoder pro máximo rende menos de um por cento a mais por seis vezes o tempo, então o padrão ficou no esforço médio.&lt;/p&gt;
&lt;p&gt;e a ideia inventada perdeu feio. ffv1 e x264 sem quantização, com as cartas ordenadas por similaridade, ficaram três a quatro vezes maiores que o jpeg de origem. a ordenação, nesse regime, moveu o tamanho em quase nada, uma fração de mega num arquivo de centenas. pixel decodificado de jpeg carrega ruído e nenhum codec prevê ruído, do jeito que eu tinha escrito de manhã sem acreditar.&lt;/p&gt;
&lt;p&gt;isso virou o perfil de arquivo do nest: quem não aceita perder nada recebe o jpeg xl repack, 3,6 GB pro corpus inteiro, e cada jpeg volta bit a bit quando pedir. não é o número que eu queria. é o número que dá pra defender com hash, e eu prefiro esse. as outras apostas, as com perda, estavam rodando enquanto eu escrevia isso. a do capcut eu já tava com medo de olhar.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="pt-BR">
        <title>O Nest Tem Potencial pra Ser um Archiver de Cartas de Magic</title>
        <published>2026-08-31T00:00:00+00:00</published>
        <updated>2026-08-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            Brenner Cruvinel
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://brennercruvinel.blog/blog/nest-archiver-de-cartas-de-magic/"/>
        <id>https://brennercruvinel.blog/blog/nest-archiver-de-cartas-de-magic/</id>
        
        <content type="html" xml:base="https://brennercruvinel.blog/blog/nest-archiver-de-cartas-de-magic/">&lt;p&gt;o felipe navarro, amigo meu, tá construindo o &lt;a rel=&quot;external&quot; href=&quot;https://github.com/ofelipenavarro/pasta.cards&quot;&gt;pasta.cards&lt;/a&gt;, um app opensource pra gerenciar coleção e deck de commander. roda local, guarda tudo em sqlite, e pra funcionar offline ele baixa a arte de cada carta de magic que já foi impressa, em duas versões: a carta inteira e só a ilustração. no meu mac isso deu 83 mil jpg e 7 GB. cada carta que existe, duas vezes, no meu disco, e o finder demorando pra abrir a pasta.&lt;/p&gt;
&lt;p&gt;o que me chamou atenção nesse número não foi o tamanho, disco é barato. foi a estrutura. as cartas dividem moldura, caixa de texto, tipografia, símbolo de mana. e o banco ao lado mostra que quase metade das impressões reusa uma arte que já saiu em outra carta. jpg solto não sabe nada disso. um catálogo bom aponta em vez de copiar, e um codec bom não gasta bit no pixel que dá pra prever do vizinho. eu gosto das duas coisas pelo mesmo motivo.&lt;/p&gt;
&lt;p&gt;eu tinha o &lt;a href=&quot;https://brennercruvinel.blog/blog/nest-vector-db-soberano/&quot;&gt;nest&lt;/a&gt; do lado, o banco de vetores de arquivo único que eu venho construindo este ano, e desde agosto ele guarda imagem também. a pergunta que eu fiz naquela manhã foi bem mais ingênua do que tudo que veio depois: como funciona, ele transforma em um vídeo? eu tenho 38 mil imagens e um database, o que ele faz com isso?&lt;/p&gt;
&lt;h2 id=&quot;ele-transforma-em-um-video&quot;&gt;ele transforma em um vídeo?&lt;/h2&gt;
&lt;p&gt;ele transforma em vídeo mesmo. cada imagem vira um frame de um stream av1 dentro do arquivo, e a busca roda sobre vetores tirados dos frames decodificados, não do jpeg de origem. isso é de propósito: o índice tem que descrever o que o arquivo serve, e o que o arquivo serve é o frame depois do codec. codec de vídeo é a tecnologia de compressão de imagem mais otimizada que existe, então o que parece gambiarra é só aproveitar décadas de trabalho dos outros.&lt;/p&gt;
&lt;p&gt;eu já tinha medido isso em pele. dermatoscopia, lâmina de biópsia, página de livro antigo, os datasets que eu tinha na mão pelo trabalho na hoff. o que aqueles números me ensinaram é que o quanto comprime é uma propriedade do domínio, não do codec. mesmo encoder, mesmo ajuste, a dermatoscopia comprimiu 151 vezes e a página de livro comprimiu 4. pele lisa comprime bem porque tem pouco detalhe pra guardar. letra pequena é o contrário, cada borda custa bit.&lt;/p&gt;
&lt;p&gt;carta de magic tem letra pequena. tem moldura igual em todas, que o codec pode ou não aproveitar. tem reprint da mesma arte, coisa que a dermatoscopia não tem. e tem uma coisa de produto que nenhuma métrica de busca mede: o tile de deck precisa ficar bonito na tela do app. eu achava que a pergunta era quanto comprime. era, mas por pouco tempo.&lt;/p&gt;
&lt;p&gt;antes de rodar qualquer coisa, olhei a pasta bruta com mais calma, e ela tinha as próprias surpresas. metade do peso era a versão recortada da arte, e minha primeira ideia foi jogar ela fora e recortar da carta inteira quando precisasse. um agente leu o código do app e voltou com má notícia: o banco não guarda coordenada de recorte nenhuma, e o app usa o recorte direto nos tiles de deck. a arte recortada é uma classe própria, não umo driftda. e do lado da pasta de imagens tinha um diretório chamado _redundant, com mais sete giga de cópia de coisa que já estava lá. esse eu deletei sem dó.&lt;/p&gt;
&lt;h2 id=&quot;o-primeiro-build&quot;&gt;o primeiro build&lt;/h2&gt;
&lt;p&gt;o piloto com algumas centenas de cartas morreu com segfault antes de fazer qualquer coisa. o binding python do nest tinha sido compilado sem a flag que diz &quot;usa o python do processo, não traz o seu&quot;, e o python que eu uso é estático, então eram dois interpretadores brigando pela mesma memória. uma flag e pronto. bug pequeno, e do tipo que me deixa desconfiado: quantos outros pressupostos de ambiente o build carrega sem declarar?&lt;/p&gt;
&lt;p&gt;com a flag, o build completo rodou em background e eu abri um dos vídeos no player. carta passando, uma por segundo, quarenta minutos de carta. minha primeira hipótese foi que o encoder estava repetindo frames pra segurar cada carta na tela, e que dava pra cortar isso. uma linha de ffprobe matou a hipótese: um frame por carta, e a duração é só anotação de tempo no container, não custa byte.&lt;/p&gt;
&lt;p&gt;7 GB viraram 1,9 GB, com busca por imagem funcionando em cima. rodou de primeira, o que num projeto meu é raro o bastante pra eu desconfiar. aí eu abri a pasta de saída.&lt;/p&gt;
&lt;h2 id=&quot;um-archiver-com-38-mp4-do-lado-nao-e-um-archiver&quot;&gt;um archiver com 38 mp4 do lado não é um archiver&lt;/h2&gt;
&lt;p&gt;38 arquivos de vídeo, um .nest com os índices, um manifest em json e um diretório de cache. quatro tipos de arquivo pra servir uma coisa só. o nest existe pra ser o banco num arquivo, e o que saiu foi um banco com quarenta coisas ao lado. funcionava, do jeito que funciona um móvel montado com parafuso sobrando. o propósito do nest estava sendo perdido ali, e a compressão nem era o problema. o nest tem potencial pra ser um archiver de carta de magic, de mapa, de qualquer dataset de imagem com database do lado. mas archiver é um arquivo, não uma pasta.&lt;/p&gt;
&lt;p&gt;a segunda pergunta veio do modelo. o script usava clip porque era o que o forge tinha, e eu nem sabia direito o que o clip fazia com uma carta. fui atrás: ele olha a arte e ignora o texto impresso. pra uma carta de magic isso é olhar a metade menos útil, porque a coisa que identifica a carta é o nome escrito em cima. pedi uma pesquisa no hugging face e no github do que existia de modelo multimodal aberto naquele mês, e a resposta era uma família da tencent chamada wemm, que lê texto dentro de imagem, mais o siglip e o jina. eu tinha o wemm de dois bilhões de parâmetros baixado por outro motivo.&lt;/p&gt;
&lt;p&gt;numa comparação com 1500 cartas e sessenta buscas do tipo &quot;a arte da carta tal&quot;, o clip acertou a carta certa uma vez em quatro. o wemm acertou nove em dez, o siglip ficou perto dele, o jina no meio. o preço do wemm é ser umas trinta vezes mais lento que o clip, e rodar ele no corpus inteiro levaria quase um dia na minha máquina. então a comparação ficou na amostra, e o corpus completo continuou com o clip por enquanto.&lt;/p&gt;
&lt;p&gt;o que saiu disso não foi &quot;troca pro wemm&quot;. foi que o modelo não pode ser uma constante no topo de um script. tem que ser uma lista de presets dentro da engine, e quem builda escolhe qual, ou quais, na hora do build. e o vídeo tem que caber dentro do arquivo, não do lado. antes de qualquer uma dessas duas coisas, eu queria saber quanto dava pra comprimir sem perder nada. nem que a gente tivesse que inventar.&lt;/p&gt;
</content>
        
    </entry>
</feed>
