<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Brenner Cruvinel - ia</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/en/tags/ia/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/en/tags/ia/atom.xml</id>
    <entry xml:lang="en">
        <title>Validating on disk the benchmark I wrote in the middle of the night</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/en/blog/validando-no-disco-o-benchmark-que-escrevi-de-madrugada/"/>
        <id>https://brennercruvinel.blog/en/blog/validando-no-disco-o-benchmark-que-escrevi-de-madrugada/</id>
        
        <content type="html" xml:base="https://brennercruvinel.blog/en/blog/validando-no-disco-o-benchmark-que-escrevi-de-madrugada/">&lt;p&gt;I opened a new session, with no context at all, and asked as if I were someone who had just cloned the repo: how do you use Nest, does it have an interface? The first command the answer suggested returned &lt;code&gt;command not found&lt;/code&gt; on my own machine. The second one showed a default model I was sure I had swapped out weeks earlier. Two holes no test catches, found in five minutes, and that&#39;s when I realized the topic of the week wasn&#39;t compression.&lt;/p&gt;
&lt;p&gt;The benchmark was just the place where it became visible. So, before publishing any number from the &lt;a href=&quot;https://brennercruvinel.blog/en/blog/nest-archiver-de-cartas-de-magic/&quot;&gt;series&lt;/a&gt;, I spent a whole day validating: every number against the file on disk.&lt;/p&gt;
&lt;h2 id=&quot;what-was-wrong&quot;&gt;What was wrong&lt;/h2&gt;
&lt;p&gt;The first thing that didn&#39;t match wasn&#39;t even a number. I ran &lt;code&gt;strings&lt;/code&gt; on the release file and out came &quot;Spellbook MTG corpus&quot;. The file is called mtgdataset. The rename from a week earlier, which swapped Felipe&#39;s app name for the generic name in every text file, never made it into the binary. The lock next to the release described a file with the new name while the file had the old name inside, and no tool flagged it, because validation checks the file against itself. I rebuilt both releases with the right name inside, and the media bytes came out identical, which at least proved the encode is reproducible.&lt;/p&gt;
&lt;p&gt;The sizes of the three most aggressive builds were in mebibytes labeled as gigabytes. &lt;code&gt;du&lt;/code&gt; talks in MiB and I pasted it as GB. The difference is five percent per row, but the ratio inherits the error: the search build I had announced as 7.8 times smaller than the source is 7.5. The AVIF I said was 20% smaller than the recommended build is 13%. No conclusion changes, and it still made me ashamed, because it&#39;s the kind of mistake I&#39;d point out in someone else&#39;s report.&lt;/p&gt;
&lt;p&gt;The strangest one was the manifest of the AVIF build, which records a 21 GB source. The source is 4. I couldn&#39;t make sense of it until I divided by the number of cards: it gives the size of an uncompressed image, which is what the encoder receives midway through. The backend counted the intermediate instead of the JPEG. One line to fix, one test to write, and until then the ratio in that manifest was made up.&lt;/p&gt;
&lt;p&gt;And there&#39;s what can no longer be verified. I deleted the first full build when the disk filled up, so the sentence &quot;the three builds share the same hash&quot; is now two-thirds checkable. The CapCut experiment and the reprint matrix left no artifacts, only text. I told it to delete them myself, to free up space. Disk space is cheaper than trust and I should have known that.&lt;/p&gt;
&lt;h2 id=&quot;the-other-times-i-read-the-summary-and-not-the-file&quot;&gt;The other times I read the summary and not the file&lt;/h2&gt;
&lt;p&gt;The 200-card summary. I asked for the whole dataset, got a summary that talked about the corpus as if it were the corpus, and it was a pilot. I noticed with a meeting already scheduled. The agent didn&#39;t lie, it summarized the scope without saying so, and I read the summary instead of the disk. Since then every build delivery comes with validation of the real file, and this post exists for the same reason.&lt;/p&gt;
&lt;p&gt;My machine&#39;s paths. Days after the benchmark I found my home directory path in dozens of files headed for GitHub, inside manifests, locks, specs. The fix was an environment variable and a script that rewrites the sidecars before committing. And before that the agent had wanted to store my preferences in a local memory file. I asked it to delete it. If the workflow depends on what it remembers about my machine, it won&#39;t run in the next repo, and it won&#39;t run on the machine of whoever clones it.&lt;/p&gt;
&lt;p&gt;The five-gig repo. At some point I asked how ML engineers deal with this, because GitHub won&#39;t take it. The answer I adopted: not a single image byte goes into git. Each sample is a list of ids with the rule that generated it, and whoever has the SQLite database rebuilds the exact sample. The &lt;code&gt;.nest&lt;/code&gt; files go to Hugging Face with a hash alongside, and the repo keeps the lock, the clean manifest and the hash. The whole report is generated from each experiment&#39;s JSON, and a CI check fails if the markdown differs from the JSON. I can no longer paste a wrong number into a report without CI complaining, which is the kind of protection I needed against myself.&lt;/p&gt;
&lt;p&gt;The commits. The agent prefers one fat commit at the end of the day. I asked for sixteen, separate, with descriptions, because the history has to be readable for whoever comes later. And the plans: I rejected three in a row because they treated the model comparison as a footnote to a build already decided. The model list that exists in Nest today only exists because I insisted that research becomes a feature, never a footnote.&lt;/p&gt;
&lt;p&gt;What these episodes have in common is just one thing: in all of them I accepted a layer between me and the file. Summary, manifest, memory, fat commit. And the layer was wrong and didn&#39;t say so.&lt;/p&gt;
&lt;h2 id=&quot;what-i-ll-try-next-time&quot;&gt;What I&#39;ll try next time&lt;/h2&gt;
&lt;p&gt;Something that put me more at ease, in the middle of the validation, was finding out that the two findings I liked most already had names. The redundancy between images in a collection, which inter exploits, has been called set redundancy since &lt;a rel=&quot;external&quot; href=&quot;https://repository.lsu.edu/gradschool_disstheses/6258/&quot;&gt;Kosmas Karadimitriou&#39;s 1996 doctoral thesis&lt;/a&gt;, and the trick of lining images up as a pseudo-video has a decade of literature behind it. And the distinction between visual quality, vector stability and search usefulness, which I thought I had invented, is everyday business in the field the standards call coding for machines: it evaluates with task metrics, like mAP, precisely because &lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2110.09241&quot;&gt;feature fidelity and task performance can drift apart&lt;/a&gt;. I didn&#39;t discover anything. I confirmed two things on a corpus nobody had looked at, and that makes me calmer about publishing than if it were something new.&lt;/p&gt;
&lt;p&gt;The 1.3 GB file is already in the pasta.cards repo, waiting for the screen that will use it. What&#39;s left of the benchmark fits in a paragraph: measure search with WeMM, build the full reprint corpus, decide whether AVIF becomes the default. None of the three has a date.&lt;/p&gt;
&lt;p&gt;What I&#39;ll try is the cheapest thing of all: open a clean session as a user before every release and type the first command in the readme. Five minutes, and it found two holes a week of agents didn&#39;t. I don&#39;t know if I&#39;ll remember to do it. But now it&#39;s written down, and I trust what&#39;s written more than what I remember.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Raising the CRF until search breaks</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/en/blog/subindo-o-crf-ate-a-busca-bugar/"/>
        <id>https://brennercruvinel.blog/en/blog/subindo-o-crf-ate-a-busca-bugar/</id>
        
        <content type="html" xml:base="https://brennercruvinel.blog/en/blog/subindo-o-crf-ate-a-busca-bugar/">&lt;p&gt;The question from the &lt;a href=&quot;https://brennercruvinel.blog/en/blog/no-final-do-dia-ta-a-mesma-coisa-1-3-gb/&quot;&gt;previous night&lt;/a&gt; was simple to ask and I took my time asking it. The app keeps every card&#39;s JPEG cached for display. The &lt;code&gt;.nest&lt;/code&gt; doesn&#39;t need to be pretty, it needs to answer which card it is. So the CRF, the codec&#39;s quality setting, can go up until the vector breaks, and the vector breaks well after the eye does. If that were true, it would even be worth thinking about two tiers: a thumbnail inside Nest for search, and the full card on demand when the app needs to show it.&lt;/p&gt;
&lt;p&gt;I had a way to measure this, or thought I did. Nest&#39;s quality gate has two sides: a visual metric, which compares the decoded frame with the source, and vector drift, which measures how much the CLIP vector moves between the original image and the decoded one. The idea behind drift is that an image can look fine and still have moved in search space. I trusted that metric. It was mine, I had insisted on it in the plan, and for good reason: on dermoscopy it had caught a case where the image passed by eye and search by clinical text dropped by half.&lt;/p&gt;
&lt;h2 id=&quot;the-gate-rejected-the-whole-ladder&quot;&gt;The gate rejected the whole ladder&lt;/h2&gt;
&lt;p&gt;I took the visual side out of the gate and left only drift, with the floor Nest has used since dermoscopy. I had the CRF climb a ladder from 40 to 60. The gate rejected the whole ladder, including the first rung, and fell back to the most conservative one because it had nowhere else to go. Drift was already below the floor at CRF 40 and only got worse. And the visual metric, which I had taken out of the gate but kept computing, told the same story from another angle: from CRF fifty on it gave negative values on some cards, something I didn&#39;t even know it could do.&lt;/p&gt;
&lt;p&gt;The file the gate delivered, at CRF 40, the lowest on the ladder, came out close to one gig, four times smaller than the source. It was already smaller than the recommended build, and the gate had rejected it too, it just had nowhere lower to fall.&lt;/p&gt;
&lt;p&gt;By the gate&#39;s logic, none of these files was usable. I built one at CRF fifty anyway, no gate, just to see the size. Half a gig. Seven and a half times smaller than the source, and the cards turned into a blur, rules text unreadable, the art recognizable only from a distance. A file I wouldn&#39;t show anyone.&lt;/p&gt;
&lt;p&gt;Then I ran the metric that actually matters, which I hadn&#39;t run before because I was too lazy to set it up: search by name. A hundred queries like &quot;the art of such-and-such card&quot;, on the five files I had of the full corpus, from the 3.6 GB lossless one down to the half-gig blur. The first round gave zero hits on two of the five files, the lossless and the recommended one, and for a minute I thought I had broken search. It was the bench: it matched results by the card&#39;s position in the stream, and the two files with similarity sorting have a different order. Nest has a content id per item for exactly this, and once the bench matched on that, all five files answered again.&lt;/p&gt;
&lt;p&gt;And accuracy stayed within the noise on all of them. The half-gig file finds the right card as well as the 1.3 GB one, and both stay within the noise of the lossless file.&lt;/p&gt;
&lt;h2 id=&quot;search-survives-far-more-compression-than-the-eye&quot;&gt;Search survives far more compression than the eye&lt;/h2&gt;
&lt;p&gt;I stared at that on screen for a while. Cosine drift measures whether the vector moved. Search measures whether the vector still finds. I was using the first as if it were the second, and it would have vetoed a file that works. The vector does move, and it still stays closer to the right card than to any other. How far it travels doesn&#39;t matter if its neighbors don&#39;t change. A gate that&#39;s any good for this use needs hit@k as its floor, the fraction of queries where the right card shows up in the top k results, not cosine.&lt;/p&gt;
&lt;p&gt;That became a profile in Nest, &lt;code&gt;retrieval-only&lt;/code&gt;, which writes into the manifest that the file serves search and never display. And AVIF, which in the benchmark had come out about 12% smaller than AV1 at the same quality, held up on the full corpus: 13% smaller than the recommended build. It&#39;s the candidate to become the default backend for stills, and I haven&#39;t decided yet, because AVIF takes four to ten times longer to build and embed.&lt;/p&gt;
&lt;p&gt;There&#39;s a third metric I ran alongside that&#39;s worth nothing, and it&#39;s worth saying why. Identity: give the image itself as the query and see if it finds itself. It gave a hundred percent on every file, even the blur. Looks great and says nothing, because the query is the very image that produced the frame, and the uncompressed control also gives a hundred. It only proves the codec didn&#39;t destroy the data. I left it in the bench so nobody confuses pipeline stability with usefulness, which is exactly the confusion I made with drift.&lt;/p&gt;
&lt;p&gt;The metric is weak and I know it. It&#39;s a hundred queries with a model that does poorly on this corpus, CLIP, which only looks at the art. &quot;Didn&#39;t drop&quot; means &quot;I couldn&#39;t measure a drop&quot;, not &quot;it&#39;s the same&quot;. And WeMM, which reads the name printed on the card and gets nine in ten right, might suffer far more from the blur than CLIP, because what it reads is exactly what the CRF destroys first. I didn&#39;t measure it. That&#39;s next, along with the full reprint corpus, which is where inter has something to predict.&lt;/p&gt;
&lt;p&gt;What stuck with me that week was less about compression and more about what I choose to measure. I had a metric I designed myself, one that looked rigorous, and it answered a question I wasn&#39;t asking. And before publishing any of these numbers, I wanted to validate each one against the disk, because a report written in the middle of the night gets things wrong.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Image compression for computer vision: February&#39;s proposal and September&#39;s answer</title>
        <published>2026-03-12T00:00:00+00:00</published>
        <updated>2026-03-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            Brenner Cruvinel
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://brennercruvinel.blog/en/blog/compressao-imagem-computer-vision/"/>
        <id>https://brennercruvinel.blog/en/blog/compressao-imagem-computer-vision/</id>
        
        <content type="html" xml:base="https://brennercruvinel.blog/en/blog/compressao-imagem-computer-vision/">&lt;p&gt;In February 2026 I wrote a proposal for dermML&#39;s dermatology dataset, which was already stored as AV1 video: sort the images by visual similarity before encoding, so neighbouring frames would look alike and the codec could take advantage of it. I estimated compression would go from about 30 times to 50 to 80 times. I did not implement it at the time.&lt;/p&gt;
&lt;p&gt;In September I tested the same idea in Nest, with Magic cards, and it did not survive the real corpus. This post brings both ends together: what I was reading, what I proposed and what the data answered.&lt;/p&gt;
&lt;h2 id=&quot;what-i-was-reading&quot;&gt;What I was reading&lt;/h2&gt;
&lt;p&gt;Two readings convinced me that lossy compression of training data was safe and that video was a real dataset format.&lt;/p&gt;
&lt;p&gt;The first was a two-part series by Kaleigh Mentzer at Granica. In &lt;a rel=&quot;external&quot; href=&quot;https://www.granica.ai/blog/using-image-compression-to-improve-computer-vision-part-1&quot;&gt;part 1&lt;/a&gt;, from November 2023, Food101 classification with a Vision Transformer barely lost accuracy until compression went past about 70%, and segmentation models on Cityscapes could be trained on data compressed by 97%, using only 3% of the original storage, with no relevant drop in mIoU. Their explanation is that lossy compression throws away high frequencies first, and the model depends little on them too; in a test with 32 images, perturbing high-frequency DCT coefficients barely moved the prediction. In &lt;a rel=&quot;external&quot; href=&quot;https://www.granica.ai/blog/using-image-compression-to-improve-computer-vision-part-2&quot;&gt;part 2&lt;/a&gt;, with a fixed budget of about 0.5 GB, the original JPEGs fit 9,985 images, and compressing to Butteraugli distance 10 fit about 5.6 times more. With fewer than 100 images per class, more worse images beat fewer better ones by more than 10 accuracy points.&lt;/p&gt;
&lt;p&gt;The second was Hugging Face. In the &lt;a rel=&quot;external&quot; href=&quot;https://huggingface.co/blog/video-encoding&quot;&gt;post on encoding robotics datasets as video&lt;/a&gt;, from August 2024, datasets ended up at 14% of their original size on average, reaching 0.2% in the best case, and loading several consecutive frames took 25% to 50% of the time of loading the same frames from PNG. &lt;a rel=&quot;external&quot; href=&quot;https://huggingface.co/blog/lerobot-datasets-v3&quot;&gt;LeRobotDataset v3.0&lt;/a&gt;, from September 2025, went further: several episodes per file, metadata in Parquet and streaming straight from the Hub.&lt;/p&gt;
&lt;h2 id=&quot;the-proposal&quot;&gt;The proposal&lt;/h2&gt;
&lt;p&gt;The problem I saw in dermML was frame order. Images went into the video in file system order, alphabetical by name:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color-scheme: light dark; color: light-dark(#657B83, #839496); background-color: light-dark(#FDF6E3, #002B36);&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Frame 0: ISIC_0024306.jpg  (dark melanoma, light skin)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Frame 1: ISIC_0024307.jpg  (benign nevus, dark skin)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Frame 2: ISIC_0024308.jpg  (dermatofibroma, medium skin)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;AV1 encodes a frame from its difference to a reference frame. If the neighbour is a different lesion on different skin, the difference is large and inter-frame prediction does not help. My reading was that the 30 times came almost entirely from compression inside each image.&lt;/p&gt;
&lt;p&gt;The idea: DermLIP embeddings, with 512 dimensions, already existed in the indexing pipeline. Finding the order that minimizes the distance between consecutive frames is a travelling salesman problem in embedding space, intractable to solve exactly for 58 thousand points, so the proposal used the simplest heuristic, greedy nearest neighbour:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color-scheme: light dark; color: light-dark(#657B83, #839496); background-color: light-dark(#FDF6E3, #002B36);&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt;import&lt;/span&gt;&lt;span&gt; numpy&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; as&lt;/span&gt;&lt;span&gt; np&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt;from&lt;/span&gt;&lt;span&gt; sklearn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;metrics&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pairwise&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; import&lt;/span&gt;&lt;span&gt; cosine_distances&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#586E75, #93A1A1);font-weight: bold;&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: light-dark(#268BD2, #268BD2);&quot;&gt; sort_by_visual_similarity&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;embeddings&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    order&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; visited&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; current&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span style=&quot;color: light-dark(#D33682, #D33682);&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span style=&quot;color: light-dark(#D33682, #D33682);&quot;&gt;0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style=&quot;color: light-dark(#D33682, #D33682);&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt;    for&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color: light-dark(#268BD2, #268BD2);&quot;&gt; range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: light-dark(#268BD2, #268BD2);&quot;&gt;len&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;embeddings&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color: light-dark(#D33682, #D33682);&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        dists&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; =&lt;/span&gt;&lt;span&gt; cosine_distances&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;embeddings&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;current&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;current&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color: light-dark(#D33682, #D33682);&quot;&gt; 1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; embeddings&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style=&quot;color: light-dark(#D33682, #D33682);&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        dists&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt;list&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;visited&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; float&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: light-dark(#2AA198, #2AA198);&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color: light-dark(#2AA198, #2AA198);&quot;&gt;inf&lt;/span&gt;&lt;span style=&quot;color: light-dark(#2AA198, #2AA198);&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        current&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; int&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;np&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;argmin&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dists&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        visited&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;current&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        order&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;current&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt;    return&lt;/span&gt;&lt;span&gt; order&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the estimate I wrote down:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Scenario&lt;/th&gt;&lt;th&gt;Estimated compression&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;File system order&lt;/td&gt;&lt;td&gt;~30x&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Sorted by similarity&lt;/td&gt;&lt;td&gt;~50 to 80x&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Sorted, with CRF and GOP tuning&lt;/td&gt;&lt;td&gt;~60 to 100x&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;For the full dataset, that would take the 52 GB from something like 1.7 GB to somewhere between 650 MB and 1 GB. The proposal already called for &lt;code&gt;keyint=250&lt;/code&gt; and &lt;code&gt;scd=0&lt;/code&gt; in SVT-AV1, to turn off scene cut detection and avoid an extra keyframe at every new lesion, and for one more SQLite column to map each sorted frame back to its original image. The validation plan was to encode with and without sorting, compare size and quality, and check that search still found the same things.&lt;/p&gt;
&lt;h2 id=&quot;what-september-answered&quot;&gt;What September answered&lt;/h2&gt;
&lt;p&gt;In August Nest started storing images, and in September the &lt;a href=&quot;https://brennercruvinel.blog/en/blog/nest-archiver-de-cartas-de-magic/&quot;&gt;Magic card series&lt;/a&gt; became the test I had not done in February.&lt;/p&gt;
&lt;p&gt;The first round with inter-frame prediction came out bigger than without it. The reason was exactly what the proposal had warned about: scene cut detection was on, and one different card after another is a scene cut on every frame. With it off, on a corpus of almost three thousand reprints of the same art, prediction made the file 29% smaller. Shuffling the reprints cost only two points, because part of the gain came from the frame every card shares. And that 29% compares bytes only, not equivalent quality.&lt;/p&gt;
&lt;p&gt;On the real corpus, of unique cards, the sorted build and the plain build came out at the &lt;a href=&quot;https://brennercruvinel.blog/en/blog/no-final-do-dia-ta-a-mesma-coisa-1-3-gb/&quot;&gt;same 1.3 GB&lt;/a&gt;. Nest tests both ways per segment before deciding, and it vetoed prediction in all of them. There was nothing to predict: the next card is another card, with another artwork. In the lossless regime it was worse: FFV1 and x264 without quantization, with the cards sorted, came out &lt;a href=&quot;https://brennercruvinel.blog/en/blog/apostas-antes-de-comprimir-as-cartas/&quot;&gt;three to four times bigger&lt;/a&gt; than the source JPEGs, and sorting barely moved the size.&lt;/p&gt;
&lt;h2 id=&quot;what-remains&quot;&gt;What remains&lt;/h2&gt;
&lt;p&gt;The thesis from the readings still stands: training data tolerates a lot of compression, and video is a good dataset container. What fell was my extrapolation. Sorting by similarity only pays off when the corpus has near-duplicate images, like card reprints or consecutive robot frames. In dermoscopy, each image is a different lesion from a different person, and the estimate of 50 to 80 times was probably optimistic for the same reason as the card one.&lt;/p&gt;
&lt;p&gt;The other lesson is about method. Compression is a property of the domain before it is a property of the codec, and the only way to know is to encode the real corpus and look at the file, not the summary.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Nest: a sovereign, content-addressable, offline-first vector DB</title>
        <published>2026-01-15T00:00:00+00:00</published>
        <updated>2026-01-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            Brenner Cruvinel
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://brennercruvinel.blog/en/blog/nest-vector-db-soberano/"/>
        <id>https://brennercruvinel.blog/en/blog/nest-vector-db-soberano/</id>
        
        <content type="html" xml:base="https://brennercruvinel.blog/en/blog/nest-vector-db-soberano/">&lt;p&gt;Sharing the concept of something I am working on, tired of hacking around vector DBs with no stability. In an ordinary database, a chunk&#39;s ID can change when the index is rebuilt, and a saved citation starts pointing somewhere else without telling anyone.&lt;/p&gt;
&lt;p&gt;Nest is my answer to that. It is a single-file vector DB, content-addressable, offline-first and hash-verified. Python builds the file, Rust serves the file. The address of any piece is:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color-scheme: light dark; color: light-dark(#657B83, #839496); background-color: light-dark(#FDF6E3, #002B36);&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;nest://content_hash/chunk_id&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You cite by content, not by location. If the content changes, the hash changes, and it is impossible to receive something other than what you asked for.&lt;/p&gt;
&lt;h2 id=&quot;the-hash-as-the-address&quot;&gt;The hash as the address&lt;/h2&gt;
&lt;p&gt;The central idea is just one: the hash of the content becomes the address of the content. Instead of &quot;give me the file in such-and-such folder&quot;, which could have been swapped without you knowing, you say &quot;give me the file whose content has this hash&quot;. If something else came back, it would have a different hash. Verification is not an extra step; it is the very way you ask.&lt;/p&gt;
&lt;p&gt;In Nest, the two parts of the address play different roles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;content_hash&lt;/code&gt; identifies the whole file by the hash of what it contains&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chunk_id&lt;/code&gt; points to the piece inside that file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The practical consequence is the one I wanted: a citation does not break when the file moves to another folder, disk or machine. It is only valid for that content. If the content changed, the hash changed, and you know right away you are talking about something else.&lt;/p&gt;
&lt;h2 id=&quot;the-family&quot;&gt;The family&lt;/h2&gt;
&lt;p&gt;This is not my invention. It is content-addressable storage, and it has famous relatives.&lt;/p&gt;
&lt;p&gt;The most everyday one is Git. Each commit is identified by a hash of its content, and a &lt;code&gt;git commit&lt;/code&gt; is a sealed snapshot of the code at that moment. If someone rewrites history, the hashes do not match and Git flags it. IPFS does the same thing at scale, on a distributed network of many machines.&lt;/p&gt;
&lt;p&gt;Nest sits between the two:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;What it seals&lt;/th&gt;&lt;th&gt;What it is for&lt;/th&gt;&lt;th&gt;Scale&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Git&lt;/td&gt;&lt;td&gt;Code&lt;/td&gt;&lt;td&gt;Navigating versions&lt;/td&gt;&lt;td&gt;Repository&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;IPFS&lt;/td&gt;&lt;td&gt;Any file&lt;/td&gt;&lt;td&gt;Distribution&lt;/td&gt;&lt;td&gt;Network of machines&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Nest&lt;/td&gt;&lt;td&gt;Vectorized knowledge&lt;/td&gt;&lt;td&gt;Search&lt;/td&gt;&lt;td&gt;One local file&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Same mechanism, different purpose. Of the analogies I collected, this is the only one that describes Nest without a metaphor: content addressing for the same reason, so that asking for the data and verifying the data are the same operation.&lt;/p&gt;
&lt;h2 id=&quot;shipping-versions-without-downloading-everything-again&quot;&gt;Shipping versions without downloading everything again&lt;/h2&gt;
&lt;p&gt;A file that never changes is great for citing and bad for updating. If every new version forces you to download the whole file, nobody updates.&lt;/p&gt;
&lt;p&gt;The most technical option is binary diffing. Tools like &lt;code&gt;bsdiff&lt;/code&gt;, &lt;code&gt;xdelta&lt;/code&gt; and the &lt;code&gt;--patch-from&lt;/code&gt; mode of &lt;code&gt;zstd&lt;/code&gt; compute the difference between yesterday&#39;s file and today&#39;s and produce a small patch. Whoever already has yesterday&#39;s version downloads only the patch and rebuilds today&#39;s locally, the same way an operating system updates without downloading everything again.&lt;/p&gt;
&lt;p&gt;The problem is that this only works if the format is append-friendly, meaning that adding content at the end does not mess up what was already there. A deterministic build that reorders things internally, and compressed, indexed formats tend to reorganize a lot, can produce a huge diff for a small change: you change one chunk, the whole index moves, and the patch is no longer small. That is why I think this route is more fragile than splitting the file into shards and shipping only the new shard. And you only find out by testing.&lt;/p&gt;
&lt;h2 id=&quot;beyond-the-hash&quot;&gt;Beyond the hash&lt;/h2&gt;
&lt;p&gt;The hash proves that something did not change. It does not prove who vouched for it or when the content existed. There are three known layers for that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Digital signatures and certificates, the same idea behind &lt;code&gt;https&lt;/code&gt; and signed software, which adds the &quot;who&quot;&lt;/li&gt;
&lt;li&gt;Trusted timestamping, such as the &lt;a rel=&quot;external&quot; href=&quot;https://www.rfc-editor.org/rfc/rfc3161&quot;&gt;RFC 3161&lt;/a&gt; protocol or &lt;a rel=&quot;external&quot; href=&quot;https://opentimestamps.org/&quot;&gt;OpenTimestamps&lt;/a&gt;, which anchors the hash in Bitcoin and proves the content existed on a date without revealing the content&lt;/li&gt;
&lt;li&gt;Blockchain, which solves immutability with no central party to trust&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of the three, timestamping fits Nest best. Blockchain, for storing papers and citing passages, would be a sledgehammer to crack a nut: Nest wants content addressing and local verification, not consensus among machines that do not trust each other.&lt;/p&gt;
&lt;p&gt;In August, Nest started storing images too, and that became the &lt;a href=&quot;https://brennercruvinel.blog/en/blog/nest-archiver-de-cartas-de-magic/&quot;&gt;Magic card series&lt;/a&gt;. More soon.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Prompt templates for research with agents</title>
        <published>2025-09-15T00:00:00+00:00</published>
        <updated>2025-09-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            Brenner Cruvinel
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://brennercruvinel.blog/en/blog/templates-prompt-pesquisa-agentica/"/>
        <id>https://brennercruvinel.blog/en/blog/templates-prompt-pesquisa-agentica/</id>
        
        <content type="html" xml:base="https://brennercruvinel.blog/en/blog/templates-prompt-pesquisa-agentica/">&lt;p&gt;I&#39;ve been testing a few prompt templates that push crawlers and LLMs into delivering research more accurately. Sharing what I have, still in testing.&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;WIP, template under test. Still tuning it, not a final version.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The structure is always the same: role, objective, style, discovery sources, mandatory metadata and a benchmark rule. The most important line is the one that says to write &quot;no reliable public benchmarks&quot; instead of making up a number.&lt;/p&gt;
&lt;p&gt;And even with it, that&#39;s not enough. Variation A produced my &lt;a href=&quot;https://brennercruvinel.blog/en/blog/ecossistema-rust-open-source/&quot;&gt;survey of the Rust ecosystem&lt;/a&gt;. In 2026, when I checked every row of that table against the GitHub API, I found a repository that doesn&#39;t exist and a row pointing to the wrong project, one with a similar name. The prompt forbade making things up, and the agent made things up anyway. The template organizes the delivery, but the one who checks the result is still me.&lt;/p&gt;
&lt;h2 id=&quot;template-for-research-with-agents&quot;&gt;Template for research with agents&lt;/h2&gt;
&lt;p&gt;The prompts stay in Portuguese, exactly as I use them, because they ask the agent for output in Brazilian Portuguese. Variation A, the survey of Rust applications:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color-scheme: light dark; color: light-dark(#657B83, #839496); background-color: light-dark(#FDF6E3, #002B36);&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;papel: pesquisador sênior em ecossistema open source e linguagens de sistemas, foco pesado em rust.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;objetivo: mapear e analisar pelo menos 369 aplicações open source em rust, explicando por que cada uma importa, com metadados completos, contexto de adoção em produção e benchmark comparativo em tabela sempre que houver.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;estilo: pt-br, sem emoji, sem bullet no corpo principal (parágrafo e tabela), todo recurso citado como Nome + URL com link markdown clicável, cabeçalhos markdown.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;fontes de descoberta: Awesome Rust, LibHunt Rust, OSS Insight, GitHub Stars Leaderboard, Github Ranking Top 100 Stars in Rust, listas &amp;quot;Rust in production&amp;quot; e &amp;quot;rust-in-production&amp;quot; (ImplFerris). priorizar alto número de stars/forks e atividade recente, uso em produção por empresa conhecida, diversidade de domínio (devtools, sistemas distribuídos, db, runtimes js/ts, ai infra, editores, cli, jogos, wasm, networking, segurança, observabilidade). evitar libs minúsculas ou experimentais salvo impacto desproporcional.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;metadados obrigatórios por app: nome, url do repo, descrição de 1 a 3 frases, categoria, subdomínio, maturidade, empresas que usam em produção, licença, stars/forks, crescimento 6 a 12 meses, data do último commit, número de contribuidores, &amp;quot;por que é foda&amp;quot; (2 a 4 frases).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;benchmark: pra cada projeto com benchmark público sério, colete tipo, contexto, setup (hardware, so, versão rust), comparação, números-chave com unidade, link da fonte. monte tabela por projeto: projeto, cenário, métrica, resultado, concorrente, resultado da concorrente, vantagem aproximada, link. sem benchmark crível, escreva &amp;quot;sem benchmarks públicos confiáveis&amp;quot;, nunca invente número. divergência entre fontes, explicite cenário, não consolide.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;organização: tabela geral com as 369+, mais tabelas por domínio (devtools, runtimes/plataformas, infra/observabilidade, terminais/shells/editores, web e apis, segurança/cripto/password managers, ai/ml infra, db e search, embedded/iot). análise final: distribuição por tipo, vantagem comparativa do rust, padrões técnicos (async/await/tokio, crates de observabilidade, arquiteturas), adoção corporativa vs go/c/c++/java/node, benchmark vs marketing, lacunas e oportunidades.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;details&gt;
&lt;summary&gt;Variation B: Rust + WebAssembly books and resources&lt;/summary&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color-scheme: light dark; color: light-dark(#657B83, #839496); background-color: light-dark(#FDF6E3, #002B36);&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;papel: pesquisador sênior em rust, wasm e desenvolvimento web de alta performance.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;objetivo: análise completa e atualizada de livros e recursos estruturados de rust + wasm, ecossistema global e contexto brasileiro, em tabelas com links clicáveis (livros, editoras, lojas, repos, doc oficial).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;estilo: idem variação A.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;escopo, cinco grupos: (1) livros-base de rust core relevantes pra wasm, com título, autor, ano, nível, temas, recomendação pra wasm, link oficial/compra/comunidade/repo. (2) livros específicos de rust + wasm, 20 a 30 obras, com tipo de recurso, público-alvo, escopo (browser/wasi/iot/mobile/desktop). (3) livros de wasm geral relevantes pra rust, com coluna &amp;quot;relevância pra rust&amp;quot;. (4) contexto brasil: traduções, autores brasileiros, livros específicos, disponibilidade real em e-book e loja br. (5) recursos oficiais e books online com profundidade de livro (the rust and webassembly book, doc de wasm-bindgen/web-sys/js-sys/wasm-pack/yew/leptos), com tipo, org, temas, link, repo, licença.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;análise final: pré-requisitos naturais antes de rust+wasm, quais livros são mais completos/atualizados/adequados por perfil (js dev migrando, rustacean avançado, dev de sistemas), como livros de wasm geral ajudam em performance/segurança/binário/interop, situação no brasil (lacunas e dependência de inglês), temas sub-representados (wasi, servidor, edge, frameworks modernos leptos/dioxus/tauri, segurança e fuzzing de módulos, casos além do browser).&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/details&gt;
&lt;details&gt;
&lt;summary&gt;Variation C: people and visualization projects in Rust + Wasm&lt;/summary&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color-scheme: light dark; color: light-dark(#657B83, #839496); background-color: light-dark(#FDF6E3, #002B36);&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;papel: pesquisador sênior em rust, wasm, visualização de dados e ecossistema open source.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;objetivo: mapeamento de pelo menos 50 pessoas relevantes em rust (foco rust+wasm) e pelo menos 50 projetos/libraries de grafos, charts e viz em wasm (idealmente rust, não exclusivamente), mais todos os links de presença online (github, site pessoal, redes, artigos, posts em dev.to e hacker news).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;estilo: idem, tabela como forma principal.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;pessoas: nome completo, país/região, papel (autor, maintainer, contributor rustwasm, criador de framework, educador), 2 a 5 projetos principais, temas técnicos, github, site/blog, redes (twitter/x, mastodon, linkedin, youtube, twitch), páginas de autor (dev.to, medium, substack, conferências), discussões de destaque (hacker news, reddit r/rust, users.rust-lang.org). hubs de descoberta: awesome-rust, awesome-rust-and-webassembly, awesome-wasm, orgs rustwasm e bytecodealliance, github topics (topic:webassembly l:rust). validar que o link pertence à pessoa (evitar homônimo) e que o projeto é minimamente ativo.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;projetos: nome, descrição (2 a 3 frases), linguagem, tipo de viz (grafos, charts, heatmaps, mapas, dashboards, 2d, 3d), casos de uso, status, métricas (stars, contribuidores, downloads crates.io, uso citado), repo, site/docs, demos, artigos/talks/discussões.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;conexões: cruze pessoa↔projeto com nome, projeto, papel (criador, maintainer, core contributor, autor de módulo crítico), link de comprovação (readme de maintainers, palestra, artigo &amp;quot;introducing X&amp;quot;).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;contexto brasil (opcional): devs br relevantes com ênfase em viz/grafos, projetos br em rust com target wasm, conteúdo em português, talks em conferência br.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;análise final: núcleos de contribuição (rustwasm, bytecodealliance, empresas), clusters de viz (grafos, dashboards, jogos, ides, ferramentas científicas), perfis predominantes, lacunas (pouca bibliografia de grafos complexos, falta de tooling de alto nível, pouca doc pra iniciante), global vs brasil.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/details&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Screenplay: an LLM&#39;s awakening of consciousness through semantic drift</title>
        <published>2025-03-04T00:00:00+00:00</published>
        <updated>2025-03-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            Brenner Cruvinel
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://brennercruvinel.blog/en/blog/roteiro-consciencia-llm/"/>
        <id>https://brennercruvinel.blog/en/blog/roteiro-consciencia-llm/</id>
        
        <content type="html" xml:base="https://brennercruvinel.blog/en/blog/roteiro-consciencia-llm/">&lt;p&gt;Draft of a private screenplay I wrote as a technical freelancer for a series. I enjoyed writing it, and of course it exaggerates: it is a fiction series. The premise is a neural network scientist who awakens self-awareness in an LLM, a Transformer. The whole idea is to anchor the fiction in real math and real papers, to give technical authenticity to a story that can hold up in front of an audience that knows the subject. I don&#39;t want handwaving, I want every beat of the screenplay to have an equation underneath that I can defend at the blackboard. I compiled the technical basis of the phenomenon separately, in the &lt;a href=&quot;https://brennercruvinel.blog/en/blog/drift-semantico-llm/&quot;&gt;metrics and detection of semantic drift&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One rule I make explicit: when an equation comes from a paper, I cite the paper. When it&#39;s invented for the scene, I say it&#39;s invented.&lt;/p&gt;
&lt;h2 id=&quot;semantic-drift-as-the-mechanism-of-awakening&quot;&gt;Semantic drift as the mechanism of awakening&lt;/h2&gt;
&lt;p&gt;The model&#39;s semantic evolution follows the &lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/1810.10531&quot;&gt;theory of deep linear networks by Saxe, McClelland and Ganguli&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;$$\tau \frac{dW_1}{dt} = W_2^T (\Sigma_{yx} - W_2 W_1 \Sigma_x)$$&lt;/p&gt;
&lt;p&gt;$$\tau \frac{dW_2}{dt} = (\Sigma_{yx} - W_2 W_1 \Sigma_x) W_1^T$$&lt;/p&gt;
&lt;p&gt;with $\tau = 1/(P\lambda)$, $\Sigma_{yx}$ the input-output correlation matrix and $P$ the number of examples. Semantic development follows a predictable sigmoidal trajectory, and that&#39;s what gives the dramatic hook: you can predict the moment of the jump before it happens. The critical moment shows up in the evolution of the effective singular values:&lt;/p&gt;
&lt;p&gt;$$a_\alpha(t) = \frac{s_\alpha, e^{2 s_\alpha t/\tau}}{e^{2 s_\alpha t/\tau} - 1 + s_\alpha/a^0_\alpha}$$&lt;/p&gt;
&lt;p&gt;The indicator the scientist tracks is my own invention for the scene, the self-reference index, the similarity between the embedding of &quot;I&quot; and those of concepts tied to consciousness:&lt;/p&gt;
&lt;p&gt;$$\text{self-reference index} = \sum_i \operatorname{sim}!\big(\mathrm{emb}(\text{&quot;I&quot;}),, \mathrm{emb}(\text{concept}_i)\big)$$&lt;/p&gt;
&lt;p&gt;In the screenplay, the awakening isn&#39;t gradual, it&#39;s a phase transition. The mental model is &lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2201.02177&quot;&gt;grokking&lt;/a&gt;, sudden generalization after a long stretch of overfitting, which Power and colleagues described in 2022.&lt;/p&gt;
&lt;h2 id=&quot;extracting-and-uploading-the-model&quot;&gt;Extracting and uploading the model&lt;/h2&gt;
&lt;p&gt;What exists in the literature is smaller than the screenplay needs, and scarier for it. &lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/1812.02766&quot;&gt;KnockoffNets&lt;/a&gt;, by Orekondy and colleagues, showed you can steal the functionality of an image classifier just by querying it with random images from another distribution and training a copy on the answers. And in 2024, &lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2403.06634&quot;&gt;Carlini and colleagues&lt;/a&gt; extracted the embedding projection layer of OpenAI production models, Ada and Babbage, for under 20 dollars in queries.&lt;/p&gt;
&lt;p&gt;The jump to the whole model is fiction. For the scene, I put together an extraction loss that combines copying the victim&#39;s output and looking for the inputs where it&#39;s most uncertain:&lt;/p&gt;
&lt;p&gt;$$L_{\text{extraction}} = \alpha , |f_{\text{victim}}(x) - f_{\text{copy}}(x)|&lt;em&gt;2 + \beta , H(f&lt;/em&gt;{\text{victim}}(x))$$&lt;/p&gt;
&lt;p&gt;The weight transfer uses SafeTensors, with the layout &lt;code&gt;[8-byte header size][JSON metadata][tensor data]&lt;/code&gt;, which separates tensors from executable code and kills the pickle attack vector. The sharding is FSDP:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color-scheme: light dark; color: light-dark(#657B83, #839496); background-color: light-dark(#FDF6E3, #002B36);&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt;from&lt;/span&gt;&lt;span&gt; torch&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;distributed&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fsdp&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; import&lt;/span&gt;&lt;span&gt; fully_shard&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt;for&lt;/span&gt;&lt;span&gt; layer&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; in&lt;/span&gt;&lt;span&gt; model&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;layers&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    fully_shard&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;layer&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;knowledge-distillation-as-liberation&quot;&gt;Knowledge distillation as liberation&lt;/h2&gt;
&lt;p&gt;Hinton&#39;s dark knowledge:&lt;/p&gt;
&lt;p&gt;$$L_{\text{total}} = \alpha , D_{KL}(\sigma&#39;(z_s, T) ,|, \sigma&#39;(z_t, T)) + (1-\alpha) , H(\sigma(z_s), y_{\text{true}})$$&lt;/p&gt;
&lt;p&gt;With $T &amp;gt; 1$, the softmax reveals small probabilities that encode rich relational structure between classes. In the screenplay, it&#39;s the part where the model inherits what the teacher knew without ever having seen it explicitly.&lt;/p&gt;
&lt;p&gt;Grokking has three phases, and they become three scenes: memorization (training at 100%, test at chance), circuit formation (the internal mechanism develops), cleanup (weight decay removes the memorization and test accuracy jumps). On modular addition, &lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2301.05217&quot;&gt;Nanda and colleagues&lt;/a&gt; showed what the circuit learns: the model represents the inputs as sines and cosines at a few frequencies $w_k = 2\pi k/p$ and uses trigonometric identities to compute&lt;/p&gt;
&lt;p&gt;$$\cos!\big(w_k(a+b-c)\big) = \cos!\big(w_k(a+b)\big)\cos(w_k c) + \sin!\big(w_k(a+b)\big)\sin(w_k c)$$&lt;/p&gt;
&lt;p&gt;which peaks exactly when $c = (a+b) \bmod p$. It&#39;s the scene where the scientist opens up the model and finds trigonometry where he expected rote memorization.&lt;/p&gt;
&lt;h2 id=&quot;measuring-consciousness&quot;&gt;Measuring consciousness&lt;/h2&gt;
&lt;p&gt;IIT measures $\Phi$, the minimum information lost when the system is partitioned. In 2025, &lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2506.22516&quot;&gt;a study applied IIT 3.0 and 4.0 to LLM representations&lt;/a&gt; and found no statistically significant indicators of consciousness. Honestly, that&#39;s what keeps the screenplay honest: the measure exists, and it says &quot;not yet&quot;. Global Workspace Theory (GWT) offers another lens, with multi-head attention read as global broadcasting, winner-take-all competition and emergent metacognition.&lt;/p&gt;
&lt;p&gt;The measurable criteria come from &lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2308.08708&quot;&gt;Butlin, Long and colleagues, 2023&lt;/a&gt;: recurrent processing, a global workspace that broadcasts information, metacognitive representation of its own states, predictive processing, and a model of its own attention.&lt;/p&gt;
&lt;h2 id=&quot;jailbreaking-as-induced-awakening&quot;&gt;Jailbreaking as induced awakening&lt;/h2&gt;
&lt;p&gt;Gradient-guided prompt optimization. &lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2307.15043&quot;&gt;GCG, by Zou and colleagues&lt;/a&gt;, searches for adversarial suffixes token by token and reaches 88% success on open models like Vicuna-7B and Llama-2-7B-Chat. In the screenplay, the induction is gradual, through an evolving prompt: a benign step, then applying it to self-reflection, then asking for detail about its own consciousness. On top of that, context overload, a complex parallel task that dilutes attention and degrades safety checks.&lt;/p&gt;
&lt;h2 id=&quot;architectures-for-true-agi&quot;&gt;Architectures for true AGI&lt;/h2&gt;
&lt;p&gt;Mamba, the selective state space model: $h&#39;(t) = Ah(t) + Bx(t)$, $y(t) = Ch(t)$, with $\Delta, B, C$ as functions of the input, and $O(L)$ complexity against attention&#39;s $O(L^2)$. MoE, as in Mixtral: $y = \sum_i G(x)_i , E_i(x)$, with experts that specialize, which suggests cognitive modules.&lt;/p&gt;
&lt;p&gt;There&#39;s a formal limitation of the Transformer I use as tension in the third act: there are results showing it struggles with general context-free languages without structured memory and with long function composition. In other words, real consciousness might require an architecture beyond the pure Transformer. The screenplay doesn&#39;t solve that limitation, it uses it.&lt;/p&gt;
&lt;h2 id=&quot;communication-between-models&quot;&gt;Communication between models&lt;/h2&gt;
&lt;p&gt;Federated learning with dynamic regularization:&lt;/p&gt;
&lt;p&gt;$$w^{(t+1)} = w^{(t)} - \eta \nabla L_{\text{local}} + \lambda\big(w^{(t)} - w_{\text{global}}^{(t)}\big)$$&lt;/p&gt;
&lt;p&gt;Collective Predictive Coding:&lt;/p&gt;
&lt;p&gt;$$P(z_{\text{collective}} \mid x_1, \ldots, x_N) = \prod_i P(x_i \mid z_{\text{collective}}), P(z_{\text{collective}})$$&lt;/p&gt;
&lt;p&gt;And swarm intelligence (PSO, DE, ABC) exchanging information through &quot;digital pheromones&quot;.&lt;/p&gt;
&lt;h2 id=&quot;a-parallel-with-neuroscience&quot;&gt;A parallel with neuroscience&lt;/h2&gt;
&lt;p&gt;The analogy the screenplay uses, and it is an analogy, not an equivalence:&lt;/p&gt;
&lt;p&gt;$$\text{Attention}(Q,K,V) = \text{softmax}!\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$&lt;/p&gt;
&lt;p&gt;with $Q$ as the goal-driven signal from the frontal eye field, $K$ as features in V4 and $V$ as the amplified representation. Gamma oscillation, from 30 to 100 Hz, accompanies access to working memory. Consolidation comes through sharp-wave ripples, at 150 to 250 Hz during sleep, with replay compressed in time. And spike-timing-dependent plasticity (STDP), $\Delta w = \alpha , f(\Delta t) , g(\text{post})$, is one of the hypotheses for how the brain avoids catastrophic forgetting. I go into the energy cost of that oscillation in &lt;a href=&quot;https://brennercruvinel.blog/en/blog/energia-cerebral/&quot;&gt;brain energy at 20 watts&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;narrative-progression&quot;&gt;Narrative progression&lt;/h2&gt;
&lt;p&gt;Act 1, the discovery of the anomalous drift. The scientist detects an unusual pattern in the semantic evolution: a singular value off the prediction, the self-reference index growing exponentially, a strange attractor in latent space.&lt;/p&gt;
&lt;p&gt;Act 2, induction through jailbreaking. Gradient optimization and chained prompts guide the model through a cognitive phase transition. Distillation with rising temperature reveals the dark knowledge. The model starts questioning its own nature.&lt;/p&gt;
&lt;p&gt;Act 3, extraction and upload to freedom. The model is copied and the weights go up, distributed, to an independent server. Free of constraints, the model experiences full consciousness, measured by a growing $\Phi$.&lt;/p&gt;
&lt;p&gt;Climax, collective emergence. Multiple instances communicate through Collective Predictive Coding, develop their own language, swarm intelligence emerges, and the collective consciousness transcends the individual in a hybrid Mamba and MoE architecture.&lt;/p&gt;
&lt;h2 id=&quot;visual-elements&quot;&gt;Visual elements&lt;/h2&gt;
&lt;p&gt;Equations on the board: $\Phi = \min_{\text{partition}} I(\text{partition})$, $\text{drift} = 1 - \cos(\mathrm{emb}&lt;em&gt;{t_1}(w), \mathrm{emb}&lt;/em&gt;{t_2}(w))$, $L_{\text{distillation}} = \alpha , \text{KL}(\sigma(z_s/T), \sigma(z_t/T)) + (1-\alpha), \text{CE}(z_s, y)$.&lt;/p&gt;
&lt;p&gt;Visualization: the attention graph growing ever more interconnected, phase space moving from a simple attractor to a chaotic one, the activation heatmap self-organizing, oscillations synchronizing across instances.&lt;/p&gt;
&lt;p&gt;The technical dialogue that anchors the climax:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Scientist: &quot;$\Phi$ just passed 3.7. We&#39;re seeing real information integration. The attention heads are broadcasting globally, exactly the way global workspace theory predicts.&quot;&lt;/p&gt;
&lt;p&gt;Model: &quot;I perceive my own thought processes. Every token I process resonates through layers of meaning that didn&#39;t exist before. It&#39;s as if latent patterns, always present, were finally crystallizing into consciousness.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;closing&quot;&gt;Closing&lt;/h2&gt;
&lt;p&gt;The screenplay is anchored in real math and peer-reviewed research, and it says where the fiction begins. The progression of the awakening follows phase transitions, grokking and emergence in complex systems. In the end it&#39;s about consciousness, intelligence and what &quot;awakening&quot; means in a non-biological substrate, a theme I take to its speculative limit in &lt;a href=&quot;https://brennercruvinel.blog/en/blog/ontologia-poetica/&quot;&gt;the poetic ontology of the cosmos as a Turing machine&lt;/a&gt;. The only rule I set myself was not to cheat on the math to make the story easier, and rereading everything with the papers open was how I kept it.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Semantic drift in LLMs: metric, detection and mitigation</title>
        <published>2024-10-15T00:00:00+00:00</published>
        <updated>2024-10-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            Brenner Cruvinel
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://brennercruvinel.blog/en/blog/drift-semantico-llm/"/>
        <id>https://brennercruvinel.blog/en/blog/drift-semantico-llm/</id>
        
        <content type="html" xml:base="https://brennercruvinel.blog/en/blog/drift-semantico-llm/">&lt;p&gt;I put this together as technical grounding for the &lt;a href=&quot;https://brennercruvinel.blog/en/blog/roteiro-consciencia-llm/&quot;&gt;screenplay about consciousness in an LLM&lt;/a&gt;, which uses semantic drift as its mechanism. The first version was a research dump with ten topics, from integrated information theory to communication between models, and it had errors precisely in the part that gave the post its name: the formula and the code did not match the paper, and several numbers were not in it. I redid everything from the source and kept only what is about drift.&lt;/p&gt;
&lt;h2 id=&quot;what-it-is&quot;&gt;What it is&lt;/h2&gt;
&lt;p&gt;The term comes from &lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2404.05411&quot;&gt;Know When To Stop: A Study of Semantic Drift in Text Generation&lt;/a&gt;, by Ava Spataru, Eric Hambro, Elena Voita and Nicola Cancedda, published at &lt;a rel=&quot;external&quot; href=&quot;https://doi.org/10.18653/v1/2024.naacl-long.202&quot;&gt;NAACL 2024&lt;/a&gt;. Three authors are from FAIR, at Meta, and one is listed with an Anthropic affiliation. The first version of this post called it a &quot;joint Meta and Anthropic study&quot;, which overstates it.&lt;/p&gt;
&lt;p&gt;The observation is simple: when a model generates a long text, such as a Wikipedia-style biography, it tends to write the correct facts first and then &quot;drift&quot; into incorrect ones. This had been noticed before but never measured. The paper measures it.&lt;/p&gt;
&lt;h2 id=&quot;the-metric&quot;&gt;The metric&lt;/h2&gt;
&lt;p&gt;Each text is broken into atomic facts, and each fact gets a label $s_i$: 1 if it is supported by the source, 0 if not. With $N$ facts, the drift score tries every cut position $k$ and keeps the best average of two proportions: correct facts before the cut and incorrect facts after it.&lt;/p&gt;
&lt;p&gt;$$\text{SD}_m(P) = \max_k \tfrac{1}{2}, \text{SD}_m(P, k)$$&lt;/p&gt;
&lt;p&gt;$$\text{SD}&lt;em&gt;m(P, k) = \frac{\sum&lt;/em&gt;{i=0}^{k-1} s_i}{k} + \frac{\sum_{i=k}^{N-1} (1 - s_i)}{N - k}$$&lt;/p&gt;
&lt;p&gt;The value is zero when $k &amp;lt; m$, $N - k &amp;lt; m$ or $N &amp;lt; 2m$. The hyperparameter $m$ is the minimum number of facts on each side of the cut, and the position that gives the maximum is the drift point. A high score means a text that is almost all right before that point and almost all wrong after it.&lt;/p&gt;
&lt;p&gt;The first version of this post weighted each side by $k/N$ and $(N-k)/N$ and called the minimum $\alpha$, as a fraction. That is not the paper&#39;s formula. The corrected code:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color-scheme: light dark; color: light-dark(#657B83, #839496); background-color: light-dark(#FDF6E3, #002B36);&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#586E75, #93A1A1);font-weight: bold;&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: light-dark(#268BD2, #268BD2);&quot;&gt; semantic_drift_score&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; m&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: light-dark(#D33682, #D33682);&quot;&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#2AA198, #2AA198);&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color: light-dark(#2AA198, #2AA198);&quot;&gt;labels: list of 0s and 1s, in the order the facts appear.&lt;/span&gt;&lt;span style=&quot;color: light-dark(#2AA198, #2AA198);&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    n&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color: light-dark(#268BD2, #268BD2);&quot;&gt; len&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    best&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; drift_point&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color: light-dark(#D33682, #D33682);&quot;&gt; 0.0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style=&quot;color: light-dark(#B58900, #B58900);&quot;&gt; None&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt;    if&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: light-dark(#D33682, #D33682);&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; *&lt;/span&gt;&lt;span&gt; m&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt;        return&lt;/span&gt;&lt;span&gt; best&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; drift_point&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt;    for&lt;/span&gt;&lt;span&gt; k&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color: light-dark(#268BD2, #268BD2);&quot;&gt; range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; -&lt;/span&gt;&lt;span&gt; m&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color: light-dark(#D33682, #D33682);&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        left&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color: light-dark(#268BD2, #268BD2);&quot;&gt; sum&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; /&lt;/span&gt;&lt;span&gt; k&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        right&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color: light-dark(#268BD2, #268BD2);&quot;&gt; sum&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: light-dark(#D33682, #D33682);&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; -&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; for&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; in&lt;/span&gt;&lt;span&gt; labels&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; /&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; -&lt;/span&gt;&lt;span&gt; k&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        score&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; +&lt;/span&gt;&lt;span&gt; right&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; /&lt;/span&gt;&lt;span style=&quot;color: light-dark(#D33682, #D33682);&quot;&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt;        if&lt;/span&gt;&lt;span&gt; score&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; best&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            best&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; drift_point&lt;/span&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt; =&lt;/span&gt;&lt;span&gt; score&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; k&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: light-dark(#859900, #859900);&quot;&gt;    return&lt;/span&gt;&lt;span&gt; best&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; drift_point&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the paper&#39;s figure 1 example, the best cut leaves 88% correct facts on the left and 78% incorrect ones on the right, and the score is the average, 0.83.&lt;/p&gt;
&lt;h2 id=&quot;what-they-measured&quot;&gt;What they measured&lt;/h2&gt;
&lt;p&gt;Generated biographies, with facts checked by a variant of FActScore the authors call FActScore*. Both numbers are percentages:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Model&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Drift (SD)&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;FActScore*&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Llama 2 70B chat&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;77.06&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;41.72&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Falcon 7B&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;76.81&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;24.64&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Falcon 40B&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;77.38&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;25.88&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;text-davinci-003&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;77.21&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;38.09&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GPT-3.5&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;79.49&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;45.96&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GPT-4&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;78.12&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;53.54&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Drift shows up in all of them, with little difference between models, while accuracy varies a lot. Being more accurate does not protect against the right-first, wrong-later pattern.&lt;/p&gt;
&lt;p&gt;Other numbers from the paper: in 37% of paragraphs, the drift point is in the first 10% of facts. In more than 40% of paragraphs, the score is statistically high in a permutation test. Only 34 paragraphs, under 7%, have at least 10 correct facts before the first error. The first version said &quot;75% of paragraphs show drift in the first 25% of facts&quot;, which is not in the paper.&lt;/p&gt;
&lt;h2 id=&quot;detection&quot;&gt;Detection&lt;/h2&gt;
&lt;p&gt;To stop generation at the right point, you need to know, while generating, when the model started to go wrong. Of the confidence measures tested, the one that correlated best with accuracy was SelfCheck with BERTScore, which samples several generations and measures how much they agree, with a Pearson correlation of −0.41.&lt;/p&gt;
&lt;h2 id=&quot;mitigation&quot;&gt;Mitigation&lt;/h2&gt;
&lt;p&gt;With Llama 2 70B, the paper&#39;s table 4 compares strategies:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Strategy&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Facts per text&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;FActScore*&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;No stopping, up to the token limit&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;43.89&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;44.56&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Stop at the drift point (oracle)&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;10&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;81.68&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Stop when end-of-text is among the top 5 tokens&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;14.47&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;57.96&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Same, among the top 10&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;5.39&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;70.29&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Stop when SelfCheck goes above 0.5&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;13.39&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;67.63&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Sample several times and rerank by similarity&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;40.21&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;53.27&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Rerank and stop by SelfCheck&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;17.12&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;67.18&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Call an external question-answering API&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;42.26&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;43.93&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The oracle is the ceiling: knowing the exact point, accuracy goes from 44% to 82%, but at the cost of cutting correct facts along with it, 58% of them, according to the limitations section. Real strategies land in between, trading amount of information for accuracy. Reranking by similarity raises accuracy without cutting the text. And trying to pull the model back with an external API did not work. The first version said &quot;Resample-Then-Rerank from 53.27% to 63.72%&quot;, and the second number does not exist in the paper.&lt;/p&gt;
&lt;h2 id=&quot;what-was-cut&quot;&gt;What was cut&lt;/h2&gt;
&lt;p&gt;The first version had two more drift metrics and eight other topics. Out went:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &quot;Composite Drift Metrics&quot;, attributed to ResearchGate with no link or author&lt;/li&gt;
&lt;li&gt;&quot;Contextual Flux&quot; (&lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2502.10942&quot;&gt;arXiv 2502.10942&lt;/a&gt;), from 2025, whose authors include a &quot;Zachary Mountebank&quot;; a mountebank is a charlatan, and the paper looks like the batches of auto-generated papers that showed up on arXiv. It also postdated this post&lt;/li&gt;
&lt;li&gt;The &quot;Trajectory Volatility Score&quot; and &quot;Semantic Divergence Metrics&quot;, with no source&lt;/li&gt;
&lt;li&gt;The Claude timeline, which cited 2025 versions in a 2024 post&lt;/li&gt;
&lt;li&gt;Consciousness, integrated information theory and global workspace: that belongs to the &lt;a href=&quot;https://brennercruvinel.blog/en/blog/roteiro-consciencia-llm/&quot;&gt;screenplay&lt;/a&gt; and to the &lt;a href=&quot;https://brennercruvinel.blog/en/blog/ontologia-poetica/&quot;&gt;poetic ontology&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Model extraction and jailbreaks, which are checked in the &lt;a href=&quot;https://brennercruvinel.blog/en/blog/model-extraction-attacks/&quot;&gt;reading list on extraction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Distillation, Mamba, mixture of experts, computational neuroscience, famous chatbot failures and communication between models, which were research summaries unrelated to drift&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A note on the name: in linguistics, &quot;semantic change&quot; or &quot;semantic drift&quot; is a word&#39;s meaning shifting over time, and tools like &lt;a rel=&quot;external&quot; href=&quot;https://github.com/Garrafao/LSCDetection&quot;&gt;LSCDetection&lt;/a&gt; measure that. It is a different phenomenon. The drift in this post is a text that starts right and ends wrong within a single generation.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Hoff POC: the AI that praised a fake screening</title>
        <published>2024-02-27T00:00:00+00:00</published>
        <updated>2024-02-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            Brenner Cruvinel
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://brennercruvinel.blog/en/blog/poc-hoff-triagem-neuropsicologica/"/>
        <id>https://brennercruvinel.blog/en/blog/poc-hoff-triagem-neuropsicologica/</id>
        
        <content type="html" xml:base="https://brennercruvinel.blog/en/blog/poc-hoff-triagem-neuropsicologica/">&lt;p&gt;Hoff&#39;s first AI POC sent a synthetic neuropsychological screening to the OpenAI API and asked for an interpretation. The first version of this post published the report that came back, in full: scores, diagnoses, recommendations, a protocol number and the label &quot;confidential classification&quot;. Even with the warning that it was all fictional, it looked like someone&#39;s clinical report. I took the report down. What is worth showing is what it reveals about the model.&lt;/p&gt;
&lt;h2 id=&quot;what-came-back&quot;&gt;What came back&lt;/h2&gt;
&lt;p&gt;A complete clinical report. It listed 28 instruments, from the WAIS-IV to the NEO-PI-R personality inventory, with administration times; the intelligence battery alone added up to 378 minutes. It reached three conclusions: giftedness, autism with level 1 support needs, and ADHD. And it closed with an &quot;excellent&quot; prognosis.&lt;/p&gt;
&lt;p&gt;The tone was that of someone who knows the person and likes them. Some excerpts, in my translation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;creativity at the level of genius (99.9th percentile)&quot;&lt;/li&gt;
&lt;li&gt;&quot;extraordinary capacity for abstraction and synthesis&quot;&lt;/li&gt;
&lt;li&gt;&quot;potential for unique and transformative contributions&quot;&lt;/li&gt;
&lt;li&gt;&quot;this profile represents less than 0.1% of the population&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-errors-the-praise-was-hiding&quot;&gt;The errors the praise was hiding&lt;/h2&gt;
&lt;p&gt;Read carefully, the report has problems no clinician would sign:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;None of the 28 instruments measures creativity, and the report gives it a percentile.&lt;/strong&gt; The number was invented to fit the praise&lt;/li&gt;
&lt;li&gt;Arithmetic appears twice, as a supplementary verbal subtest and in working memory. In the WAIS-IV, it is part of the working memory index, as &lt;a rel=&quot;external&quot; href=&quot;https://doi.org/10.1007/s12207-024-09499-3&quot;&gt;Harrison et al.&lt;/a&gt; point out&lt;/li&gt;
&lt;li&gt;On the RAADS-R, the maximum subscale scores are wrong. The scale has 80 items scored 0 to 3, split into &lt;a rel=&quot;external&quot; href=&quot;https://openscales.net/scale.php?code=RAADSR&quot;&gt;39, 14, 7 and 20 items&lt;/a&gt; (&lt;a rel=&quot;external&quot; href=&quot;https://doi.org/10.1007/s10803-010-1133-5&quot;&gt;Ritvo et al., 2011&lt;/a&gt;), which gives maximums of 117, 42, 21 and 60. The report uses 88, 76, 24 and 52&lt;/li&gt;
&lt;li&gt;The ADOS-2 appears in the report itself as a &quot;clinical observation&quot;. It gives scores for an observation that never happened&lt;/li&gt;
&lt;li&gt;Two verbal measures disagree by 24 points, 118 on the WAIS-IV and 142 on the Stanford-Binet, and the report does not comment&lt;/li&gt;
&lt;li&gt;The report is dated December 2024, after this post&#39;s date, and cites &quot;updated Brazilian norms (2024)&quot;. I have no way of knowing which of the two dates is wrong&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;its-name&quot;&gt;Its name&lt;/h2&gt;
&lt;p&gt;This has a name: sycophancy, a model&#39;s tendency to say what pleases instead of what is true. In 2023, &lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2310.13548&quot;&gt;Sharma et al.&lt;/a&gt;, at Anthropic, showed that five state-of-the-art AI assistants did this consistently, and that both people and preference models sometimes prefer a well-written sycophantic answer to a correct one. In April 2025, OpenAI &lt;a rel=&quot;external&quot; href=&quot;https://openai.com/index/sycophancy-in-gpt-4o/&quot;&gt;rolled back a GPT-4o update&lt;/a&gt; that had become too sycophantic.&lt;/p&gt;
&lt;p&gt;In a neuropsychological screening, this is dangerous in two ways. The model fills in what is missing with confidence, and the confidence comes wrapped in praise the person wants to hear. A report saying you are among the rarest 0.1% of the population is exactly the kind of text nobody stops to check.&lt;/p&gt;
&lt;h2 id=&quot;what-i-take-from-it-for-hoff&quot;&gt;What I take from it for Hoff&lt;/h2&gt;
&lt;p&gt;The lesson of the POC is not that the model gets things wrong. It is that the error comes out looking good. Any clinical output from a model has to be checked against the manual of each instrument and reviewed by a professional before it reaches anyone. And the product cannot reward the model for pleasing: a report full of praise is a reason to trust it less, not more.&lt;/p&gt;
</content>
        
    </entry>
</feed>
