<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Fang-Pen&apos;s coding note</title>
    <description>Fang-Pen Lin&apos;s blog about programming</description>
    <link>https://fangpenlin.com/</link>
    <atom:link href="https://fangpenlin.com/feed.xml" rel="self" type="application/rss+xml" />
    
      <item>
        <title>No, LLM is not going to replace software engineers, here&apos;s why</title>
        <description>&lt;style type=&quot;text/css&quot;&gt;
  figure {
    text-align: center;
    margin: 0 auto;
  }
  figcaption, figcaption p {
    color: grey;
    font-size: 0.9em;
  }
  figure img {
    max-width: 100%;
  }
&lt;/style&gt;

&lt;p&gt;Today, I’d like to share my theory about why LLMs cannot replace software engineers, based on my experience and observations.
Who am I to talk about this topic, you may ask.
Well, not much, except that I have spent more than two decades of my life programming, almost every single day, long before GPT was a thing.
You can check my &lt;a href=&quot;https://github.com/fangpenlin&quot;&gt;GitHub profile&lt;/a&gt;, but it only captures one decade+ (from when git was a thing), and those are just GitHub repos not including all proprietary repos I have worked on.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/decade-on-github.png&quot; alt=&quot;A screenshot of my GitHub profile contribution graph spanning roughly a decade, showing regular activity over time. It reflects only public GitHub work, not proprietary repositories.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;More than a decade of GitHub contributions on my public profile. This is only what GitHub can see (public + any private contributions I opted to show), not the proprietary repos I’ve worked in.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;These are all organic commits. I never commit a single line of code just to make my GitHub profile look green, because that’s stupid.
Before LLMs, I wrote almost all my code keystroke by keystroke. Sounds crazy, right? 🤣&lt;/p&gt;

&lt;p&gt;Yep, that’s how things used to be.
And it can last for so long only because I truely enjoy programming.
I’ve built countless software projects: backend, frontend, mobile, data pipelines, browser extensions, infrastructure as code, and even trained AI models from scratch, and so on.
After using LLMs to speed up coding, I have to say: if you use them the right way, they can make you many times faster.
But it’s not all sunshine and rainbows, if you rely on them too much, programming becomes painful, and it chips away at your ability to think like an engineer from first principles.
I did some soul-searching and found that I still enjoy programming, so I intentionally handwrite code from time to time to keep the muscle strong.&lt;/p&gt;

&lt;p&gt;Like many of my fellow software engineers, I used to panic a bit when I saw machines spitting out code like crazy fast.
Are my experiences from the past two decades all in vain?
But the truth is, the more I learn about LLMs, the more I use them, the more I realize it is not like many claim, that they could replace software engineers in 12 months.
I didn’t buy into the hype, but at the same time, I don’t feel desperate. Instead, I feel cautiously optimistic about the future of software engineering.&lt;/p&gt;

&lt;p&gt;Because, simply put: no. Despite many tech companies committing collective suicide by drinking the Kool-Aid, LLMs are not going to replace software engineers.
Most people confuse the idea of coding with software engineering.
LLMs surely have a huge impact on the software industry: they speed up many things greatly, but like any tool, there is a trade-off.
Now the cost of making sloppy software goes down almost to zero, but that does not mean software engineers will disappear.
Let’s see why I think it is this way.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;entropy-llms-cannot-escape-the-information-density-of-the-software-spec&quot;&gt;Entropy, LLMs cannot escape the information density of the software spec&lt;/h2&gt;

&lt;p&gt;One of the main reasons why LLMs cannot replace software engineers is the limitation imposed by the information density of the software spec, i.e., &lt;a href=&quot;https://en.wikipedia.org/wiki/Entropy_(information_theory)&quot;&gt;Shannon entropy&lt;/a&gt;.
Software development is a process of discovering the requirements and removing the uncertainties.
Usually, we start with a rough idea about the system, for example, say we want to build a login page.
You can tell the LLM&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;hey, build me a login page&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The concept of a login page is a well‑known idea to LLMs because there are countless login pages implemented in various programming languages.
However, when you tell an LLM to build such a page, the model does not have any context about the details of your specific login page.
Therefore, for the system you are building, there is a high level of uncertainty.
An LLM will generate the most likely code based on statistical norms embedded in its weights from the training data.
With a non‑zero &lt;a href=&quot;https://huggingface.co/docs/transformers/main_classes/text_generation#transformers.GenerationConfig.temperature&quot;&gt;temperature&lt;/a&gt; (the level of randomness of picking the next token from the list of token candidates) plus the internal statistical model, there is a distribution of plausible outcomes.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/entropy-login-page.svg&quot; alt=&quot;A spectrum of plausible login pages when the spec is just &apos;build me a login page&apos;: little entropy (information) provided implies high uncertainty about the outcome.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A spectrum of plausible login pages when the spec is just &quot;build me a login page&quot;. Little entropy (information) provided → high uncertainty about the outcome.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;At this point, you have offered little entropy (information) about the system requirements.
Because of the lack of information, there is high uncertainty about the system. You are basically rolling a die.
Due to the similarity between using LLMs to generate code and gambling, some people on X even came up with a &lt;a href=&quot;https://x.com/shiri_shh/status/2006751251036590354&quot;&gt;funny comparison to gambling&lt;/a&gt;:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/vibe-coding-gambling.png&quot; alt=&quot;A meme comparing vibe coding with LLMs to gambling/slot machines.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A meme comparing vibe coding with LLMs to gambling/slot machines. Source: &lt;a href=&quot;https://x.com/shiri_shh/status/2006751251036590354&quot;&gt;original post on X&lt;/a&gt;.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The end result may not be the best login page in the world, but it will probably come with very basic stuff and it might just work.
If you do not like the look of the login page, you can then supply more entropy about the software spec.
Say, you want the login button to be blue.
Now your login page spec goes from just a login page to&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;a login page with a blue login button&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By trading in more entropy (information), the uncertainty of the system is reduced.
But here comes yet another problem: what kind of blue?
There are so many shades of blue; it’s a big range.
You can either roll the dice multiple times, or you can put it in the prompt directly, saying you want a particular type of blue, like Baby Blue, for example.
Whether you are rolling the dice multiple times and pick one or supplying the details in the prompt directly, it is essentially the same thing: you are supplying more entropy (information) to the system spec and reducing uncertainty.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/entropy-login-page-blue-button.svg&quot; alt=&quot;A spectrum of plausible login pages after adding one constraint (&apos;the login button is blue&apos;): more entropy in the spec narrows the distribution, but uncertainty remains.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Adding one constraint (&quot;the login button is blue&quot;) injects more entropy into the spec and narrows the output distribution, uncertainty decreases, but doesn&apos;t disappear (there are still many &quot;blues&quot;).&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;After some iterations, now you have a beautiful login page with a blue button that meets your taste.
As a person who cares about security, you might want a two‑factor authentication feature after the user inputs their username and password as an extra layer of defense.
Now, your software spec has become&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;a login page with a Baby Blue login button; after user login we need to prompt for two-factor authentication&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well, once again, there are many two‑factor authentication mechanisms to choose from. It could be hardware-based like YubiKey, biometric info like fingerprints, facial recognition, SMS, or email one-time passwords.
There are so many options. Which one would you like the LLM to build?&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/entropy-login-page-blue-button-tfa.svg&quot; alt=&quot;A spectrum of plausible login pages under more specific requirements (e.g. a specific blue and a two-factor authentication flow): more entropy is provided and a smaller space of plausible implementations is imposed.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;When the requirements are more specific (e.g. a specific blue + a two‑factor auth flow), more entropy is provided in the spec and a smaller space of plausible implementations is imposed.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;You see, this is a very simplified version of software development. We can play this game forever: keep adding new requirements and ask the LLM to generate code for us.
But here comes another question: why blue, but not yellow?
And if we are going to implement email one‑time passwords, why not get rid of the username and password, relying only on emailing the code?
In fact, many software systems do that. For example, &lt;a href=&quot;https://slack.com&quot;&gt;Slack&lt;/a&gt; has chosen a passwordless approach as its authentication method.
The different choices present different paths you can take in building the system.
If there are parallel universes, there could be multiple versions of you making different decisions about system design.
If we draw the diagram again for each of them over time, you will see something like this:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/entropy-login-page-tree.svg&quot; alt=&quot;A &apos;parallel universes&apos; mental model: the login-page spec can branch into a tree as you add requirements and make design choices, representing many plausible paths you could have taken.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;This is &lt;em&gt;not&lt;/em&gt; an exhaustive map of every possible login page, it&apos;s a mental model under a &quot;parallel universes&quot; thought experiment. As you add requirements and make design choices, the &quot;spec&quot; can branch into a tree, representing the many plausible paths you could have taken.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;All possibilities for building a system could span a tree structure.
The trend is very obvious: over time, with more entropy supplied, uncertainty goes down.&lt;/p&gt;

&lt;h3 id=&quot;the-system-spec-data-density-grows-exponentially-with-the-software-usage-and-the-value-it-provides&quot;&gt;The system spec data density grows exponentially with the software usage and the value it provides&lt;/h3&gt;

&lt;p&gt;You may ask&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Why does this have anything to do with LLMs not being able to replace software engineers?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;First of all, while LLMs are very good at generating code based on the instructions you give them, they are not good at discovering and validating the spec by themselves in the real world.
You still need someone (or some accountable process) to collect feedback, get hands-on with the system, arbitrate trade‑offs, and feed more details into the software spec to reduce uncertainty.&lt;/p&gt;

&lt;p&gt;And the more details in the software spec, the more information you need to provide to make it more certain.
The density of information about the software requirements is something you cannot escape with LLMs.
Shannon showed us (via &lt;a href=&quot;https://en.wikipedia.org/wiki/Information_theory&quot;&gt;information theory&lt;/a&gt;) that there is a limit to lossless data compression: you can’t, in general, represent arbitrary information with fewer bits than its inherent information content.
In practice we often “compress” specs by leaning on conventions and shared references (framework defaults, “make it like Stripe checkout,” existing libraries, existing test suites).
But the moment you deviate from those well‑known paths, the missing information has to be supplied somewhere (prompt, tests, code, or human decisions).
No matter how hard you prompt, you still need to find a way to convey the idea of your system, either in plain English, tests, or in programming language.&lt;/p&gt;

&lt;p&gt;Certainly, LLMs can translate your English prompt into code, empowering many people who do not know how to code. Now they can at least build something.
For people who already know how to program, it also saves a tremendous amount of time from mechanical work.
But regardless, there is no way to consistently convey an idea with less information than is theoretically necessary to disambiguate it.
In other words, LLMs act as an amplifier in coding and drafting, while finding, negotiating, and defining clear requirements remains a hard, human‑in‑the‑loop challenge.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I do not care about the login page, as long as it works, I am fine with it&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I heard you yelling at me.
Well, the login page is just an overly simplified example to help the reader understand the concept of software development and the relationship with information entropy.
Sure, if you do not care about the actual output of the system, then indeed LLMs can do a great job, because they can generate something that looks like it works the way you want.
In fact, not all subsystems in a software project are equally important.
Even if a page looks ugly, the code is a mess, but it works, who cares?&lt;/p&gt;

&lt;p&gt;That kind of statement usually comes from people who vibe‑coded side projects which have nothing to lose.
Based on my own experience, as the usage of a software system grows and the business value it produces increases, so do the requirements.
And the growth of requirements does not come in a linear fashion; it usually grows exponentially, or at least super‑linearly.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/sepc-growth.svg&quot; alt=&quot;A diagram showing system spec requirements growing non-linearly (roughly exponentially) as software usage and business value increase.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;As usage (and the business value on top of it) increases, the amount of system spec/requirements tends to grow non‑linearly, often super-linear or exponential in practice.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Say you have a website that serves one hundred people per day and makes no money, versus a website serving 10 million users per day across the globe with a business that makes big money on top of it, the requirements would be completely different.
For the website with very few users, nobody really cares if the website goes down, certainly nobody cares that much about the color of your login buttons.
But for a 10M DAU (daily active users) website, I can easily name many obvious requirements:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Security requirements&lt;/strong&gt;: ensure user data will not leak, because now it has real-life impact&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Performance requirements&lt;/strong&gt;: with high load, you need to serve that many people efficiently&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Reliability requirements&lt;/strong&gt;: SLAs with customers, uptime requirements, etc.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Compliance requirements&lt;/strong&gt;: GDPR, different local laws, data retention requirements, etc.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Requirements from legacy code&lt;/strong&gt;: given that the website has grown from nothing to 10M, there must be legacy code some users still depend on; you still need to keep it around for some time&lt;/li&gt;
  &lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The list can go on and on.
Just the sheer amount of information in those requirements is tremendous.
Even if there were a perfect LLM that could translate your software spec into code flawlessly, you would still need to provide those specs to the LLM.
So the bottleneck doesn’t disappear; it shifts.
If the hard part is specifying and verifying behavior under real‑world constraints, then “replacing engineers” would require replacing that spec/verification work too, not just typing code faster.
This is why I think it is unrealistic for LLMs (as we use them today) to replace software engineers: that’s not what they are designed for.&lt;/p&gt;

&lt;p&gt;There are different types of people claiming LLMs can replace software engineers.
One type is people who have a stake in LLM model companies, they need a story to keep the VC money flowing.
Another type is people who have software with nothing to lose, of course LLMs could work great in that case:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/nothing-to-lose-zone-sepc-growth.svg&quot; alt=&quot;The same spec-growth curve with the low-usage, low-stakes region highlighted (the &apos;nothing to lose&apos; zone).&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;The low‑usage, low‑stakes &quot;nothing to lose&quot; zone is where LLMs shine in terms of generating low-quality software quickly. But once the software has something to lose, the sheer amount of requirements can make LLMs less effective, because the entropy of the requirements might be as big as writing the code yourself.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;functional-requirements-are-easy-nonfunctional-requirements-are-the-real-challenges&quot;&gt;Functional requirements are easy, non‑functional requirements are the real challenges&lt;/h3&gt;

&lt;p&gt;So far, we have been talking about the software spec.
As you see in our login page example, we only use functional requirements as examples, because they are very easy to define and verify.
However, as you saw when we discussed potential requirements for a large-scale website, most of these requirements are not even functional.
There is a joke surfacing around X capturing the irony perfectly:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;To let AI build a secure system, you should tell AI:&lt;/p&gt;

  &lt;p&gt;Build a secure system, make no mistake&lt;/p&gt;

  &lt;p&gt;Do not forget the important part “make no mistake”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This prompt carries almost no entropy at all.
A requirement like this is extremely hard to define in simple terms, because it is not a single piece of code that you can magically generate and the whole system would be secure because of that.
The security of a system relies on whether every single line of code is doing the right thing, while taking all possible attack vectors into consideration.
It even includes all the third‑party libraries, your upstream vendors, and how they handle security from their ends.
And of course, there is no unbreakable system, you also need to consider the value of the compromised system and the potential cost for the attackers.&lt;/p&gt;

&lt;p&gt;A simple prompt like that implies a full inspection of every single line of code on a potential attack surface.
And there is no easy way to verify whether you are doing it right or wrong.
There is also an implication of taking user mistakes into consideration.
System requirements such as performance share similar challenges, they are hard to define and verify.
Also, while functional requirements are usually only impacting a local subsystem, non‑functional requirements usually have a global impact on the whole system.
For example, you may have multiple subsystems running at the same time, and eventually there is a final task that collects the result and reports it to the user.
The actual performance of the whole system is defined by the slowest one:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/minimum-tonne.png&quot; alt=&quot;A barrel diagram where the water level is limited by the shortest stave, illustrating that overall performance is constrained by the weakest component.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A &quot;shortest barrel stave&quot; view of system performance: like Liebig&apos;s barrel, the overall capacity is limited by the weakest part, not the average. One slow or fragile subsystem can cap the whole system (same &quot;weakest link&quot; idea as &lt;a href=&quot;https://en.wikipedia.org/wiki/Liebig%27s_law_of_the_minimum#Liebig%27s_barrel&quot;&gt;Liebig&apos;s law of the minimum&lt;/a&gt;).&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Security and compliance are similar: if you have any weakness in the system, or anything that violates compliance, the end result is the same: a breach or a compliance violation. It does not matter how good your other parts are doing.&lt;/p&gt;

&lt;p&gt;Now you see: given how hard and wide-ranging the non‑functional spec’s impact on a system is, the required entropy to define such a system is much bigger than for functional requirements.
We will talk more about this in the hidden context and software verification sections.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/tip-of-icebrug.jpg&quot; alt=&quot;Tip-of-the-iceberg diagram showing visible functional requirements above the waterline and large hidden non-functional requirements below.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Software requirements are like an iceberg: the visible functional spec above the waterline is only the tip, while the much larger mass of non‑functional constraints (security, reliability, compliance, operability, etc.) sits hidden below the surface and dominates the true complexity.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;programming-code-is-also-spec-and-reading-code-is-harder-than-writing-code&quot;&gt;Programming code is also spec, and reading code is harder than writing code&lt;/h3&gt;

&lt;p&gt;Usually, in a healthy software system you will have high level human readable language, like &lt;a href=&quot;https://en.wikipedia.org/wiki/Software_design_document&quot;&gt;design docs&lt;/a&gt;, potentially plus &lt;a href=&quot;https://en.wikipedia.org/wiki/Behavior-driven_development&quot;&gt;BDD&lt;/a&gt; (behavior‑driven development) / &lt;a href=&quot;https://en.wikipedia.org/wiki/End-to-end_testing&quot;&gt;e2e&lt;/a&gt; (end‑to‑end) tests to define how the system should work from a high level.
Then, software engineers will write code based on the system spec.
People forget, we came all the way from &lt;a href=&quot;https://en.wikipedia.org/wiki/Machine_code&quot;&gt;machine code&lt;/a&gt; to &lt;a href=&quot;https://en.wikipedia.org/wiki/High-level_programming_language&quot;&gt;high-level programming languages&lt;/a&gt;.
It’s already very high-level compared to what it used to be.
The code itself is also spec, it tells the machine how to behave.
So the programming code itself also carries a big portion of the entropy about how the system should behave, in detail of course.
It might be something like 10% / 90% to 20% / 80%.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/software-spec.svg&quot; alt=&quot;Pyramid diagram with a small high-level system spec at the top and a large base of detailed code at the bottom, connected by a downward arrow.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A pyramid view of software: a relatively small, high‑level system spec at the top, and a much larger volume of detailed code at the bottom that implements and refines that spec.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;LLMs indeed changed the game.
Now, instead of writing the 80% of code yourself, it is possible that you only write the top 10% ~ 20% of the system spec, and let LLMs generate the code for you.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/software-spec-with-ai-gen-code.svg&quot; alt=&quot;Pyramid diagram with high-level system spec at the top and a base of AI-generated code with question marks at the bottom.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;The same pyramid, but with the bottom filled by AI‑generated code: the high‑level spec is still small, yet a large volume of detailed code appears underneath it. Without careful review, that code is annotated with question marks.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This feels like magic, because now it seems like it saves you 80% of the work of writing code yourself.
However, the problem is, the code is generated as the most likely arrangement of tokens based on the given prompt and context.
It is an average of what the code might look like.
Before someone reviews the code and verifies the intent of the generated code, whether it really meets all the requirements defined in the spec, and whether there are any bugs in the code, it is still just plausible code based on the spec.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/dlss-5.jpg&quot; alt=&quot;Left: low-quality image of NVIDIA CEO; right: AI-upscaled result that is clearly not the same person&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Left: low-quality image of the CEO of NVIDIA. Right: AI upscaling result of DLSS 5, obviously not the CEO of NVIDIA. It&apos;s a joke, obviously 😅 &lt;a href=&quot;https://x.com/micheevs5/status/2033788255955566701&quot;&gt;Originally from this X post&lt;/a&gt;; I modified it slightly so the point is clear. AI can resize or enhance images, but when the original data is not there, it can only guess and may invent details that do not exist, much like generating a large amount of source code from a small portion of spec.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If your software has nothing to lose (no users, no customers), most vibe coders would just stop here.
Because it seems like it is doing what they want it to do. In that case, they do not care about quality.
We just mentioned in the previous section, when the usage and business value of the software system grows, so do the requirements.
If the vibe coder is lucky enough and their software is no longer a toy but a business, they face a problem, they either take it seriously and fix the critical problems, like performance, security and compliance, or risk losing everything they have just gained.
Now the software has something to lose.
You are forced to add more software requirements into the system spec, be it performance, security or anything else.&lt;/p&gt;

&lt;p&gt;If you just vibe‑coded the system, have zero clue what the code looks like, but you do have the spec, one obvious idea is to add all your requirements into the spec and let the LLM generate the code again for you.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/software-spec-too-big.svg&quot; alt=&quot;Pyramid diagram with an oversized high-level system spec block on top and a very small code block at the bottom, suggesting a mismatch between spec entropy and implementation.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;An extreme case: a huge, detailed system spec sitting on top of a tiny amount of code. When the entropy in the spec vastly exceeds what the code actually covers, it might be much easier to just write your software spec in the code as using English to define such accurate details would be impractical.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Here is the problem, now you have a super huge system spec carrying all the entropy.
English is not an accurate language.
Describing roughly how a system should work might be good enough.
But describing how software should work from the high level down to tiny details would be a nightmare.
In that case, letting code carry the implementation details, i.e., the entropy of the software spec, is a better idea.&lt;/p&gt;

&lt;h2 id=&quot;why-trading-code-generation-speed-with-more-code-reviews-is-a-bad-deal&quot;&gt;Why trading code generation speed with more code reviews is a bad deal&lt;/h2&gt;

&lt;p&gt;LLMs generate code extremely fast; it almost feels instantaneous compared to humans.
But now we know that code generated purely with LLMs, without review, is just a plausible implementation of your spec.
We still need to review the code.
Engineering is all about trade-offs. Using LLMs or not, reviewing code or not, each comes with its own pros and cons.
Now, let’s see what’s the deal we are making here.&lt;/p&gt;

&lt;p&gt;As mentioned previously, most people don’t realize that reading code is actually harder than writing it. But how hard is it?
This is my personal experience (it’s very case-by-case), but in general, I would grade the difficulty of programming activities as:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Activity&lt;/th&gt;
      &lt;th&gt;Difficulty (relative units)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Thinking&lt;/td&gt;
      &lt;td&gt;6&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Writing&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Reading other’s code&lt;/td&gt;
      &lt;td&gt;10&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Naming&lt;/td&gt;
      &lt;td&gt;999&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Usually, thinking is the most challenging part other than reading other people’s code. Once you know how it should work, writing it down is the easy part.
Of course, you may need to check the library, syntax and some details about the programming language.
Depending on how familiar you are with it, sometimes it can be slower or faster.
But with LLMs, you save a great amount of time on mechanical work.
Researching syntax, reading library docs; many of those can now be combined into a single prompt.&lt;/p&gt;

&lt;p&gt;Reviewing other people’s code is usually hard because, as mentioned previously, you are guessing the author’s intention; it’s puzzle-like by nature.
Even for your own code, after some time, you can forget the details; it will be hard for you as well.
Of course, not all code is complex, but even a one-line change can have hidden context behind it.
We will talk about the hidden context later, you will see why it’s so hard.&lt;/p&gt;

&lt;p&gt;With LLMs, I would say the difficulty of writing goes down to almost zero.
I would put it as just 1 here:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Activity&lt;/th&gt;
      &lt;th&gt;Difficulty (relative units)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Thinking&lt;/td&gt;
      &lt;td&gt;6&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Writing&lt;/td&gt;
      &lt;td&gt;&lt;span style=&quot;color:#b31d28;text-decoration:line-through&quot;&gt;3&lt;/span&gt; → &lt;span style=&quot;color:#22863a&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Reading other’s code&lt;/td&gt;
      &lt;td&gt;10&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Naming&lt;/td&gt;
      &lt;td&gt;999&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The productivity has indeed been improved greatly for writing, from 3 to 1.
However, if you are not happy with the gain, there’s still something you can do.
You can delegate part of the “thinking” to LLM.
For example, you want to achieve something, but you don’t know what to do; you can ask an LLM to make a decision for you.
In that way, you’ve saved time from thinking, right?&lt;/p&gt;

&lt;p&gt;Well, yes, but no.
As you can see, the difficulty of thinking goes down, let’s say to 1.
But because it’s not your intention, you don’t know why the LLM is doing that, so you need to review it as like you would for other’s code.
Depending on the code quality and volume of generated code, the reviewing difficulty could go up even further, say 20 instead of 10.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Activity&lt;/th&gt;
      &lt;th&gt;Difficulty (relative units)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Thinking&lt;/td&gt;
      &lt;td&gt;&lt;span style=&quot;color:#b31d28;text-decoration:line-through&quot;&gt;6&lt;/span&gt; → &lt;span style=&quot;color:#22863a&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Writing&lt;/td&gt;
      &lt;td&gt;&lt;span style=&quot;color:#b31d28;text-decoration:line-through&quot;&gt;3&lt;/span&gt; → &lt;span style=&quot;color:#22863a&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Reading other’s bad code&lt;/td&gt;
      &lt;td&gt;&lt;span style=&quot;color:#b31d28;text-decoration:line-through&quot;&gt;10&lt;/span&gt; → &lt;span style=&quot;color:#22863a&quot;&gt;20&lt;/span&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Naming&lt;/td&gt;
      &lt;td&gt;999&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;If you are in a team, your co-workers need to review it, that would multiply the cost on reviewing slop code generated by AI.
It’s insane when you think about it: you pay a lower cost to trade for something with a much higher cost. It’s a huge net loss.
Therefore, I would say, if you care about software quality, outsourcing thinking to LLM is a bad idea.&lt;/p&gt;

&lt;h2 id=&quot;llm-is-not-doing-great-outside-of-its-comfort-zone&quot;&gt;LLM is not doing great outside of its comfort zone&lt;/h2&gt;

&lt;p&gt;The term comfort zone is used to describe the area people feel comfortable in, but I think it’s perfect for describing LLMs as well.
In the previous diagrams we have shown, the well‑known paths of approaches / algorithms / code patterns available in great amounts in the training data from mostly open‑source projects are exactly the comfort zone of LLMs.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/llm-comfort-zone.svg&quot; alt=&quot;Diagram illustrating an LLM&apos;s comfort zone: strong performance on common, well-represented patterns in training data, and degraded performance outside that region.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;The &quot;comfort zone&quot; of an LLM is the set of patterns it has seen a lot of during training (common frameworks, common algorithms, common app shapes). Inside that zone, outputs are usually solid; outside it, uncertainty spikes and the model is more likely to guess or hallucinate.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;LLMs can do extremely well in their comfort zone, but not outside of it, simply because the whole system is built to recognize patterns and predict based on them.
For all the software, algorithms, building blocks with good amounts and great quality in LLM training data, I call them commodity software.&lt;/p&gt;

&lt;p&gt;However, if there is any requirement outside of well‑known patterns, LLM performance degrades greatly.
In simpler terms, LLMs are not creative.
More often than not, LLMs make things up if asked to do things outside of their comfort zone, i.e., &lt;a href=&quot;https://en.wikipedia.org/wiki/Hallucination_(artificial_intelligence)&quot;&gt;hallucination&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example, as in the login page example we just mentioned above.
Most login pages all look mostly the same, but what if we want to do something really odd and barely seen out there?
Like:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hey, build me a login page and only grant access to the user if they can perform the Moonwalk perfectly on camera&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have not really tried, but my gut tells me it might actually be able to make one.
But the reason is not because the LLM is creative and comes up with algorithms to detect human movement on camera, it would more likely be because there are algorithms for detecting human movements in some open‑source projects it has read before.
It can either use those libraries in the code it generates, or write down the algorithm it memorized with twists in the target language and taking the context into account.
Using existing building blocks to create something new, that is what software engineers do on a daily basis.
LLMs can certainly do similar things, and it makes people feel they are super smart or even creative, but under the hood, they are chaining the patterns seen in the training data.
In the case of using the moonwalk dance move as a way to log in, whether the moonwalk is accurate or not can be output as a simple value.
Somewhere in the weights, it has seen the pattern that you can chain output from one system and pipe it to another system, so it applies that rule.&lt;/p&gt;

&lt;p&gt;Say if we live in a universe where all the body motion research and the corresponding code do not exist, and you give an LLM such a prompt, it will not know how to do that.
Or, even if it tries, there is little chance the system could work.&lt;/p&gt;

&lt;h2 id=&quot;hidden-context&quot;&gt;Hidden context&lt;/h2&gt;

&lt;p&gt;Yet another reason LLMs cannot replace software engineers is because of hidden context.
I have been learning Japanese lately, and I realized Japanese is a very context‑dependent language.
People get used to communicating with hidden context that is obvious in that environment.
Here is an example.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;好き&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It means “like” in Japanese.
If this is an anime scene, a male student saying it to a female student, it is obvious the male student is saying to the female student that “I like you”.
If you put just “好き” into Google Translate, despite translation technology being really advanced nowadays (thanks to transformer models), it still does not understand the implied “I like you” part.
It can only guess the most likely meaning of “好き” based on statistics from its training data.&lt;/p&gt;

&lt;p&gt;As we mentioned in the first section, we discussed how important contributing entropy and reducing uncertainty are to a software project.
When a software engineer writes a few lines of code, there could be hundreds of considerations in their mind when writing them.
It could be anything like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Performance considerations&lt;/li&gt;
  &lt;li&gt;Security considerations&lt;/li&gt;
  &lt;li&gt;Compliance considerations&lt;/li&gt;
  &lt;li&gt;Legacy code considerations&lt;/li&gt;
  &lt;li&gt;Workarounds for third‑party bugs …&lt;/li&gt;
  &lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So on and so forth.
Most of the time, there will be nothing mentioned in the code about those intentions.
If you are lucky, the engineer who takes good practice seriously may leave a comment explaining why these lines are here.
But more often than not, this context stays in the author’s brain.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/hidden-context.jpg&quot; alt=&quot;Iceberg diagram showing &apos;visible context&apos; above the waterline and much larger &apos;hidden context&apos; below.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Another iceberg: the prompt and the code you can see are just the &quot;visible context&quot;. The bulk of engineering intent is &quot;hidden context&quot; (trade-offs, constraints, historical decisions, production failures, tribal knowledge) that rarely makes it into the code or the prompt.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;It is not because software engineers are lazy (well, sometimes we are 😅), but rather because the entropy of those hidden contexts would be tremendous if we had to write all of them down in detail.
The code is just the artifact of the thought process.
When LLMs are trained based on that code, the LLM has no idea about these hidden contexts.
For example, why are we using syscall A instead of syscall B here?
There could be a reason, maybe syscall A performs better in the first situation and syscall B performs better in the second situation?
Or, maybe we need to support legacy Linux kernel here, so that despite it’s a bit slower, we need to use syscall A instead of B?
Skilled and experienced software engineers usually bring far more considerations to writing the same line of code than junior software engineers.
Sometimes you would be surprised by how many considerations are behind a simple few lines of code.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/hidden-context.svg&quot; alt=&quot;Diagram showing hidden context on the left influencing the choice of artifact code A, B, or C on the right, with the hidden context not visible to readers.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;&quot;Hidden context&quot; (constraints, trade-offs, historical incidents, performance profiles, production realities) drives the choice between multiple plausible artifacts (code A/B/C). But that context is usually invisible to future readers, and therefore invisible to LLMs trained only on the code.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;When you prompt an LLM:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Write me performant software, MAKE NO MISTAKE!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is not going to work, as mentioned previously, this is outside of the comfort zone of LLM models.
Not because there is zero data in the world (there are benchmarks, perf reports, CVEs, postmortems, and private incident history), but because the relevant context is rarely captured in the artifacts the model sees at generation time, and often isn’t written down at all.
How do you know these lines of code are here, or why the order of lines matters for performance reasons if it is not explicitly stated?
This is why I say reading code is many times harder than writing code, because you can only guess about the author’s intention if they do not write it down.&lt;/p&gt;

&lt;h2 id=&quot;disorderness-compounds-over-time&quot;&gt;Disorderness compounds over time&lt;/h2&gt;

&lt;p&gt;Yet another reason is, in a system with lots of low-cost code generation (LLM or not), the &lt;a href=&quot;https://en.wikipedia.org/wiki/Entropy_(thermodynamics)&quot;&gt;entropy&lt;/a&gt; (disorderness) tends to grow over time unless you spend deliberate effort to remove it.
The entropy here means disorderness rather than the information amount as we mentioned before, it is from &lt;a href=&quot;https://en.wikipedia.org/wiki/Thermodynamics&quot;&gt;thermodynamics&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Any &lt;a href=&quot;https://en.wikipedia.org/wiki/Isolated_system&quot;&gt;isolated system&lt;/a&gt;, entropy (disorderness) will only grow over time (i.e., the &lt;a href=&quot;https://en.wikipedia.org/wiki/Second_law_of_thermodynamics&quot;&gt;second law of thermodynamics&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because I have used the term entropy too many times, to make this easier to read, I will use disorderness instead moving forward.
This is only an analogy (software projects are not isolated thermodynamic systems), but it matches a familiar engineering reality: if you can produce code faster than you can review, delete, and simplify it, noise accumulates.&lt;/p&gt;

&lt;p&gt;What I mean is: for an LLM to generate code that works (or is more likely to work), it tends to copy code that it has no idea what it is for.
For example, recently I cleaned up some useless lines added by an LLM:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/anti-entropy.jpg&quot; alt=&quot;Screenshot of Fork (Git GUI) showing a diff that deletes unnecessary lines added by an LLM.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A real diff from &lt;a href=&quot;https://git-fork.com&quot;&gt;Fork&lt;/a&gt;: removing unnecessary defensive or cargo-cult lines that an LLM sprinkled throughout the codebase. This kind of cleanup work is &quot;anti-entropy&quot;, restoring signal by deleting noise.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Not only that, if you ever use LLMs to generate code and you have coding experience without using LLMs, you will notice that the LLM models act very defensively when generating code.
They will always try to catch any exception, and when importing a library they will fall back to the situation if the library does not exist.
These lines of useless code look harmless, but they are a kind of disorderness added by LLMs.
They not only make the code potentially run slower, they also make the code much harder to read.
It is purely adding noise to the signal.
Over time, more and more noise will be added to the code base, the noise‑to‑signal ratio will be higher than ever.
Not only will it consume more tokens to process, at some point the LLM itself will lose focus.&lt;/p&gt;

&lt;p&gt;It is funny: even in 2026, there are still people using lines of code as a measurement of productivity. I saw people brag about tens of thousands of lines of code an LLM generated, as if we are in a competition to generate as many lines of code as possible.
Unfortunately, for people without software engineering experience, it seems like a logical idea, as software engineers produce code, the more code the better, right?
No, that is totally wrong.
Code is liability: the more lines your codebase has, the more likely it is to have bugs.
And more code means readers will need to spend more time reading through nonsense; even your LLM will spend more tokens reading it.&lt;/p&gt;

&lt;p&gt;You know what is harder than generating code?
Removing code without breaking the system or reducing the functionalities.
Because it requires complete understanding of how the code works, why the line is not needed, or whether there is a better, simpler flow to achieve the same thing.
So far, I have never seen an LLM that is good at removing code without breaking things, because once again, that is not what the system is designed for.&lt;/p&gt;

&lt;p&gt;Without removing the disorderness and noise from the code, very soon, you will realize that it gets harder and harder to prompt the LLM to modify software without breaking it.
Most people out there vibe coding their pet projects, and their pet projects do not survive long or become complex enough to hit that wall.
In other words, anti‑entropy needs to be introduced periodically to keep the code base’s noise‑to‑signal ratio below a certain level.
We usually call it &lt;a href=&quot;https://en.wikipedia.org/wiki/Code_refactoring&quot;&gt;refactoring&lt;/a&gt;.
LLMs are good at mimicking, but they are not good at understanding, so it will not be helpful if you let them do it for you.
Because this task requires understanding of the system, fortunately or unfortunately, you will still need an experienced software engineer to help you do so.&lt;/p&gt;

&lt;h2 id=&quot;software-verification-is-still-challenging-and-becoming-more-important-than-ever&quot;&gt;Software verification is still challenging, and becoming more important than ever&lt;/h2&gt;

&lt;p&gt;We have already talked about this a lot. If your software has nothing to lose, you probably do not care about any of this.
But say you have something to lose, then software verification is going to be the really challenging part for you, even with LLMs.
Usually, to ensure the system works as expected, we will write &lt;a href=&quot;https://en.wikipedia.org/wiki/Behavior-driven_development&quot;&gt;BDD&lt;/a&gt; tests, &lt;a href=&quot;https://en.wikipedia.org/wiki/End-to-end_testing&quot;&gt;end‑to‑end&lt;/a&gt; tests, &lt;a href=&quot;https://en.wikipedia.org/wiki/Acceptance_testing&quot;&gt;acceptance tests&lt;/a&gt; and other tests to have a way to verify the system automatically.
In a healthy software project, if we put on the lens of information entropy, you may have something like this:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/software-verifications-with-tests.svg&quot; alt=&quot;Pyramid diagram showing high-level spec on top, automatic tests in the middle, and implementation code at the bottom.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A verification pyramid: high‑level product spec at the top, a large layer of automated tests (BDD/E2E/acceptance/regression) in the middle that continuously checks behavior, and the implementation code at the bottom.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The software product spec is at the top, the middle is tons of automatic tests, and finally the bottom is the code.
While some people would love to see the automatic testing code as, well, just code, I prefer to see it more as software spec.
Because it ensures the behavior of the software.
What is even better?
It can be done automatically.&lt;/p&gt;

&lt;p&gt;For some software projects, they may not have much of the spec written in plain English, but they have intensive spec written as automatic test cases.
There are many interesting well‑known test suites, such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;JVM / Java SE - &lt;a href=&quot;https://openjdk.org/groups/conformance/JckAccess/index.html&quot;&gt;JCK (TCK for Java SE compatibility)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;SQLite - &lt;a href=&quot;https://sqlite.org/src/doc/trunk/doc/testrunner.md&quot;&gt;TCL test suite / test runner&lt;/a&gt; and &lt;a href=&quot;https://sqlite.org/sqllogictest&quot;&gt;sqllogictest&lt;/a&gt; (plus TH3, their proprietary harness)&lt;/li&gt;
  &lt;li&gt;Web browsers - &lt;a href=&quot;https://web-platform-tests.org/&quot;&gt;Web Platform Tests (WPT)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Android - &lt;a href=&quot;https://source.android.com/docs/compatibility/cts&quot;&gt;Compatibility Test Suite (CTS)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Kubernetes - &lt;a href=&quot;https://sonobuoy.io/&quot;&gt;Sonobuoy conformance tests&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Graphics APIs - &lt;a href=&quot;https://github.com/KhronosGroup/VK-GL-CTS&quot;&gt;Khronos Vulkan/OpenGL conformance tests (VK-GL-CTS)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The test cases provide a huge amount of entropy and reduce a great amount of software uncertainty.
In the era of AI‑assisted programming, implementation details are still very important, but compared to a robust way to define the software and verify it, the value proposition has shifted from the actual code implementation to the verifiable spec.&lt;/p&gt;

&lt;h3 id=&quot;anthropics-compiler-experiment&quot;&gt;Anthropic’s compiler experiment&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://anthropic.com&quot;&gt;Anthropic&lt;/a&gt; ran an experiment to have LLMs generate a compiler and claimed that it’s from scratch (not really) and showed that it can compile the Linux kernel (&lt;a href=&quot;https://anthropic.com/engineering/building-c-compiler&quot;&gt;Building a C compiler with a team of parallel Claudes&lt;/a&gt;).
The reason they can do it is not because of how smart the LLM is, it is because the software is already well defined by the intensive testing suites provided by the open‑source compiler community, such as the &lt;a href=&quot;https://gcc.gnu.org/onlinedocs/gccint/Torture-Tests.html&quot;&gt;GCC torture tests&lt;/a&gt; (and similar compiler test suites).
And even if it works, the binary executable performance is still worse than GCC/Clang in many cases, especially without deep optimization work.
That is because the entropy that defines performant compiling behavior is not captured in the tests, and LLMs do not understand what makes compiled binary code run fast.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/monkey-and-typewriter.jpg&quot; alt=&quot;A chimpanzee seated at a typewriter.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A chimpanzee seated at a typewriter. Credit: New York Zoological Society, circa 1906, Public Domain (via Wikimedia Commons: &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Chimpanzee_seated_at_typewriter.jpg&quot;&gt;Chimpanzee seated at typewriter&lt;/a&gt;).&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;You see, that is the power of having an intensive testing suite carrying a massive amount of software spec entropy, making it possible even for LLMs to generate code that can compile the Linux kernel.
This is basically the &lt;a href=&quot;https://en.wikipedia.org/wiki/Infinite_monkey_theorem&quot;&gt;infinite monkey theorem&lt;/a&gt;: given infinite time, a monkey randomly hitting keys on a typewriter will almost surely type the complete works of Shakespeare.
With test cases detailed enough, given enough monkeys and time, they can also generate code to pass those tests.
Certainly using LLMs has reduced the searching space, plus the compiler code is already part of their training data.
Also, humans still made architecture decisions and constrained the problem.
But the key enabler is the same: intensive verification (tests) that pins down behavior tightly enough for automation to converge.&lt;/p&gt;

&lt;p&gt;Interesting side note: people noticed that the compiler seems to reproduce a bunch of very specific mistakes that match bugs in small open-source C compilers (e.g. chibicc), which suggests it is likely copying/rewriting patterns from its training data rather than “discovering” everything from first principles (see: &lt;a href=&quot;https://github.com/anthropics/claudes-c-compiler/issues/232&quot;&gt;Hunting for traces of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chibicc&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Claude&apos;s C compiler&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;h3 id=&quot;cursors-browser-experiment&quot;&gt;Cursor’s browser experiment&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://cursor.com&quot;&gt;Cursor&lt;/a&gt; did a similar experiment, but they built a browser instead (&lt;a href=&quot;https://cursor.com/blog/scaling-agents&quot;&gt;Scaling long-running autonomous coding&lt;/a&gt;).
After spending trillions of tokens, they had something that &lt;a href=&quot;https://emsh.cat/cursor-implied-success-without-evidence/&quot;&gt;barely compiles&lt;/a&gt;.
The project was later criticized for relying heavily on existing open‑source components (for example &lt;a href=&quot;https://github.com/servo/servo&quot;&gt;Servo&lt;/a&gt; and &lt;a href=&quot;https://github.com/bellard/quickjs&quot;&gt;QuickJS&lt;/a&gt;) rather than being “from scratch” (see: &lt;a href=&quot;https://theregister.com/2026/01/26/cursor_opinion&quot;&gt;The Register&lt;/a&gt; and the related &lt;a href=&quot;https://news.ycombinator.com/item?id=46650998&quot;&gt;HN thread&lt;/a&gt;).
And even if they made anything that looks like it works, it would mostly be because they are using the existing intensive testing cases from existing open‑source browsers.&lt;/p&gt;

&lt;h3 id=&quot;cloudflares-nextjs-rebuild&quot;&gt;Cloudflare’s Next.JS rebuild&lt;/h3&gt;

&lt;p&gt;Yet another interesting and relatively successful case is &lt;a href=&quot;https://cloudflare.com&quot;&gt;Cloudflare&lt;/a&gt; rewriting Next.JS (&lt;a href=&quot;https://blog.cloudflare.com/vinext/&quot;&gt;How we rebuilt Next.js with AI in one week&lt;/a&gt;).
Once again, they are using (and explicitly porting) intensive test cases from the Next.js project to verify the AI-generated code works, with some manual architecture work (Cloudflare notes they “ported tests directly from their suite” in &lt;a href=&quot;https://blog.cloudflare.com/vinext/&quot;&gt;How we rebuilt Next.js with AI in one week&lt;/a&gt;).
The end result outperforms the original project.
Is it impressive?
Certainly it is.
Is LLM going to replace software engineers?
Obviously not.&lt;/p&gt;

&lt;h3 id=&quot;tdd-and-bdd-may-make-a-huge-comeback&quot;&gt;TDD and BDD may make a huge comeback&lt;/h3&gt;

&lt;p&gt;Now, you see so many examples; they all share the same factor: they all have intensive test cases carrying a huge amount of the verifiable spec.
This shows you how important software verification is, particularly in the AI‑assisted programming era.
It also shows you that with enough high-quality entropy provided by an automatic test suite, even LLMs can generate software that works.
For these projects, they actually chose to do them probably only because there are existing test cases.
But what if you do not have the test cases to begin with?
Now it begs the interesting question, how do you come up with an intensive test suite from nowhere?
It is still the same problem as where the entropy about the software spec is coming from, and how detailed it should go to make it behave exactly like you need.&lt;/p&gt;

&lt;p&gt;Interestingly, in the industry, some people advocated strongly for &lt;a href=&quot;https://en.wikipedia.org/wiki/Test-driven_development&quot;&gt;TDD&lt;/a&gt; (test‑driven development) and &lt;a href=&quot;https://en.wikipedia.org/wiki/Behavior-driven_development&quot;&gt;BDD&lt;/a&gt; (behavior‑driven development) in the past.
I like the idea of having test cases before implementing code, because it is like defining how the software behaves first.
But you can only go so far with an imaginary system and thinking about how it could behave.
After you build the system, there will always be unexpected details popping up, and you need to go back and revise your spec, i.e., your test cases.
Thus, there is still no running away from understanding the software, i.e., you still need a software engineer to help you do so.&lt;/p&gt;

&lt;p&gt;Back in 2023, I &lt;a href=&quot;https://x.com/fangpenlin/status/1667795250885324800&quot;&gt;envisioned on X&lt;/a&gt; that TDD/BDD would become a big thing again with AI‑assisted programming.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/use-bdd-as-spec-and-gen-code.png&quot; alt=&quot;Screenshot of my 2023 X post about using BDD as the spec, generating code, and using tests to verify correctness.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;My 2023 X post: &lt;a href=&quot;https://x.com/fangpenlin/status/1667795250885324800&quot;&gt;use BDD as the spec and generate code&lt;/a&gt;.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Today, seeing the examples mentioned above, I more firmly believe that approaches similar to TDD/BDD will make a huge comeback.
It is not a solved problem, and unlikely to become one: because of the sheer amount of entropy the test cases need to carry, it cannot come out of nowhere. Someone needs to provide it.
And it remains a challenging problem, there is no way you can get rid of software engineers.
LLMs can help generate test cases, but someone still needs to drive it and ensure it is testing the actual desired behavior.
It is like:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hey, write tests to ensure the software is correct&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I would show you this meme:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-03-19-no-llm-is-not-going-to-replace-software-engineers-heres-why/bug-and-feature.webp&quot; alt=&quot;Meme about ambiguity between a bug and a feature.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;The classic &quot;bug or feature?&quot; ambiguity: without a precise spec, you can&apos;t even tell whether behavior is wrong or intended, and &quot;write tests to ensure correctness&quot; becomes &quot;define what correct means&quot; first.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;And say&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Well, define what is correct 🤣&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You see?
It still needs entropy from you regardless.&lt;/p&gt;

&lt;h3 id=&quot;the-implication-of-the-value-proposition-shifting-from-implementation-to-verifiable-software-spec-for-tech-companies-and-open-source-communities&quot;&gt;The implication of the value proposition shifting from implementation to verifiable software spec (for tech companies and open-source communities)&lt;/h3&gt;

&lt;p&gt;The real interesting case to me is Cloudflare’s Next.JS port.
With extensive test cases carrying the entropy, it seems like you can throw away the implementation code and rewrite from the ground up quickly.
What does it mean to the tech companies and the open source communities?
Interestingly, there are some special cases of open-source projects, such as SQLite: they keep some of their most critical test cases secret, for many reasons.
If they published those test suites, recreating SQLite would be much easier.
For companies running open-source business models, does this mean they also need to keep extensive test cases private, while only keeping the code open-sourced, to avoid a quick clone with LLMs?
But that’s a topic for another day.&lt;/p&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final thoughts&lt;/h2&gt;

&lt;p&gt;There’s much more I’d like to discuss.
But this is already a long article.
Overall, I think replacing software engineers with LLMs is far more challenging than people think.
A productivity boost could displace some software engineers, but not all.
And the new demand created by empowered non-technical people will create new opportunities for software engineers.&lt;/p&gt;

&lt;p&gt;If you don’t know how to code and an LLM empowers you to build software you couldn’t build before, I’d encourage you to go for it.
There’s only upside in your use case.
I bet this will create more demand for experienced software engineers, because some people will have something to lose.
But if you’re a big tech company making big money from your software, I would do it very carefully if I were one of your stakeholders.&lt;/p&gt;

&lt;p&gt;It seems like the leading LLM companies are under heavy pressure to pull more money into the system.
So they keep telling a story as big as “replacing all software engineers.”
I intentionally use the term LLM instead of AI because the discussion above is specifically about the LLM architecture.
At least for LLMs, I think it’s impossible; it’s just nonsense.&lt;/p&gt;

&lt;p&gt;Could there be a new model that’s not an LLM?
I have no clue.
But I highly doubt it.
I don’t currently see credible signs of a near-term paradigm shift that removes the spec/verification bottleneck rather than merely accelerating implementation.
This belief also aligns with Fred Brooks’s classic idea of “&lt;a href=&quot;https://en.wikipedia.org/wiki/No_Silver_Bullet&quot;&gt;No Silver Bullet&lt;/a&gt;”: there isn’t a single breakthrough that gives an order-of-magnitude improvement by itself, especially when the hard part is the essential complexity of specifying what you actually want.
Plus, LLMs have absorbed a lot of research resources.
There’s little diversity in research directions.
I doubt anything fundamentally different will emerge in the short term.&lt;/p&gt;

&lt;p&gt;I know many of you can’t wait to throw stones at me for saying the truth LLM companies don’t want to hear.
Hey, not so fast. 😅
As I said, I’ve found LLMs very useful in software engineering.
And they’ve boosted my productivity a lot.
There are patterns I find very effective.
And there are anti-patterns they handle poorly.
I’ll try to write those up in a separate article.
“Vibecoding” is more about ignoring (and accepting) whatever comes out of the model.
I prefer to call it AI-assisted programming.&lt;/p&gt;

&lt;p&gt;How AI-assisted programming will change the industry is yet another interesting topic.
Unlike some people who avoid making predictions, I love making predictions.
I’d love to make a statement, then revisit it a few years later to see if I was right or wrong.
Of course, I could be wrong.
But next time, I might make a better prediction.&lt;/p&gt;

&lt;p&gt;That’s it.
I hope you found this article helpful.
Feel free to leave feedback or questions below.
Thanks for taking the time to read.&lt;/p&gt;
</description>
        <pubDate>Thu, 19 Mar 2026 07:00:00 +0000</pubDate>
        <link>https://fangpenlin.com/posts/2026/03/19/no-llm-is-not-going-to-replace-software-engineers-heres-why/</link>
        <guid isPermaLink="true">https://fangpenlin.com/posts/2026/03/19/no-llm-is-not-going-to-replace-software-engineers-heres-why/</guid>
      </item>
    
      <item>
        <title>Manufacturing as Code is the Future, and the Future is Now</title>
        <description>&lt;style type=&quot;text/css&quot;&gt;
  figure {
    text-align: center;
    margin: 0 auto;
  }
  figcaption, figcaption p {
    color: grey;
    font-size: 0.9em;
  }
  figure img {
    max-width: 100%;
  }
&lt;/style&gt;

&lt;p&gt;Since I &lt;a href=&quot;/posts/2024/12/11/cading-and-3d-printing-like-a-software-engineer-part1/&quot;&gt;started my journey with 3D printing&lt;/a&gt;, I have built and shared dozens of 3D printable models to the public.
Surely, &lt;a href=&quot;/posts/2025/11/26/tinyrack-a-3d-printable-modular-rack-for-mini-server/&quot;&gt;TinyRack is one of them&lt;/a&gt;.
You can find them on my MakerWorld profile &lt;a href=&quot;https://makerworld.com/en/@fangpenlin/upload&quot;&gt;here&lt;/a&gt; or on my Printables profile &lt;a href=&quot;https://www.printables.com/@FangPenLin_2668889&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-01-12-manufacturing-as-code-is-the-future/printable-and-makerworld-profiles.jpg&quot; alt=&quot;My Printable and MakerWorld profiles showing dozens of 3D printable models&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;My &lt;a href=&quot;https://www.printables.com/@FangPenLin_2668889&quot;&gt;Printables&lt;/a&gt; and &lt;a href=&quot;https://makerworld.com/en/@fangpenlin/upload&quot;&gt;MakerWorld&lt;/a&gt; profiles showing dozens of 3D printable models&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So far, I have really enjoyed the process of designing and printing the models.
If there’s anything I’ve experienced that feels most like it came out of science fiction, it’s 3D printing technology.
When you realize that the physical form of objects can be defined by digital bits, it opens up unbounded possibilities for what we can do with the technology.&lt;/p&gt;

&lt;p&gt;The more I design and print, the more I realize that while the printing process takes time, it runs smoothly in the background.
But for design, it’s a whole different story.
More often than not, it takes a huge amount of effort and countless iterations to design even for a simple snap-fit part.
I often get lost when working with different revisions of the same part with slight differences.
As printing technology becomes more and more mature, the bottleneck is not the printing anymore, it’s the design instead.&lt;/p&gt;

&lt;p&gt;As a software engineer, I get very comfortable with writing code to define the behavior of a system.
Setting up the CI/CD pipeline to automate the build and deployment process is also a common practice.
While I work on my 3D printing projects, none of those exist.
Then I wondered, given that now bits can shape atoms, why not use the same approach to build software for the physical world?&lt;/p&gt;

&lt;p&gt;With that in mind, I spent the past few weeks building a prototype of a GitHub-like platform for manufacturing, called &lt;a href=&quot;https://makerrepo.com&quot;&gt;MakerRepo&lt;/a&gt;.
Today I am very excited to announce that the project is now online and has entered the beta testing phase for the public. 😄🎉&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-01-12-manufacturing-as-code-is-the-future/makerrepo-artifact-page.png&quot; alt=&quot;Screenshot of MakerRepo artifacts viewer featuring a 3D model of a part&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Screenshot of MakerRepo artifacts viewer featuring a 3D model of a part&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img src=&quot;/images/2026-01-12-manufacturing-as-code-is-the-future/makerrepo-code-screenshot.png&quot; alt=&quot;Screenshot of MakerRepo code viewer featuring a Python code file for generating a 3D model with Build123D&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Screenshot of MakerRepo code viewer featuring a Python code file for generating a 3D model with Build123D with an &quot;artifact&quot; decorator&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;!--more--&gt;

&lt;h2 id=&quot;the-painful-parts-of-traditional-cad-design-process&quot;&gt;The painful parts of traditional CAD design process&lt;/h2&gt;

&lt;p&gt;I have been using Fusion 360 since I started my journey with 3D printing.
It’s a powerful CAD software that can handle complex models; it can even simulate how the part performs under different conditions.
I probably only use about 5% of the features of the software, but it’s more than enough for my needs.
I really enjoy using it, but it’s not the problem of the software itself—it’s the mindset upon which this software has been built.
Before we dive into the future of manufacturing, let’s first understand the painful parts of the traditional CAD design process from a software engineer’s perspective.&lt;/p&gt;

&lt;h3 id=&quot;version-control&quot;&gt;Version control&lt;/h3&gt;

&lt;p&gt;The first painful part is version control.
There are version control systems built into the CAD software, but they are not very user-friendly or they lack critical features.
I am used to comparing two versions of code in the code editor to find out what the changes are between them.
I am also used to having artifacts like executable files come with a version number, so I can easily find the version I am holding right now.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-01-12-manufacturing-as-code-is-the-future/fork-screenshot.png&quot; alt=&quot;Screenshot of Fork GUI app showing the changes in a commit&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Screenshot of &lt;a href=&quot;https://git-fork.com&quot;&gt;Fork&lt;/a&gt; GUI app showing the changes in a commit&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;But with real-world objects printed from different revisions of the same design, it’s very hard to track what the changes are between them.
It’s also hard for me to tell which one is which.
More often than not, a minor change like snap-fit clearance difference could be very small, and you cannot even tell with the naked eye.
When trying out the different revisions, I need to be very careful not to mix them up.
Of course, I can add a version number to the model, but it’s not very convenient because I will have to manually change the version number for each revision.
Eventually, I feel there’s a need for better version control for CAD models.&lt;/p&gt;

&lt;h3 id=&quot;collaboration&quot;&gt;Collaboration&lt;/h3&gt;

&lt;p&gt;Another painful part is the collaboration.
With traditional CAD software, it’s very hard to collaborate with others.
Maybe not for the manufacturing industry, but for 3D printing open-source community, it’s very common to remix a design and share it with others.
To collaborate with others, first of all, you need to have the same CAD software installed on your computer.
Despite that CAD software like Fusion 360 provides collaboration features, if the other person doesn’t have the same CAD software installed on their computer, that collaboration feature is useless.&lt;/p&gt;

&lt;p&gt;For example, when I was designing my under table cable management with &lt;a href=&quot;https://handsonkatie.com/underware-2-0-the-made-to-measure-collection/&quot;&gt;Underware 2.0&lt;/a&gt; and &lt;a href=&quot;https://www.opengrid.world&quot;&gt;openGrid&lt;/a&gt;, I realized that one of the lock snap components has an issue.
If you screw it in too tight, it will be very hard to screw it out.
That’s why I decided to remix the author’s design to cut a slot on the thread of the component to make it easier to screw it out.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-01-12-manufacturing-as-code-is-the-future/opengrid-multiconnect-lock-snap-unwinding-tool.jpg&quot; alt=&quot;Screenshot of openGrid Multiconnect Lock Snap Unwinding Tool I made by remixing the original design&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Screenshot of &lt;a href=&quot;https://makerworld.com/en/models/1906036-opengrid-multiconnect-lock-snap-unwinding-tool#profileId-2043345&quot;&gt;openGrid Multiconnect Lock Snap Unwinding Tool&lt;/a&gt; I made by remixing the original design&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I then &lt;a href=&quot;https://makerworld.com/en/models/1906036-opengrid-multiconnect-lock-snap-unwinding-tool#profileId-2043345&quot;&gt;uploaded the remix to MakerWorld&lt;/a&gt; and shared the link with the author in the comments.
Okay, cool, I shared an improvement to the design on the same platform, but what now?
I felt this is not the right way to collaborate as we do in the software engineering world.
Some people can find it, but unless the original author took my improvement, used the CAD software he preferred and updated the model himself, then reuploaded the model to MakerWorld, it’s not very helpful.
This is particularly painful as we are used to making contributions to open source projects with code changes.
When people contribute to open source projects, the author can review the change easily and hit the merge button to merge the change into the main branch.
And there you go, you have a new model everybody can enjoy!
In the end, I realized that “open-source” is not so open if there’s no easy way to collaborate with others.&lt;/p&gt;

&lt;h3 id=&quot;customizable-parts&quot;&gt;Customizable parts&lt;/h3&gt;

&lt;p&gt;Another painful part is to create a part with multiple variants based on different parameters.
With traditional parametric CAD software, surely you can define a part with parameters that reflect the changes of the part.
I did exactly this with TinyRack to make it customizable.
For example, with the same post, I can create variants with different notches at different positions.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-01-12-manufacturing-as-code-is-the-future/post-variants.png&quot; alt=&quot;Screenshot of TinyRack post variants notched at different positions&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Screenshot of TinyRack post variants notched at different positions&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;While this works for different permutations of the same part, I need to manually change the parameters for each variant in the CAD software, export the model and upload it to the platform.
It’s not very efficient and not very scalable.
For platforms like MakerWorld, they provide a way to create a generator from a CAD file that allows the user to create variants with different parameters.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-01-12-manufacturing-as-code-is-the-future/makerworld-underware-channel-generator.jpg&quot; alt=&quot;Screenshot of MakerWorld Underware Channel Generator&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Screenshot of &lt;a href=&quot;https://makerworld.com/en/models/1329404-underware-for-opengrid-customizer-beta?from=search#profileId-1367368&quot;&gt;MakerWorld Underware Channel Generator&lt;/a&gt;&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;But that’s the proprietary software built by BambuLab.
If I want to do the same locally, I need to build my own generator that can read and understand the CAD file and also needs to generate the STL or 3MF files for each variant, which is not easy.&lt;/p&gt;

&lt;h3 id=&quot;lack-of-automation&quot;&gt;Lack of automation&lt;/h3&gt;

&lt;p&gt;Other than the problems mentioned above, the final straw that breaks the camel’s back is the lack of automation.
As you can see, I built and uploaded tons of models to both MakerWorld and Printables.
Each time, I need to manually change the parameters for each variant, export the model and upload it to the platform.
And this is not just one time task.
If I make any revision to the design, I need to do it manually all over again with multiple platforms.
As a software engineer, I am used to the idea of automation to solve this problem.
For example, when I worked as an iOS engineer in the past, I built a CI/CD pipeline to automate the build and deployment process.
That process even included taking screenshots of the app and uploading it to the App Store.
The only thing I need to do is to push a new tag to the repository, and the CI/CD pipeline will take care of the rest.
While traditional CAD software provides an easy-to-use UI, it doesn’t provide an easy-to-use API to automate the process.
This is why I felt, you know what, I need to build my own platform to solve this problem.&lt;/p&gt;

&lt;h2 id=&quot;the-future-of-manufacturing---manufacturing-as-code&quot;&gt;The future of manufacturing - manufacturing as code&lt;/h2&gt;

&lt;p&gt;The traditional manufacturing industry is built upon the idea of mass production.
But 3D printing changes the game.
Now instead of a one size fits all product, you can design a product that is tailored to your needs.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-01-12-manufacturing-as-code-is-the-future/3d-printing-for-semi-customized.svg&quot; alt=&quot;A venn diagram showing the market segmentation of 3D printing fits perfectly for semi-customized products, and the market size could be expanding as now customers have more options to fit their needs even better&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A Venn diagram showing the market segmentation of 3D printing fits perfectly for semi-customized products, and the market size could be expanding as now customers have more options to fit their needs even better&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The customers used to be forced to tolerate the one-size-fits-all design that is not tailored to their needs.
With 3D printing technology, there’s no difference in cost between a customized part and a mass production part.
Now they can have the design that is tailored to their needs.
If we use 3D printing only as a replacement for the mass production, we are not really taking the advantage of the technology.
I believe this market segment is going to expand in the future as now customers have more options to fit their needs even better.
Mass production and hand crafted products will still exist and stay strong, but semi-customized products will be a new segment in the market.&lt;/p&gt;

&lt;p&gt;For example, with TinyRack, you may need a notch at different positions on the post based on the size of the machine you are going to put in it.
With traditional mass production, you will probably design a post with as many notches as possible to fit all the possible needs.
But then of course it makes the model looks ugly.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-01-12-manufacturing-as-code-is-the-future/customized-vs-no-customization.png&quot; alt=&quot;Two TinyRacks with notch on post at the exact needed position vs a TinyRack with notches on the post for all possible positions&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Two TinyRacks with notch on post at the exact needed position vs a TinyRack with notches on the post for all possible positions&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;With the ability to at least semi-customize the design, we can now have parts that are tailored to our needs, not the other way around.
This opens a new market for small-to-medium-size batch production with customized products.
With the concept of semi-customized products, the bottleneck is not the manufacturing anymore, it’s the design instead.
Making a design from scratch is a very time-consuming process.
Therefore, starting from an existing design and changing the parameters to tailor towards your needs is a much faster way to get the job done.&lt;/p&gt;

&lt;p&gt;How do you change the parameters of a design to tailor towards your needs of a part without starting from scratch?
The answer is to use code to define the design of a part.
In fact, with traditional CAD software, you can already define a CAD model with parameters, that’s why it’s &lt;a href=&quot;https://en.wikipedia.org/wiki/Parametric_design&quot;&gt;called parametric CAD&lt;/a&gt;.
However, because it’s not a programming language, it’s very hard to understand the relationship between the parameters and the model.
It’s also very hard to track what’s the changes of the model between revisions.
But with code, it’s nature that software developers have been using version control to track the changes of the code for decades.
More than that, with code and modern LLMs, there’s also a potential opportunity to design a part with prompts, or even automatically make a design from scratch.&lt;/p&gt;

&lt;h2 id=&quot;the-knowledge-of-manufacturing-could-be-lost&quot;&gt;The knowledge of manufacturing could be lost&lt;/h2&gt;

&lt;p&gt;The process of manufacturing an object is knowledge-intensive.
And over time, the knowledge might have been lost if not well documented.
For example, NASA used to build the Saturn V rocket for the Apollo program.
But after the program ended, the knowledge of building the rocket was lost.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-01-12-manufacturing-as-code-is-the-future/saturn-v-rocket.jpg&quot; alt=&quot;A photo of the Saturn V rocket&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A &lt;a href=&quot;https://en.wikipedia.org/wiki/File:Apollo_11_Launch_-_GPN-2000-000630.jpg&quot;&gt;photo of the Saturn V rocket by NASA in public domain&lt;/a&gt;&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;And now, it’s very hard to build the very same rocket again.
Of course with modern technologies today, we can build a better rocket like SpaceX did with Starship.
But the lesson here is if we don’t document the manufacturing process, we will lose the knowledge eventually.&lt;/p&gt;

&lt;p&gt;Thanks to CAD software, we can now define the manufacturing process in the digital world and can backup the design easily across the globe.
But what if the CAD software is lost?
While I love using Fusion 360, it’s a proprietary software.
When I share a design online, if the other person doesn’t have Fusion 360, they cannot open the design.
They provide free license for personal use, but for commercial use, they require a paid license.
There are also other CAD software packages like SolidWorks, OnShape, and others.
But eventually, if the software is not open-source, it will be lost one day with the company.
We have &lt;a href=&quot;https://www.youtube.com/watch?v=fzI9FNjXQ0o&quot;&gt;Arctic Code Vault&lt;/a&gt; for backing up software code, but what good does it do if all the knowledge of manufacturing is lost?
This is why I believe Manufacturing as Code is also very crucial to preserve the knowledge of manufacturing.&lt;/p&gt;

&lt;h2 id=&quot;build123d---build-cad-models-with-python-code&quot;&gt;Build123D - build CAD models with Python code&lt;/h2&gt;

&lt;p&gt;In the past, when I shared the first article about CADing and 3D printing like a software engineer, some readers mentioned the &lt;a href=&quot;https://build123d.readthedocs.io/en/latest/index.html&quot;&gt;Build123D&lt;/a&gt; software.
It’s a free and open-source CAD software that can be used to design 3D models in Python code.
Here’s an example from the documentation where you build a tea cup with Build123D:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2026-01-12-manufacturing-as-code-is-the-future/build123d-tea-cup.png&quot; alt=&quot;A screenshot the Build123D code for making a tea cup and the resulting 3D model&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A screenshot of the Build123D code for making a tea cup and the resulting 3D model&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;When I first saw it, I thought it was really cool.
I have been thinking about using it, but didn’t have the time to try it out.
Until recently, I finally finished the v1 design of TinyRack, and got the time to try it out.
And I ended up really liking it.
At the very beginning, I missed the friendly UI of the traditional CAD software where I can click and select the objects I want to work on.
But after I got better and better in it, I feel the time I spent on building the same model is getting shorter and shorter.
I feel at some point, I can even build the model faster than I can click and select the objects in the traditional CAD software in some cases.&lt;/p&gt;

&lt;h2 id=&quot;makerrepo---the-platform-for-manufacturing-as-code&quot;&gt;MakerRepo - the platform for manufacturing as code&lt;/h2&gt;

&lt;p&gt;After I got myself more familiar with the Build123D, I started to envision the future of manufacturing with code.
I concluded that the platform should be a GitHub-like website but for manufacturing.
Building a platform like GitHub is definitely not easy, not to mention if you need to host large-scale repositories, dealing with the scalability and durability issues, more importantly, security issues (because you need to run user-uploaded code).&lt;/p&gt;

&lt;p&gt;It may take me years to build a platform like GitHub, but fortunately, I have seen this movie before.
If you read some of my &lt;a href=&quot;/posts/2024/12/30/my-beancount-books-are-95-percent-automatic/&quot;&gt;previous articles&lt;/a&gt;, you will know that I have built a similar platform before.
Yes, that is &lt;a href=&quot;https://beanhub.io&quot;&gt;BeanHub&lt;/a&gt;, a Git repository hosting service for Beancount accounting books.
If you are interested in the technology behind BeanHub, you can read my article &lt;a href=&quot;https://beanhub.io/blog/2024/04/23/how-beanhub-works-part1-sandboxing/&quot;&gt;How BeanHub works part 1, contains the danger of processing Beancount data with sandbox&lt;/a&gt; and &lt;a href=&quot;https://beanhub.io/blog/2024/06/26/how-beanhub-works-part2-layer-based-git-repos/&quot;&gt;How BeanHub works, part 2, a large-scale auditable Git repository system based on container layers&lt;/a&gt;.
tl;dr, I used container sandboxing + overlayfs to build a large-scale auditable Git repository system.
MakerRepo is built upon the same technology, but for manufacturing as code instead of accounting books.&lt;/p&gt;

&lt;p&gt;When I built BeanHub, I pushed really hard to open-source as much as possible.
You can see the list of our open-source projects &lt;a href=&quot;https://beanhub.io/open-source/&quot;&gt;here&lt;/a&gt;.
Because I believe if one day BeanHub is not in business anymore, people should still be able to use the open-source projects to continue their existing workflow.
Ideally, you should be able to do anything locally with the open-source tools on your own computer just like what’s provided by the platform.
MakerRepo just makes it much easier to host your code and artifacts online and share them with others, and also provides a platform for you to collaborate with others.
This is why I open sourced &lt;a href=&quot;https://github.com/LaunchPlatform/MakerRepo&quot;&gt;MakerRepo&lt;/a&gt;.
It’s a library and CLI tool written in Python to help you build your model with manufacturing as code.
I don’t want to change too much how people write their Build123D code, therefore, I made it very easy to integrate your existing Build123D code with MakerRepo.
One major feature provided by MakerRepo is the ability to collect the artifacts of your model and view them in a web interface via the &lt;a href=&quot;https://github.com/bernhard-42/vscode-ocp-cad-viewer/tree/main&quot;&gt;OCP viewer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To do so, you only need to install the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MakerRepo&lt;/code&gt; library and add an “artifact” decorator to a function that makes the model.
To install it, you can run:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;makerrepo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then in your code for generating the model, you can add an “artifact” decorator to the function that makes the model.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;build123d&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;mr&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;artifact&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;artifact&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;make_model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And that’s it!
After you commit the code and push it to your Git repository on MakerRepo, it will automatically run a CI job to build the model and collect the artifacts.
Then you can view your beautiful artifacts in the web interface via the embedded OCP viewer.&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s next?&lt;/h2&gt;

&lt;p&gt;This is just the beginning.
The MakerRepo is still in the early stages of development.
For now, the CI build environment is still very limited, and you can only build the model with Build123D but no other libraries.
Soon I will add new features like custom CI pipeline, custom build environment.
You can imagine the possibilities are endless. For example, you can have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;develop&lt;/code&gt; branch that builds artifacts with version information onto the model, so that it’s much easier to track which version is which in the real world.
With the CI/CD concept, maybe you can event have a branch like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;production&lt;/code&gt;, and it automatically prints the model into a physical object.&lt;/p&gt;

&lt;p&gt;I will also add fork and pull request features to make collaboration with others much easier.
In the near future, you will be able to fork a repository you like, make changes to it, and then submit a pull request to the original repository.
And the author will be able to review the changes, see a visualized diff of the changes, provide feedback on the model and code, then decide to merge the changes or not easily.&lt;/p&gt;

&lt;p&gt;Currently, there’s no paid plan yet, but I will add one in the near future.
I aim to follow a pricing model that’s similar to GitHub. For open source projects, you should enjoy most of the features for free.
With a paid plan, you can host private repositories and enjoy more features and maybe more CI build time quota.
For those who are not familiar with the pricing approach I take, here’s what I usually do.
The price will start lower, then after new features are added, the price will be raised.
The early users can enjoy the lower price with more new features added later on.
Stay tuned for the pricing model announcement.&lt;/p&gt;

&lt;p&gt;Finally, while this is just yet another software product I built, it means something else to me.
There were people laughing at the idea of bringing back manufacturing jobs to the US.
As Tim Cook said, the problem is not just the cost, but also the talent and knowledge of the workers who know how to manufacture.
Unfortunately, the knowledge of manufacturing is dying with the older generation of workers, not just in the US, but also in the rest of the world.
I tried to think about how I could help from the perspective of a software engineer, and this is what I came up with.
The COVID supply chain issues taught us a lesson that we need to be more independent and not rely on the supply chain of other countries.
To bring manufacturing jobs back to the US, we should not think about replicating the workflow of the manufacturing industry, but instead, we should think about copying the software industry.
We need to think smart and make the design process more efficient and accessible.
I hope MakerRepo can be a small step towards that goal.
Please feel free to try it out and let me know if you have any feedback or suggestions. 😄👍&lt;/p&gt;
</description>
        <pubDate>Mon, 12 Jan 2026 07:00:00 +0000</pubDate>
        <link>https://fangpenlin.com/posts/2026/01/12/manufacturing-as-code-is-the-future/</link>
        <guid isPermaLink="true">https://fangpenlin.com/posts/2026/01/12/manufacturing-as-code-is-the-future/</guid>
      </item>
    
      <item>
        <title>TinyRack - A 3D printable modular rack for mini servers</title>
        <description>&lt;style type=&quot;text/css&quot;&gt;
  figure {
    text-align: center;
    margin: 0 auto;
  }
  figcaption, figcaption p {
    color: grey;
    font-size: 0.9em;
  }
  figure img {
    max-width: 100%;
  }
&lt;/style&gt;

&lt;blockquote&gt;
  &lt;p&gt;Update:
This is the second article in the CADing and 3D printing series.
You can read other articles here:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;/posts/2024/12/11/cading-and-3d-printing-like-a-software-engineer-part1/&quot;&gt;Article01 - CADing and 3D printing like a software engineer, part 1 - baby step with an overengineered webcam raiser&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;/posts/2026/01/12/manufacturing-as-code-is-the-future/&quot;&gt;Article03 - Manufacturing as Code is the Future, and the Future is Now&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my &lt;a href=&quot;/posts/2024/12/11/cading-and-3d-printing-like-a-software-engineer-part1/&quot;&gt;previous article&lt;/a&gt;, I’ve shared my journey of 3D printing and learning CAD from the perspective of a software engineer.
As mentioned in the article, I really wanted to build a server rack for my bare metal Kubernetes cluster as seen in &lt;a href=&quot;/posts/2024/01/14/high-speed-usb4-mesh-network/&quot;&gt;this article&lt;/a&gt;.
Recently, I finally got some time to actually print some projects I have designed so far.
Today, I am excited to introduce what I’ve built - TinyRack, a modular rack for mini servers!&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/tinygrad-with-mini-pc-cluster.jpg&quot; alt=&quot;TinyRack with my mini PC cluster&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;TinyRack with my mini PC cluster&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I imagine many people would enjoy a server rack designed specifically for mini servers, given how popular homelabs have become in recent years, so I share my models under an open license.
You can download all the models from &lt;a href=&quot;https://tinyrack.io&quot;&gt;TinyRack.io&lt;/a&gt; and print them yourself, or you can also purchase them on the website.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;why-not-just-use-1u-rack&quot;&gt;Why not just use 1U rack?&lt;/h2&gt;

&lt;p&gt;Look at people’s homelabs, many of them use 1U server racks as their main rack.
Certainly there are many benefits of using the standard U server rack.
First of all, most data centers are built around the &lt;a href=&quot;https://en.wikipedia.org/wiki/19-inch_rack&quot;&gt;19 inch rack standard&lt;/a&gt;.
There are countless pieces of gear you can buy out there which are 19 inch rack compatible.
So the first obvious question you may ask is, why not just use the U rack?&lt;/p&gt;

&lt;p&gt;Well, while the U server rack is very popular, first of all, its form factor is just too big.
Thanks to advances in chip manufacturing technologies, CPUs / GPUs are becoming more and more powerful while consuming less and less power.
In the context of homelab or mini server based clusters, they are usually way smaller than the 1U size.
As a result, one may need to adapt to the U size by buying an adapter.
But these pieces of equipment are geared toward enterprises mostly, and they are expensive.
Take Mac Mini for example, if you want to mount your Mac Minis to a U rack, you can buy &lt;a href=&quot;https://www.mk1manufacturing.com/Mac-Mini-Rack-Mounts/Sliding-Rackmount-shelf-2024-Mac-Mini-p104.html&quot;&gt;some of those&lt;/a&gt;:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/mk1-mac-mini-4-rack.png&quot; alt=&quot;A Mac Mini rack mount for 1U server rack from MK1 Manufacturing&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A Mac Mini rack mount for 1U server rack from &lt;a href=&quot;https://www.mk1manufacturing.com/Mac-Mini-Rack-Mounts/Sliding-Rackmount-shelf-2024-Mac-Mini-p104.html&quot;&gt;MK1 Manufacturing&lt;/a&gt;&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Certainly they could work, but they are also very expensive.
Just the rack itself set you back $459.90 USD, while the most basic Mac Mini itself costs $599.00 USD.
There are also cheaper options like &lt;a href=&quot;https://www.amazon.com/gp/product/B0DZNZL87P/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&amp;amp;psc=1&quot;&gt;this one&lt;/a&gt; that are not as good.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/amazone-mac-mini-m4-rack.jpg&quot; alt=&quot;A Mac Mini rack mount for 1U server rack from Amazon&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A Mac Mini rack mount for 1U server rack from Amazon&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As you can see, it’s more obvious in the second picture that the load goes through only the front panel while the Mac Mini themselves are not being held by anything else.
This is still a okay situation as long as the device is not too heavy and the plate is not too deep and making the torque too high.
But more often than not, I have saw way too many times where people have to attach one side of their tiny device to the U rack, leaving another side hanging in the air because the size of the device is just way too small.
As the design of the U rack is taking the load through the ears of the rack, the material required for the U rack is usually thicker and heavier.
So, as you can see, the U server rack is not really designed for mini servers.&lt;/p&gt;

&lt;p&gt;Yet another thing to think about is the benefits of living in the 3D printing era, where everything is customizable.
Why do we want to force ourselves into form factors which are not suitable at all?
With the above in mind, I think it’s time to design a server rack.&lt;/p&gt;

&lt;h2 id=&quot;project-mini-rack&quot;&gt;Project MINI RACK?&lt;/h2&gt;

&lt;p&gt;Fun fact, originally the name of the project I had in mind was Mini Rack.
Until I saw the &lt;a href=&quot;https://www.youtube.com/c/JeffGeerling&quot;&gt;YouTuber Jeff Geerling&lt;/a&gt; introduce his &lt;a href=&quot;https://www.youtube.com/watch?v=y1GCIwLm3is&quot;&gt;Project Mini Rack&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/jeff-geerling-mini-racks.jpg&quot; alt=&quot;Picture from Jeff Geerling&apos;s Mini Racks&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Picture from Jeff Geerling&apos;s Mini Racks &lt;a href=&quot;https://mini-rack.jeffgeerling.com&quot;&gt;mini-rack.jeffgeerling.com&lt;/a&gt;&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;After that I’ve decided to change the project name to TinyRack to avoid confusion.
A bit like my TinyRack project, the Project Mini Rack is targeting mini form factor servers.
I really like the concept, it’s super cool that the whole server rack is portable without going offline if you have a UPS built into the rack.
But it’s based on the 1U standard, and the rack you can buy from Amazon costs $129.99 as of when I wrote this article.
It’s a bit overkill with the heavy duty metal rack.
For now, I only want a modular server rack that can host my cluster on top of it and potentially be extended with different modules.
That’s why, despite having seen a project like that, I still decided to continue building my own.
I think there’s some overlap with the target audience, but overall, TinyRack is targeting more lightweight users who prefer a rack with a smaller footprint and a more flexible design.
And more importantly, it’s all 3D printable!&lt;/p&gt;

&lt;h2 id=&quot;the-design&quot;&gt;The design&lt;/h2&gt;

&lt;p&gt;When designing the rack, I wanted to make it able to carry as heavy a load as possible.
Because while mini servers are not too heavy compared to a full 1U server, they aren’t lightweight either.
There could also be many devices to put on top of the rack.
I have purchased some Wire Racks and assembled them a while back.
I got inspiration from the way they work.
You have rods taking vertical loads, with the plastic clips that come with a ramp and that clamp on the rod’s notch, and with the wire rack mounting on top of them. Surprisingly, this simple structure can take a ton of load.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/wire-rack.jpg&quot; alt=&quot;A Wire Rack from Amazon&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A &lt;a href=&quot;https://www.amazon.com/Amazon-Basics-Adjustable-Shelving-Organizer/dp/B01LYBQXRH&quot;&gt;Wire Rack&lt;/a&gt; from Amazon&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I wanted to design a similar structure, with a strong post, with notches cut into it every so often.
Have a clip with a ramp that hooks on top of the notch.
Then the platform has holes that can sit on top of the clip, and because of the ramp of the clip, the platform will be stuck more firmly when a load is applied.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/design.png&quot; alt=&quot;A Wire Rack structure&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;The design of the TinyRack load bearing structure, the platform pushes against the clip, and the clip pushes against the post&apos;s notch&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;A clip looks like this:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/clip-cad.png&quot; alt=&quot;The design of a clip in the CAD model&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;The design of a clip in the CAD model&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;And here’s how the whole post looks like:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/notched-post-cad.png&quot; alt=&quot;The design of a post with a notch for holding the platform in the CAD model&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;The design of a post with a notch for holding the platform in the CAD model&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;And the platform looks like this:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/platform.png&quot; alt=&quot;The design of a platform in the CAD model&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;The design of a platform in the CAD model&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;And I wanted to have a rubber leveling feet for each post, to absorb vibration, so I added a thread at the bottom.
The size of the thread is set to 5/16” (8mm) so that you can buy &lt;a href=&quot;https://www.amazon.com/dp/B0DFW23LNC&quot;&gt;rubber feet from Amazon&lt;/a&gt; and install them by screwing them into the thread.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/rubber-leveler.jpg&quot; alt=&quot;A rubber leveling feet from Amazon&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A &lt;a href=&quot;https://www.amazon.com/dp/B0DFW23LNC&quot;&gt;rubber leveling feet&lt;/a&gt; from Amazon&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;And because the posts have limited height, I also added a screw hole on top of the post so that you can attach multiple posts together.
The whole assembly looks like this in the CAD model:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/assembly.png&quot; alt=&quot;The whole assembly of the TinyRack posts in the CAD model&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;The whole assembly of the TinyRack in the CAD model&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;the-cherry-on-the-top-the-jetkvm-mount&quot;&gt;The cherry on the top, the JetKVM mount&lt;/h2&gt;

&lt;p&gt;On top of the three mini PCs in my bare metal Kubernetes cluster, I also found myself in need of a remote KVM system.
A while back, I found a new affordable KVM system called &lt;a href=&quot;https://www.jetkvm.com&quot;&gt;JetKVM&lt;/a&gt; that pretty much meets my requirements.
It’s very tiny, yet provides most of the features I need.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/jetkvm.png&quot; alt=&quot;JetKVM is a tiny KVM system that provides remote access to your PCs through a web interface&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;JetKVM is a tiny KVM system that provides remote access to your PCs through a web interface&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;It even provides an external module for controlling the power that goes into the mini PCs, so that in case the server crashes and fails to restart, you can force it to reboot by cutting off the power and turning it on again.
While it’s really nice that the JetKVM provides remote access to my mini PCs, it also brings more headaches to cable management.&lt;/p&gt;

&lt;p&gt;Thanks to 3D printing and the CAD skills I’ve picked up along the way building all these, I soon designed a mount to sit on top of each mini PC and have the JetKVM, the DC module, and the cable sit in place.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/jetkvm-mount-base-cad.png&quot; alt=&quot;The design of a JetKVM mount base in the CAD model&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;The design of a JetKVM mount in the CAD model&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/jetkvm-mount-with-lid-cad.png&quot; alt=&quot;The design of a JetKVM mount with a lid in the CAD model&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;The design of a JetKVM mount with a lid in the CAD model&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/jetkvm-mount-base.jpg&quot; alt=&quot;TinyRack JetKVM mount base in real world&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;TinyRack JetKVM mount base in real world&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/jetkvm-mount-with-lid.jpg&quot; alt=&quot;TinyRack JetKVM mount with lid in real world&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;TinyRack JetKVM mount with lid in real world&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;It looks so beautiful right now, it almost feels like a device designed to work exactly that way as a whole 😍
You can also download my JetKVM mount from TinyRack.io.&lt;/p&gt;

&lt;h2 id=&quot;opengrid-integration&quot;&gt;openGrid integration&lt;/h2&gt;

&lt;p&gt;I used to have pretty good cable management under my desk.
But over time, as more and more devices were added, it became messy again.
With a 3D printer, I started looking into cable management solutions, and I found project &lt;a href=&quot;https://handsonkatie.com/underware-2-0-the-made-to-measure-collection/&quot;&gt;Underware&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/underware.webp&quot; alt=&quot;Underware is a 3D printed grid based modular system for under desk cable management by Katie&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Underware is a 3D printed grid based modular system for under desk cable management by &lt;a href=&quot;https://handsonkatie.com&quot;&gt;Katie&lt;/a&gt;&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;It’s a grid based modular system to have different modules attach to the grid.
This is exactly what I wanted!
In the past, when I designed my cable routing under the desk, it was designed as it was.
But then when new things were added, the old design didn’t anticipate the new stuff, so it became a mess real quick.
It’s really hard to change once the design is settled.
With a grid based modular system, I can change the routing any time I want without removing the double tape and applying new tape again.&lt;/p&gt;

&lt;p&gt;The Underware 1.0 is based on &lt;a href=&quot;https://www.multiboard.io&quot;&gt;Multiboard&lt;/a&gt;, and the license of Multiboard is a bit odd.
All of your creations derived from Multiboard go to the author of Multiboard and allow them to use them freely as they see fit.
I guess this could be the reason, the author of Underware switched to &lt;a href=&quot;https://www.opengrid.world&quot;&gt;openGrid&lt;/a&gt; in Underware 2.0.
It’s a similar grid based modular system, but with a &lt;a href=&quot;https://creativecommons.org/licenses/by/4.0/&quot;&gt;CC-BY license&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/opengrid-grid.webp&quot; alt=&quot;An openGrid grid from Katie&apos;s Underware 2.0 website&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;An openGrid grid from &lt;a href=&quot;https://handsonkatie.com/underware-2-0-the-made-to-measure-collection/&quot;&gt;Katie&apos;s Underware 2.0 website&lt;/a&gt;&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;After I built some cable management with Underware 2.0, I really liked the ecosystem behind it.
I looked at my TinyRack system and wondered, why not make it possible to integrate TinyRack with openGrid?
That way, we can have a modular mounting system that can grow vertically and potentially horizontally.
With that in mind, I’ve designed an adapter to mount an openGrid panel to TinyRack posts.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/tinyrack-opengrid-adapter-cad.png&quot; alt=&quot;The design of a TinyRack adapter for openGrid in the CAD model&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;The design of a TinyRack adapter for openGrid in the CAD model&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-11-26-tinyrack-a-3d-printable-modular-rack-for-mini-server/tinyrack-opengrid.jpg&quot; alt=&quot;TinyRack with openGrid on the third layer with some Underware 2.0 modules attached to it&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;TinyRack with openGrid on the third layer with some Underware 2.0 modules attached to it&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Now, with TinyRack and openGrid adapters, things get very interesting.
You can build different kind of layers, some of them can be used for cable management, some of them can be used for holding devices.
I plan to introduce more modules, like ethernet patch panels.&lt;/p&gt;

&lt;p&gt;Please note that while openGrid can carry some load with TinyRack adapters, they are not designed to carry a ton of load, unlike the TinyRack platform.
It’s not tested yet how far we can push the grid with load.&lt;/p&gt;

&lt;h2 id=&quot;more-to-come&quot;&gt;More to come&lt;/h2&gt;

&lt;p&gt;That’s it!
It’s a super fun 3D printing project.
I have learned so much about CAD tricks and 3d printing knowledge while building these.
Actually, I have built way more than described in this article.
I will find time to share more of them later.&lt;/p&gt;

&lt;p&gt;In the meantime, other than building it for my own servers, I want to see if I can run it as a side business.
If you don’t have a 3D printer, remember, you can also purchase them on &lt;a href=&quot;https://tinyrack.io&quot;&gt;TinyRack.io&lt;/a&gt;.
Also, at this moment, all the models come with fixed parameters, but in the future, I may build online tools to make customizing much easier.
Thanks for reading, stay tuned for more!&lt;/p&gt;
</description>
        <pubDate>Wed, 26 Nov 2025 07:00:00 +0000</pubDate>
        <link>https://fangpenlin.com/posts/2025/11/26/tinyrack-a-3d-printable-modular-rack-for-mini-server/</link>
        <guid isPermaLink="true">https://fangpenlin.com/posts/2025/11/26/tinyrack-a-3d-printable-modular-rack-for-mini-server/</guid>
      </item>
    
      <item>
        <title>Continual learning with the Marketplace algorithm: model learns new data through inference, not training</title>
        <description>&lt;style type=&quot;text/css&quot;&gt;
  figure {
    text-align: center;
    margin: 0 auto;
  }
  figcaption, figcaption p {
    color: grey;
    font-size: 0.9em;
  }
  figure img {
    max-width: 100%;
  }
&lt;/style&gt;

&lt;p&gt;Have you ever wondered why machines need a dedicated training process while humans can learn from experience?
I wondered the same thing for a long time.
Today, I’d like to introduce continual learning with the Marketplace algorithm, which demonstrates the possibility of machines learning new things by simply doing!&lt;/p&gt;

&lt;p&gt;This is the third article in the Marketplace algorithm series.
Please read the &lt;a href=&quot;/posts/2025/08/18/marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/&quot;&gt;first article&lt;/a&gt; and &lt;a href=&quot;/posts/2025/09/02/marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/&quot;&gt;second article&lt;/a&gt; for details on the Marketplace algorithm.
Last week, I published the second article, which discusses using all the probes to compose the best parameter delta. It was a lot of fun! 😄&lt;/p&gt;

&lt;p&gt;However, training a model like a normal training process is not the most exciting application of the Marketplace algorithm.
The previous articles were just appetizers; the main course is here.
The most intriguing application of the Marketplace algorithm is continual learning.
I had this idea almost immediately after developing the Marketplace algorithm.
After running through the concept in my mind, I believed it was feasible.
So, I spent a few days implementing it, and it worked!
It still has a long way to go, but it already shows great potential.&lt;/p&gt;

&lt;p&gt;The experiment’s design is straightforward.
First, I trained the &lt;a href=&quot;https://github.com/tinygrad/tinygrad/blob/9182948951e9c8a1d6432ce983b4f497badec862/examples/beautiful_mnist.py&quot;&gt;beautiful MNIST model from Tinygrad&lt;/a&gt; using the Marketplace V2 algorithm and the digits dataset for 2,000 steps, achieving 96% accuracy on the validation dataset.
Next, I took the trained model, simulated the inference process, and added class 3 (dress) from the Fashion MNIST dataset, mixing these images with the digits dataset to allow the model to classify them.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/mnist-digits-plus-fashion-3.png&quot; alt=&quot;A diagram showing the original MNIST digits dataset and the new dataset, which combines the MNIST digits dataset with class 3 (dress) from the Fashion MNIST dataset.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing the original MNIST digits dataset and the new dataset, which combines the MNIST digits dataset with class 3 (dress) from the Fashion MNIST dataset.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I applied the Marketplace algorithm to enable the model to continually learn the new dress images gradually with each step.
The goal was to determine whether the model could learn the new dress images primarily through inference, without dedicated training, while still classifying digits correctly most of the time to provide business value.
Here’s the result:&lt;/p&gt;

&lt;div class=&quot;center mb2&quot;&gt;
    &lt;video style=&quot;width: 650px; margin: 0 auto&quot; crossorigin=&quot;anonymous&quot; width=&quot;650&quot; height=&quot;650&quot; playsinline=&quot;&quot; src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/continual-learning-fashion-replay.mp4&quot; muted=&quot;&quot; controls=&quot;&quot;&gt;&lt;/video&gt;
    &lt;div class=&quot;small text-center&quot;&gt;
      Video showing the model gradually learning the new dress images while still classifying digits correctly most of the time. All these steps involve only inference, with no dedicated training process!
    &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;As shown, the model gradually learns the new dress images over several steps while maintaining its ability to classify digits correctly most of the time.
These steps involve only inference, with no dedicated training process!&lt;/p&gt;

&lt;p&gt;The implications of this technology are tremendous.
I believe the future of machine learning lies in learning rather than training.
Companies that master this approach in production will gain a significant advantage because their models improve as more people use them, quickly and without much additional cost.
As the model improves, it attracts more users, creating a flywheel effect: the more it’s used, the better it becomes.
Best of all, this approach requires almost no additional computational cost for training.
Of course, this is just a proof of concept, and there are still many improvements to make and challenges to overcome.
Nevertheless, I’m thrilled about the possibilities of continual learning.
Today, I’m excited to share my first take on continual learning with the Marketplace algorithm.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;context-for-new-readers&quot;&gt;Context for New Readers&lt;/h2&gt;

&lt;p&gt;This article continues my journey to tackle the challenge of eliminating backpropagation using first principles.
As this series of articles is now reaching a wider audience, some new readers may have missed the context of this article, so I’d like to provide it here.
I aim to explore how far I can push this idea without referring to academic papers. I haven’t read any papers specifically on this topic.
Many people on X have recommended academic papers to me, and I truly appreciate their suggestions.
Unfortunately, I don’t have time to read all the papers thoroughly.
If I had to read every paper before starting my research, I’d see you again in five years! 😂&lt;/p&gt;

&lt;p&gt;For this reason, my definition of continual learning may differ from others’.
Here’s my definition of continual learning:
It means that a model can learn new concepts primarily through inference alone.
The model should adapt to new data gradually.
Others may have explored similar ideas, but I’m unaware of their work.
As this is independent research, please take it with a grain of salt.&lt;/p&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How It Works&lt;/h2&gt;

&lt;p&gt;Previously, when using the marketplace algorithm to train a model, we fed the same batch of data with different permutations of vendor deltas for the model parameters.
We calculated the loss of the final output and attributed it to the parameter delta that contributed to it, then composed the reconciled delta as the direction to update the model parameters.
Consequently, the same data passed through the model via all possible paths.&lt;/p&gt;

&lt;p&gt;For inference, this approach is impractical due to high computational costs.
The advantage of enabling the model to learn new information through inference is that it operates at a way larger scale.
We can process a large volume of input data through the model across various paths.
With that in mind, instead of passing data through all possible paths, we select just one random path for each inference.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/pass-through-random-path.svg&quot; alt=&quot;A diagram showing the inference process using a single random forward pass instead of all possible paths.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing the inference process using a single random forward pass instead of all possible paths.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;After passing data through the model, we retain the chosen random path.
If the label is known, we keep the loss; otherwise, we store the logits and input data for later labeling.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/data-to-keep.png&quot; srcset=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/data-to-keep.png 1x, /images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/data-to-keep@2x.png 2x&quot; alt=&quot;A diagram showing that we retain the chosen random path and either the logits or loss, depending on whether the input data label is known.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing that we retain the chosen random path and either the logits or loss, depending on whether the input data label is known.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;After collecting a sufficient number of paths along with their logits or loss, we can generate the reconciled delta, as described in the V2 article.
Before doing so, we may want to filter out unrepresentative data.
In this step, we can select which final outputs (you may need to keep at least some metadata to tell which to keep or not) participate in the reconciliation process, giving us greater control over the direction of the reconciled delta.
For example, we can exclude extreme outliers that are not representative of the data.
While we did not apply this filtering in our experiment, but I think it makes sense to do it in the production environment.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/filter-data.svg&quot; alt=&quot;A diagram showing the filtering of collected paths and logits or loss based on specific criteria to ensure data quality.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing the filtering of collected paths and logits or loss based on specific criteria to ensure data quality.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If the collected data is unlabeled, we must label it first and then compute the loss from the stored logits.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/label-data.svg&quot; alt=&quot;A diagram showing the labeling process for input data when the label is unknown.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing the labeling process for input data when the label is unknown.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Using the collected paths and loss, we can then generate the attribution for each loss.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/attributions.svg&quot; alt=&quot;A diagram showing the calculation of attribution for each parameter delta by standardizing the loss using collected paths and loss.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing the calculation of attribution for each parameter delta by standardizing the loss using collected paths and loss.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;And then likewise, we can generate the reconciled delta by multiplying the attribution from the loss by the contributing parameter delta and summing them.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/attribution-combined.svg&quot; alt=&quot;A diagram showing the process of multiplying the attribution from the loss by the contributing parameter delta and summing them to generate the reconciled delta.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing the process of multiplying the attribution from the loss by the contributing parameter delta and summing them to generate the reconciled delta.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;And then, we update the model parameters with the reconciled delta.
That’s it!
We just process a step in continual learning, performed entirely through inference.
This step is analogous to a minibatch in the training process.
The model continues to provide business value during this process, with the only additional cost being the reconciliation step.&lt;/p&gt;
&lt;h2 id=&quot;the-first-experiment-learning-to-classify-dresses&quot;&gt;The First Experiment: Learning to Classify Dresses&lt;/h2&gt;

&lt;p&gt;Let’s recap the experiment introduced at the beginning of this article.
We trained a model using the Marketplace V2 algorithm on the MNIST digits dataset for 2,000 steps, achieving 96% accuracy on the validation dataset.
We then took the trained model and simulated the inference process, incorporating class 3 (dress) from the MNIST fashion dataset. We mixed these dress images with the digits dataset and tasked the model with classifying the combined images.
For each step, we used 240 images from the original dataset and 16 images from the new dataset, totaling 256 images. The marketplace shape was 4x4x4, i.e, 64 probes in total.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/mnist-digits-plus-fashion-3.png&quot; alt=&quot;Diagram showing the original MNIST digits dataset and the new dataset combining MNIST digits with class 3 (dress) from the MNIST fashion dataset.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram showing the original MNIST digits dataset and the new dataset, which combines the MNIST digits dataset with class 3 (dress) from the MNIST fashion dataset.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I must admit, this experiment is far from perfect.
It serves as a proof of concept to demonstrate the potential of tuning a model using mostly inference. After all, it’s unusual to present a dress image and expect the model to classify it as the digit 3.
In real-world scenarios, data shifts are likely to occur gradually rather than through abrupt changes like this one. Despite its flaws, the experiment shows promising results.&lt;/p&gt;

&lt;p&gt;The learning (inference) and validation accuracy for the new data (class 3 from the MNIST fashion dataset) are shown in the following figure:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/new-validation-accuracy.svg&quot; alt=&quot;Diagram with 50% smoothing showing the validation accuracy for class 3 (dress) from the MNIST fashion dataset increasing gradually to 86% at 100,000 steps.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing the validation accuracy for class 3 (dress) from the MNIST fashion dataset increasing gradually to 86% at 100,000 steps.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/new-learning-accuracy.svg&quot; alt=&quot;Diagram with 50% smoothing showing the learning (inference) accuracy for class 3 (dress) from the MNIST fashion dataset increasing gradually to 100% at 28,000 steps.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing the learning (inference) accuracy for class 3 (dress) from the MNIST fashion dataset increasing gradually to 100% at 28,000 steps.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As shown, the model gradually learns the new data.
Meanwhile, the testing accuracy for the original digits dataset is as follows:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/old-validation-accuracy.svg&quot; alt=&quot;Diagram with 50% smoothing showing the validation accuracy for the original MNIST digits dataset decreasing gradually from 96.2% to 95.2% after 100,000 steps.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing the validation accuracy for the original MNIST digits dataset decreasing gradually from 96.2% to 95.2% after 100,000 steps.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/old-learning-accuracy.svg&quot; alt=&quot;Diagram with 50% smoothing showing the learning (inference) accuracy for the original MNIST digits dataset increasing gradually to 100% at 55,000 steps.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing the learning (inference) accuracy for the original MNIST digits dataset increasing gradually to 100% at 55,000 steps.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The model successfully learns the new data while maintaining high accuracy on the original digits dataset most of the time.
However, the validation accuracy for the original digits dataset drops slightly, which is not ideal. More concerning is the downward trend, which we will discuss later.&lt;/p&gt;

&lt;p&gt;The loss decreases gradually for both the new data and the original dataset during the learning process (inference):&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/new-loss.svg&quot; alt=&quot;Diagram with 50% smoothing showing the loss for the new data decreasing gradually during the learning process (inference).&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing the loss for the new data decreasing gradually during the learning process (inference).&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/old-loss.svg&quot; alt=&quot;Diagram with 50% smoothing showing the loss for the original dataset decreasing gradually during the learning process (inference).&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing the loss for the original dataset decreasing gradually during the learning process (inference).&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The accuracy reaches 100% for both the new data and the original dataset during the learning process (inference). This suggests the model may be memorizing the data rather than generalizing effectively.
Given the slight decline in validation accuracy for the original dataset, I wondered whether this was due to the model overfitting or “lazily” memorizing the data. To explore this, I tested whether continual learning could still work if the original dataset was augmented.
Thus, I rerun the continual learning process with the original dataset augmented alongside the new data, using the &lt;a href=&quot;https://github.com/tinygrad/tinygrad/blob/c6c16b294616447238d5d19974bceca52c9f2a40/extra/augment.py#L11-L21&quot;&gt;augment function&lt;/a&gt; from the Tinygrad library.&lt;/p&gt;

&lt;p&gt;Here’s the accuracy for the new data with augmentation:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/augmented-new-validation-accuracy.svg&quot; alt=&quot;Diagram with 50% smoothing showing the validation accuracy for the new data increasing gradually, though more slowly than without augmentation, reaching 70% at 100,000 steps.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing the validation accuracy for the new data increasing gradually, though more slowly than without augmentation, reaching 70% at 100,000 steps.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/augmented-new-learning-accuracy.svg&quot; alt=&quot;Diagram with 50% smoothing showing the learning (inference) accuracy for the new data increasing gradually, though more slowly than without augmentation, reaching 100% at around 50,000 steps with slight fluctuations afterward.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing the learning (inference) accuracy for the new data increasing gradually, though more slowly than without augmentation, reaching 100% at around 50,000 steps with slight fluctuations afterward.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Interestingly, the accuracy for the new data with augmentation is lower than without augmentation. This suggests that the model’s learning capacity is being shared between the new data and the augmented original dataset.&lt;/p&gt;

&lt;p&gt;For the original dataset, the model initially struggles with augmented data, achieving only around 85% accuracy at the start. However, it quickly recovers and improves steadily:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/augmented-old-learning-accuracy.svg&quot; alt=&quot;Diagram with 50% smoothing showing the learning (inference) accuracy for the augmented original dataset increasing gradually to 95% at 100,000 steps.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing the learning (inference) accuracy for the augmented original dataset increasing gradually to 95% at 100,000 steps.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The validation accuracy for the augmented original dataset drops slightly more than without augmentation, from 96.2% to 94.6% after 100,000 steps, but remains within a reasonable range.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/augmented-old-validation-accuracy.svg&quot; alt=&quot;Diagram with 50% smoothing showing the loss for the augmented original dataset decreasing gradually from 96.2% to 94.6% at 100,000 steps.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing the loss for the augmented original dataset decreasing gradually from 96.2% to 94.6% at 100,000 steps.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The loss for the augmented original dataset also decreases gradually, though it starts from a higher point due to augmentation.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/augmented-old-loss.svg&quot; alt=&quot;Diagram with 50% smoothing showing the loss for the augmented original dataset decreasing gradually from a higher starting point than without augmentation.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing the loss for the augmented original dataset decreasing gradually from a higher starting point than without augmentation.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The loss for the new data also decreases steadily, but more slowly than without augmentation.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/augmented-new-loss.svg&quot; alt=&quot;Diagram with 50% smoothing showing the loss for the new dataset decreasing gradually, slightly higher than without augmentation.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing the loss for the new dataset decreasing gradually, slightly higher than without augmentation.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;second-experiment-with-a-new-digit&quot;&gt;Second Experiment with a New Digit&lt;/h2&gt;

&lt;p&gt;The drop in validation accuracy is concerning.
I’ve been considering why this is happening and how to address it.
Could it be because the MNIST digit dataset differs significantly from the Fashion-MNIST dataset in style?
To explore this, I designed an new experiment where we remove one digit from the original MNIST dataset and train the model to classify the missing digit.
Specifically, I trained a model on the MNIST dataset excluding the digit 9.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/mnist-digits-learning-9.png&quot; alt=&quot;Diagram showing the MNIST dataset excluding digit 9 and the new dataset including digit 9.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram showing the MNIST dataset excluding digit 9 and the new dataset including digit 9.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Next, I applied the marketplace algorithm to learn the digit 9 through inference.
Here are the results:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/digit-new-validation-accuracy.svg&quot; alt=&quot;Diagram with 50% smoothing showing validation accuracy for digit 9, starting at zero until around 18,000 steps, then gradually increasing to 71% by 100,000 steps.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing validation accuracy for digit 9, starting at zero until around 18,000 steps, then gradually increasing to 71% by 100,000 steps.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/digit-new-learning-accuracy.svg&quot; alt=&quot;Diagram with 50% smoothing showing learning (inference) accuracy for digit 9, starting at zero until around 18,000 steps, then gradually increasing to near 100% by 36,000 steps, with fluctuations afterward.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing learning (inference) accuracy for digit 9, starting at zero until around 18,000 steps, then gradually increasing to near 100% by 36,000 steps, with fluctuations afterward.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Initially, the validation accuracy for digit 9 remained at zero.
I suspected a bug in my code—why else would the accuracy stay at zero?
However, I noticed that the loss was decreasing, confirming there was no bug.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/digit-new-loss.svg&quot; alt=&quot;Diagram with 50% smoothing showing the loss for digit 9 decreasing gradually.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing the loss for digit 9 decreasing gradually.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The issue stemmed from the original model being trained without digit 9, while reserving a label slot for it, fixed at zero and expected to output as such.
This created inertia in the model against outputting 9.
The decreasing loss indicated learning was occurring, but the model hadn’t yet overcome this inertia.
After waiting patiently, the validation accuracy eventually increased.&lt;/p&gt;

&lt;p&gt;For the original dataset (digits 0–8), the inference accuracy increased gradually, reaching 100% by 50,000 steps, as expected.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/digit-old-learning-accuracy.svg&quot; alt=&quot;Diagram with 50% smoothing showing learning (inference) accuracy for the original dataset, increasing gradually to 100% by 50,000 steps.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing learning (inference) accuracy for the original dataset, increasing gradually to 100% by 50,000 steps.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;However, the validation accuracy for the original dataset dropped slightly from 96.8% to 95.2%, which was frustrating.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/digit-old-validation-accuracy.svg&quot; alt=&quot;Diagram with 50% smoothing showing validation accuracy for the original dataset, decreasing from 96.8% to 95.2%.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Diagram with 50% smoothing showing validation accuracy for the original dataset, decreasing from 96.8% to 95.2%.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;To see if we can eliminate the inertia, I retrained the model without digit 9, this time using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ignore_index&lt;/code&gt; feature &lt;a href=&quot;https://github.com/tinygrad/tinygrad/blob/239091d111caae102f8078c9179099cc0fb79997/tinygrad/tensor.py#L3987-L4010&quot;&gt;provided by the cross-entropy function&lt;/a&gt; to exclude digit 9 from the loss calculation.
My goal was to create a model neutral to the reserved label slot, enabling faster learning of new digits without inertia.
However, the model still exhibited some inertia against outputting 9, though less than before.
Although I couldn’t fully eliminate the inertia, the fact that the training algorithm can overcome it and successfully learn the new digit 9 is encouraging.&lt;/p&gt;

&lt;h2 id=&quot;why-is-the-validation-accuracy-for-the-original-dataset-decreasing&quot;&gt;Why Is the Validation Accuracy for the Original Dataset Decreasing?&lt;/h2&gt;

&lt;p&gt;Now, we know that with the Marketplace algorithm, we learn new data through just inference mostly without a dedicated training process.
However, the declining validation accuracy for the original dataset is concerning.
The goal of continual learning is to deliver business value while gradually incorporating new data.
If the validation accuracy continues to decline, the model may eventually fail to provide business value.&lt;/p&gt;

&lt;p&gt;With this in mind, I’ve carefully considered why the validation accuracy for the original dataset is decreasing.
After closely examining the chart, I noticed that the validation accuracy remains stable until we begin learning the new digit, number 9, and producing valid outputs.
My theory is that when new data is introduced, it generates high loss because the model has not encountered this type of data before.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/loss-gap.svg&quot; alt=&quot;A diagram showing the loss gap between the new digit, number 9, and the original dataset is huge at the beginning and gradually decreases.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing the loss gap between the new digit, number 9, and the original dataset is huge at the beginning and gradually decreases.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Given the significant loss gap, the Marketplace algorithm prioritizes parameter updates that reduce the loss for the new data, which are often low-hanging fruit.
However, the model already has parameters critical for correctly predicting the original dataset.
Modifying these critical parameters is costly.
In the early stages, the algorithm selects updates that are not critical to the original dataset, focusing on easy wins for the new data.
Over time, as the low-hanging fruit diminish, the algorithm begins to adjust parameters that are critical to both the new and original datasets.
Since the new data typically has a higher loss, the algorithm prioritizes updates that have a greater positive impact on the new data, even if they negatively affect the original dataset.&lt;/p&gt;

&lt;p&gt;If this theory is correct, I predict that a balance between the two datasets will eventually emerge.
When the loss for both datasets becomes nearly identical, the algorithm will select parameters that equally impact both datasets, stabilizing the validation accuracy for the original dataset.&lt;/p&gt;

&lt;p&gt;In the real world, data shifts are gradual, not sudden.
Therefore, the loss balance between old and new data patterns is unlikely to change drastically.
By continually applying the Marketplace algorithm, the loss for old and new data patterns should balance out shortly after a data shift occurs.
However, this is just a hypothesis, and other factors I haven’t considered may be causing the decline in validation accuracy.
Further investigation is needed to confirm this approach’s reliability.&lt;/p&gt;

&lt;p&gt;Another factor to consider is the fixed learning rate used for continual learning.
In traditional training, the learning rate is typically reduced over time to allow smaller, more precise adjustments, enabling the model to fine-tune the final decimal points of accuracy.
With a fixed learning rate, the model may overshoot when only small adjustments are needed, especially when the loss gap is minimal.
This forces the algorithm to select parameters that impact both old and new data.
To address this, we could dynamically adjust the learning rate based on the loss gap between the new and original datasets.
For example, using the variance or standard deviation of the loss gap to schedule the learning rate could allow the model to focus on fine-tuning when the loss gap is small, improving overall accuracy.&lt;/p&gt;

&lt;p&gt;Finally, model capacity is another important factor.
The current model is small, limiting the algorithm’s ability to select parameter changes that benefit the new data without negatively impacting the original dataset.
I wonder if a larger model would yield different results.&lt;/p&gt;

&lt;h2 id=&quot;what-does-this-mean-for-the-future-of-machine-learning&quot;&gt;What Does This Mean for the Future of Machine Learning?&lt;/h2&gt;

&lt;p&gt;If the potential impact of using the Marketplace algorithm for training is one, I would argue that its potential for continual learning is a thousand times greater—or more.
While it appears effective with a limited dataset and a very small model, this does not guarantee success with larger models or more complex datasets.
However, given the scale of inference—often millions or even billions of inferences per day—I believe we can make it work by leveraging the abundant probes provided by real-world model usage.
Even if the approach is only viable for small models, I can envision many exciting applications.
Here are some ideas:&lt;/p&gt;

&lt;h3 id=&quot;privacy-friendly-machine-learning&quot;&gt;Privacy-Friendly Machine Learning&lt;/h3&gt;

&lt;p&gt;Traditional machine learning often lacks privacy-friendliness.
The conventional process involves collecting all user data, running a forward pass, collecting logits, performing a backward pass, and updating model parameters.
This process is difficult to break down into smaller steps.
For example, consider a mobile app that predicts a user’s blood pressure for the next day based on their current health data.
The traditional approach requires collecting and anonymizing all user health data before keeping them for training.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/traditional-data-collection.svg&quot; alt=&quot;A diagram showing that the traditional approach to training a model requires collecting all user data and storing it on the server.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing that the traditional approach to training a model requires collecting all user data and storing it on the server with an anonymization process.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;However, what can we do if regulations, such as &lt;a href=&quot;https://www.hhs.gov/hipaa/for-professionals/security/laws-regulations/index.html&quot;&gt;HIPAA compliance&lt;/a&gt;, prohibit sending user data to the server?&lt;/p&gt;

&lt;p&gt;One straightforward solution is to run the backward pass on the user’s mobile device to compute the gradient and send only the gradient to the server to update the model parameters.
However, the backward pass is computationally expensive, and the gradient size is often as large as the model parameters, making it impractical to perform on a mobile device and transmit to the server.
Even for smaller models designed for edge devices, this approach may still be too resource-intensive.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/backprop-on-device.svg&quot; alt=&quot;A diagram showing a naive approach to training a model on the user&apos;s device, requiring the gradient from the backward pass to be sent to the server.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing a naive approach to training a model on the user&apos;s device, requiring the gradient from the backward pass to be sent to the server.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Moreover, sharing gradient data risks leaking user information, as gradients can potentially be reverse-engineered to reconstruct the original data.
However, the Marketplace algorithm offers a different approach.
If the model is small enough, the user’s device can run a forward pass with a random path and different vendor deltas, then send only the path and logits to the server.
The delta is generated from seed-based random numbers, making it easy to produce on the fly during the forward pass.
If a label is available, only the loss needs to be sent, and the logits can be discarded.
In cases where the label is provided naturally by the user after a prediction, this process avoids sending any local user data to the server.&lt;/p&gt;

&lt;p&gt;Logits are typically small, containing concentrated information that is difficult to use to deduce the original user data.
With loss, a single float value, it’s virtually impossible to reconstruct the user’s data, especially if the input data space is large.
(Well, not so fast.. 😅 speaking from infosec background, with enough samples, it might still be possible to reconstruct the user’s data, but that’s another story)&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/marketplace-on-device.svg&quot; alt=&quot;A diagram showing a privacy-friendly approach to training a model on the user&apos;s device using the Marketplace algorithm. The device only sends the chosen random path and the loss or logits to the server.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing a privacy-friendly approach to training a model on the user&apos;s device using the Marketplace algorithm. The device only sends the chosen random path and the loss or logits to the server.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;From the server’s perspective, collecting enough forward paths and losses allows it to perform the steps outlined in the V2 article to generate the reconciled delta.
This enables the server to update model parameters without accessing any user data.
This approach has significant potential for privacy-friendly machine learning.
Hey, Apple, I would be very interested in exploring this approach if I was you!😄&lt;/p&gt;

&lt;h3 id=&quot;tailored-model-for-each-user-or-group-based-on-usage-patterns&quot;&gt;Tailored Model for Each User or Group Based on Usage Patterns&lt;/h3&gt;

&lt;p&gt;Some argue that marketplace algorithms may not perform well for larger models.
I disagree, as they often overlook the significant differences in inference scale.
With large-scale inference, I believe we can make it work one way or another; the only question is how.
However, if the marketplace algorithm is indeed unsuitable for bigger models, what then?
This led me to question whether we truly need large models.&lt;/p&gt;

&lt;p&gt;The reason models are so large is that they aim to cover all possible scenarios.
In reality, different users have distinct needs.
What if we could train a model for each user without incurring extra costs?
Or, at the very least, for a group of users?
The exciting part is that the more users engage with the model, the better it becomes.&lt;/p&gt;

&lt;p&gt;One potential application of a marketplace continual learning algorithm is to start with a smaller base model, divide use cases into different groups, and then let the continual learning process adapt to the specific needs of each group.
This way, we can create models tailored to individual users or groups without significant additional costs.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/big-model-and-small-models.svg&quot; alt=&quot;A diagram showing that while a large model tries to cover all possible scenarios, a small model can be tailored to specific use cases.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing that while a large model tries to cover all possible scenarios, a small model can be tailored to specific use cases.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Even if the model is large, techniques like &lt;a href=&quot;https://arxiv.org/abs/2106.09685&quot;&gt;LoRA&lt;/a&gt; can be used to fine-tune only a small part of the model, keeping costs low.
The potential of this approach is highly promising.&lt;/p&gt;

&lt;h2 id=&quot;future-research-directions&quot;&gt;Future Research Directions&lt;/h2&gt;

&lt;p&gt;Several questions remain unanswered in this field.
Below, I outline some key areas for future exploration.&lt;/p&gt;

&lt;h3 id=&quot;how-does-inferencing-with-random-vendors-and-their-deltas-impact-inference-performance&quot;&gt;How does inferencing with random vendors and their deltas impact inference performance?&lt;/h3&gt;

&lt;p&gt;Currently, we observe no major differences in inference performance, likely due to the simplicity of our model and dataset.
However, with more complex models and datasets, the impact may be more pronounced.
Adding deltas to the original model weights requires careful consideration to avoid degrading inference performance, as we rely on accurate outputs for users.&lt;/p&gt;

&lt;h3 id=&quot;how-can-we-train-a-model-that-is-more-conducive-to-continual-learning&quot;&gt;How can we train a model that is more conducive to continual learning?&lt;/h3&gt;

&lt;p&gt;In our second experiment with the digit “9,” the model exhibited inertia, resisting outputting this digit even when it was excluded from the loss calculation.
This raises the question: Can we train a model with reserved label slots for new classes to be more continual learning-friendly?
Ideally, such a model would learn new data immediately without inertia.
Beyond classification models, other approaches may also enhance continual learning capabilities.&lt;/p&gt;

&lt;h3 id=&quot;developing-a-dataset-with-data-shift-for-testing-continual-learning&quot;&gt;Developing a dataset with data shift for testing continual learning&lt;/h3&gt;

&lt;p&gt;Our experiments currently rely on the MNIST dataset.
I am not familiar with other datasets.
I think MNIST is not ideal for testing novel concepts like continual learning.
A more suitable dataset would incorporate data shift, divided into groups where each group exhibits slight variations from the previous one.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-09-continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/continual-learning-dataset.svg&quot; alt=&quot;A diagram illustrating a dataset with data shift for testing continual learning.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram illustrating a dataset with data shift for testing continual learning.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Such a dataset would enable us to evaluate how well a model adapts to new data gradually during the continual learning process.&lt;/p&gt;

&lt;h3 id=&quot;what-are-the-limits-of-the-marketplace-algorithm&quot;&gt;What are the limits of the Marketplace algorithm?&lt;/h3&gt;

&lt;p&gt;The Marketplace algorithm has shown promise with small models and simple datasets.
However, its effectiveness with larger models and more complex datasets remains untested.
While it demonstrates significant potential as a proof of concept, further experiments are needed to assess its performance in these scenarios.
Given the scale of inference—often millions or billions of inferences per day—finding optimal parameters for updates is likely feasible, suggesting that these challenges are solvable.&lt;/p&gt;

&lt;h3 id=&quot;exploring-privacy-friendly-machine-learning&quot;&gt;Exploring privacy-friendly machine learning&lt;/h3&gt;

&lt;p&gt;It is often assumed that machine learning cannot prioritize privacy without compromising training.
However, the Marketplace algorithm suggests otherwise, enabling privacy-friendly machine learning.
This is particularly valuable in use cases like healthcare, where regulations such as HIPAA may prevent sending local user data to servers due to patient privacy concerns.
The Marketplace algorithm’s continual learning approach allows models to learn through inference alone, without transmitting sensitive data.&lt;/p&gt;

&lt;p&gt;My background in information security, from my master’s degree in computer science, informs my perspective on this topic.
Techniques like &lt;a href=&quot;https://en.wikipedia.org/wiki/Blind_signature&quot;&gt;blind signatures&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Homomorphic_encryption&quot;&gt;homomorphic encryption&lt;/a&gt;, and &lt;a href=&quot;https://en.wikipedia.org/wiki/Oblivious_transfer&quot;&gt;oblivious transfer&lt;/a&gt; offer interesting ways for protecting data privacy while providing service to the users.
I think similiarly, we can protect the privacy of the data while providing machine learning powered service to the users.
Privacy-friendly machine learning warrants further exploration and could be the subject of an entire article or more.&lt;/p&gt;

&lt;h2 id=&quot;join-the-research-effort&quot;&gt;Join the Research Effort!&lt;/h2&gt;

&lt;p&gt;For now this is still a solo research project.
There are many exciting avenues to explore with this algorithm.
As always, all my work is open-source under the MIT license and accessible to everyone:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/LaunchPlatform/marketplace&quot;&gt;https://github.com/LaunchPlatform/marketplace&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please feel free to contribute to the project, fork it, or conduct your own research.
I believe this algorithm offers a fresh perspective on machine learning.
And this is just the beginning.
Even if I hit a dead end someday, someone with greater insight might find a way to make it work or draw inspiration from it to create something even better.&lt;/p&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final thoughts&lt;/h2&gt;

&lt;p&gt;That’s a tons of things I have done in the past few weeks.
There are simply too many exciting avenues to explore with this algorithm.
For now, I’ll take a short break to focus on my SaaS products (like &lt;a href=&quot;https://beanhub.io&quot;&gt;BeanHub&lt;/a&gt;) for a short while.
In the meantime, I’m considering the next direction for my research.
Thank you for reading this article!
I hope you found it at least somewhat inspiring.
Stay tuned for upcoming articles. 😄&lt;/p&gt;
</description>
        <pubDate>Tue, 09 Sep 2025 07:00:00 +0000</pubDate>
        <link>https://fangpenlin.com/posts/2025/09/09/continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/</link>
        <guid isPermaLink="true">https://fangpenlin.com/posts/2025/09/09/continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/</guid>
      </item>
    
      <item>
        <title>Marketplace V2 is all you need: A training algorithm on par with backprop that needs only forward pass</title>
        <description>&lt;style type=&quot;text/css&quot;&gt;
  figure {
    text-align: center;
    margin: 0 auto;
  }
  figcaption, figcaption p {
    color: grey;
    font-size: 0.9em;
  }
  figure img {
    max-width: 100%;
  }
&lt;/style&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; Third article is here, &lt;a href=&quot;/posts/2025/09/09/continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/&quot;&gt;Continual Learning with Marketplace: Model Learns New Data with Mostly Inference&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two weeks ago, I published an article, &lt;a href=&quot;/posts/2025/08/18/marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/&quot;&gt;Marketplace: My first attempt at training without backprop on GPU efficiently&lt;/a&gt;.
To my surprise, it received far more positive feedback than I expected.
I was thrilled that people found my research project interesting, and I greatly appreciate the kind words from readers.&lt;/p&gt;

&lt;p&gt;Curious about how far I could push this idea, I spent another two weeks improving it.
Initially, my research focused on enhancing the scalability of the Marketplace algorithm.
I implemented &lt;a href=&quot;https://github.com/LaunchPlatform/marketplace/pull/1&quot;&gt;seed-based random number generation&lt;/a&gt; for each vendor’s weights, as mentioned in the previous article.
I also explored other ideas to improve the Marketplace algorithm, such as using a second forward pass to determine the optimal learning rate.
However, exploring permutations of the loss outputs to compose a better overall delta, accounting for both good and bad outcomes, truly blew my mind 🤯.&lt;/p&gt;

&lt;p&gt;The performance is now on par with backpropagation in certain configurations.
Here’s the comparison with backpropagation plus &lt;a href=&quot;https://en.wikipedia.org/wiki/Stochastic_gradient_descent&quot;&gt;Stochastic Gradient Descent (SGD)&lt;/a&gt; as the optimizer:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/cmp-accuracy.svg&quot; alt=&quot;A diagram with 50% smoothing shows the comparison of the validation accuracy between the Marketplace V2, V1 algorithms and the backprop with SGD as the optimizer based on different learning rates. The Marketplace V2 algorithm outperforms the Marketplace V1 algorithm greatly and the backprop with SGD as the optimizer at 1e-3 learning rate. It only lose to the backprop with SGD as the optimizer at 3e-3 learning rate and 7e-3 learning rate. While backprop with SGD as the optimizer is still better, but I believe with hyperparameter tuning, the Marketplace V2 algorithm can at least match it.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram with 50% smoothing shows the comparison of the validation accuracy between the Marketplace V2, V1 algorithms and the backprop with SGD as the optimizer based on different learning rates. The Marketplace V2 algorithm outperforms the Marketplace V1 algorithm greatly and the backprop with SGD as the optimizer at 1e-3 learning rate. It only lose to the backprop with SGD as the optimizer at 3e-3 learning rate and 7e-3 learning rate. While backprop with SGD as the optimizer is still better, but I believe with hyperparameter tuning, the Marketplace V2 algorithm can at least match it.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/cmp-loss.svg&quot; alt=&quot;A diagram with 50% smoothing shows the comparison of the loss between the Marketplace V2, V1 algorithms and the backprop with SGD as the optimizer based on different learning rates. The Marketplace V2 algorithm outperforms the Marketplace V1 algorithm greatly and the backprop with SGD as the optimizer at 1e-3 learning rate and 3e-3 learning rate in later steps. It only lose to the backprop with SGD as the optimizer at 7e-3 learning rate. While backprop with SGD as the optimizer is still better, but I believe with hyperparameter tuning, the Marketplace V2 algorithm can at least match it.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram with 50% smoothing shows the comparison of the loss between the Marketplace V2, V1 algorithms and the backprop with SGD as the optimizer based on different learning rates. The Marketplace V2 algorithm outperforms the Marketplace V1 algorithm greatly and the backprop with SGD as the optimizer at 1e-3 learning rate and 3e-3 learning rate in later steps. It only lose to the backprop with SGD as the optimizer at 7e-3 learning rate. While backprop with SGD as the optimizer is still better, but I believe with hyperparameter tuning, the Marketplace V2 algorithm can at least match it.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I believe my research has a significant potential to revolutionize the machine learning training process.
Today, I’m excited to share the improvements to the Marketplace algorithm, which I call the Marketplace V2 algorithm.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;why-does-marketplace-work&quot;&gt;Why Does Marketplace Work?&lt;/h2&gt;

&lt;p&gt;In my last article, I explained how it works.
However, I didn’t delve into &lt;em&gt;why&lt;/em&gt; it works.
After publishing, I reflected on this question because understanding why it works is crucial for improving the algorithm, otherwise, I’d be left guessing and testing.
While I haven’t had time to test these hypotheses, here’s my reasoning.&lt;/p&gt;

&lt;p&gt;When discussing gradient descent, people often compare it to walking down a hill.
This analogy isn’t entirely accurate because the terrain appears to change when you mutate parameters, as noted in &lt;a href=&quot;https://www.youtube.com/watch?v=NrO20Jb-hy0&quot;&gt;this video&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/ml-loss-curve-terrain-change.png&quot; alt=&quot;A screenshot of a video titled &apos;The Misconception that Almost Stopped AI,&apos; explaining that the terrain changes when parameters are mutated.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A screenshot of &lt;a href=&quot;https://www.youtube.com/watch?v=NrO20Jb-hy0&quot;&gt;a video&lt;/a&gt; titled &quot;The Misconception that Almost Stopped AI,&quot; explaining that the terrain changes when parameters are mutated.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Despite it may not be 100% accurate, this analogy provides a good intuition for gradient descent.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/move-down-hill.svg&quot; alt=&quot;A diagram of a curve with an arrow pointing in the direction of the steepest descent.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram of a curve with an arrow pointing in the direction of the steepest descent.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;With backpropagation, we measure the slope of the terrain and descend carefully.
But do we really need such precision?
The Marketplace algorithm, instead of measuring the slope, feels more like sending probes in random directions and selecting the best one.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/random-probes.svg&quot; alt=&quot;A diagram of probes sent in random directions to find the best one.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram of probes sent in random directions to find the best one.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The algorithm’s clever trick is reusing intermediate products to efficiently create more permutations of the probe candidates.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/permutations-of-random-probes.svg&quot; alt=&quot;A diagram showing more colored probes in random directions than the previous diagram, reusing intermediate products to create more permutations efficiently.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing more colored probes in random directions than the previous diagram. We reuse intermediate products to create more permutations of the best probe efficiently.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The more probes you send, the higher the chance of finding good ones, some of which will roughly move in the right direction.
With a large model, the number of parameters creates a vast search space.
But are all parameters equally important?
The &lt;a href=&quot;https://arxiv.org/abs/1803.03635&quot;&gt;lottery ticket hypothesis&lt;/a&gt; suggests that only a small subset of parameters are critical.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/lottery-ticket-hypothesis.png&quot; alt=&quot;A screenshot of a video about the lottery ticket hypothesis.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A screenshot of &lt;a href=&quot;https://www.youtube.com/watch?v=jeFMWtddkTs&quot;&gt;a video&lt;/a&gt; about the &lt;a href=&quot;https://arxiv.org/abs/1803.03635&quot;&gt;lottery ticket hypothesis&lt;/a&gt;.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Thus, for probes to trend in the right direction, only a small subset of parameters needs to align correctly.
Most parameters are likely just noise.
As long as a probe’s overall direction has more upside than downside, it’s good enough.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/upside-and-downside.svg&quot; alt=&quot;A diagram showing that probes (delta) may contain positive and negative changes, but as long as the upside outweighs the downside, the probe is good enough.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing that probes (delta) may contain positive and negative changes, but as long as the upside outweighs the downside, the probe is good enough.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The Marketplace algorithm selects the best combination of parameters from the probes.
Since the leading vendor for each specification remains unchanged, it only mutates if the new direction is slightly better.
This process accumulates small, fortunate changes over time, eventually leading to a good solution.&lt;/p&gt;

&lt;h2 id=&quot;the-problem-with-marketplace&quot;&gt;The Problem with Marketplace&lt;/h2&gt;

&lt;p&gt;As mentioned in the previous article, the Marketplace algorithm selects only the best parameter combination from the probes, discarding all others.
“Best” refers to the combination with the lowest loss.
While this combination reduces overall loss, it’s an all-or-nothing approach.
By accepting the best probes, we also incorporate mutations that may increase loss in some aspects.&lt;/p&gt;

&lt;p&gt;This is wasteful because other combinations also provide valuable directional information.
The best probes have more beneficial mutations than harmful ones, which is why they’re selected.
However, as parameters improve, finding better mutations becomes harder, and the probability of good mutations decreases compared to bad ones.
At that point, more probes are needed to find a good mutation.&lt;/p&gt;

&lt;p&gt;Let’s view the Marketplace algorithm through the lens of a real-world marketplace.
When a product sells well, multiple factors contribute to its success.
The same applies to factors causing poor performance.
For a vendor, many parameters influence the product, making it hard to pinpoint the most critical one.
But what if you have enough products with slightly adjusted parameter permutations?
Couldn’t you statistically attribute the product’s performance to the parameter adjustments?
Yes!
That’s the core idea behind the Marketplace V2 algorithm.&lt;/p&gt;

&lt;h2 id=&quot;the-v2-algorithm&quot;&gt;The V2 Algorithm&lt;/h2&gt;

&lt;p&gt;The V2 algorithm is largely similar to the V1 algorithm, except that we reward each parameter delta based on the performance of the final product and compose the overall delta from the delta of each parameter.
Please read the &lt;a href=&quot;/posts/2025/08/18/marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/&quot;&gt;first article&lt;/a&gt; for the details of the V1 algorithm.
Here’s how it works:&lt;/p&gt;

&lt;p&gt;There are combinations of loss and their corresponding paths.
Let’s say each loss is \(L_i\), the parameter is \(\theta\), and the corresponding delta is \(\Delta \theta_i\), where \(i\) is the index of each unique path. The mean loss is:&lt;/p&gt;

\[\mu = \frac{1}{n} \sum_{i=1}^{n} L_i\]

&lt;p&gt;The standard deviation of the loss is:&lt;/p&gt;

\[\sigma = \sqrt{\frac{1}{n} \sum_{i=1}^{n} (L_i - \mu)^2}\]

&lt;p&gt;We normalize the loss by subtracting the mean value and dividing by the standard deviation.
We call this attribution and denote it as \(A_i\):&lt;/p&gt;

\[A_i = - \frac{L_i - \mu}{\sigma}\]

&lt;p&gt;Note that we invert the sign because we want to reward parameter deltas that contribute to a lower loss and penalize those that contribute to a higher loss.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/attributions.svg&quot; alt=&quot;Diagram showing conversion of the loss of each final product, calculation of attribution by normalizing the loss, and flipping the sign.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing the conversion of the loss of each final product, calculation of attribution by normalizing the loss, and flipping the sign.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Then, we attribute the loss to the parameter delta that contributed to it:&lt;/p&gt;

\[\Delta R = \sum_{i=1}^{n} A_i \times \Delta \theta_i\]

&lt;p&gt;We call \(\Delta R\) the reconciled delta because it accounts for the performance of the final product for each parameter. It’s like distributing the profit or loss to the parameter deltas that contributed to it.&lt;/p&gt;

&lt;p&gt;To calculate the reconciled delta, the process is straightforward. We take the attribution value \(A_i\) and multiply it by the parameter delta \(\Delta \theta_i\) for each unique path, i.e., the vendors’ deltas on each path.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/multiply-attributions.svg&quot; alt=&quot;Diagram showing multiplication of the attribution value with the parameter delta of vendors at a unique path.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing multiplication of the attribution value with the parameter delta of vendors at a unique path.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/multiply-attributions-2.svg&quot; alt=&quot;Diagram showing multiplication of the attribution value with the parameter delta of vendors at a different unique path.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing multiplication of the attribution value with the parameter delta of vendors at a different unique path.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Finally, we sum the delta multiplied by the attribution value for each unique path:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/attribution-combined.svg&quot; alt=&quot;Diagram showing the summation of the delta multiplied by the attribution value for each unique path.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing the summation of the delta multiplied by the attribution value for each unique path.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The reconciled delta \(\Delta R\) is a vector pointing in the direction that leads to better overall performance.
We believe this is somewhat similar to the gradient direction in backpropagation.
The reconciled delta should approximate the gradient direction in backpropagation, and with more samples, the direction should converge closer to the true gradient direction.&lt;/p&gt;

&lt;p&gt;From a linear algebra perspective, we are essentially composing a linear combination of the parameter deltas at each path.
For the direction that is most correct, we assign more weight to the parameter delta at that path.
For those pointing in the wrong direction, we assign a negative weight to the parameter delta to redirect it toward the correct direction.
For neutral directions, we assign weight close to zero to the parameter delta to avoid affecting the overall direction.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/attribute-to-random-probes.svg&quot; alt=&quot;Diagram showing attribution of the loss to the parameter delta vector at each path.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing attribution of the loss to the parameter delta vector at each path.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Since the directions are random, unrelated parameters in the vectors should cancel each other out.
A bad direction is also helpful in finding the right direction because we multiply it by a negative weight, effectively pointing it toward the correct direction.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/combined-vector.svg&quot; alt=&quot;Diagram showing that the combined vector approximates the gradient direction in backpropagation.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showing that the combined vector approximates the gradient direction in backpropagation.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As the scale of the reconciled delta may not be appropriate, we normalize it to a unit vector:&lt;/p&gt;

\[\Delta G = \frac{\Delta R}{||\Delta R||}\]

&lt;p&gt;We believe this unit vector direction is nearly identical to the gradient direction in backpropagation, so we call it the gradient direction and denote it as \(\Delta G\).
With the gradient direction \(\Delta G\), you can update the parameters by multiplying it by the learning rate \(\eta\):&lt;/p&gt;

\[\theta&apos; = \theta + \Delta G \times \eta\]

&lt;p&gt;The next is basically updating the seeds and generate random small weight deltas based on the seeds for each vendor, and then repeat the process.&lt;/p&gt;

&lt;h2 id=&quot;comparison-with-backprop&quot;&gt;Comparison with Backprop&lt;/h2&gt;

&lt;p&gt;In the previous article, I forgot to mention that the backprop experiment uses &lt;a href=&quot;https://arxiv.org/abs/1412.6980&quot;&gt;Adam&lt;/a&gt; as the optimizer.
I realized it’s somewhat unfair to compare the Marketplace approach with backprop because I was not only competing against backprop but also the Adam optimizer.
The backprop algorithm is highly performant, and Adam enhances its power.
I heard some people said that Adam is the optimizer on steroids, well, using steroid in a fight is obvious cheating, right?
In contrast, the Marketplace algorithm simply applies a learning rate to the gradient direction, which is essentially equivalent to SGD.
Therefore, it makes more sense to compare the Marketplace algorithm with backprop using SGD as the optimizer.&lt;/p&gt;

&lt;p&gt;Please note that in the previous article, we swapped the &lt;a href=&quot;https://docs.tinygrad.org/nn/?h=batchnorm#tinygrad.nn.BatchNorm&quot;&gt;Batch Normalization layer&lt;/a&gt; for an &lt;a href=&quot;https://docs.tinygrad.org/nn/?h=inst#tinygrad.nn.InstanceNorm&quot;&gt;Instance Normalization layer&lt;/a&gt; in the backprop algorithm.
However, when running backprop with SGD as the optimizer, we reverted to the Batch Normalization layer because the Instance Normalization layer does not perform well with SGD for this particular model.
For the Marketplace algorithm, we kept the Instance Normalization layer, as the Batch Normalization layer is less effective with this approach.&lt;/p&gt;

&lt;p&gt;With a more level playing field, the results are much more exciting.
I observed very similar performance between the Marketplace V2 algorithm and backprop with SGD as the optimizer.
Here are the results:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/cmp-accuracy.svg&quot; alt=&quot;A diagram with 50% smoothing showing the comparison of validation accuracy between the Marketplace V2, V1 algorithms, and backprop with SGD as the optimizer across different learning rates. The Marketplace V2 algorithm significantly outperforms the Marketplace V1 algorithm and backprop with SGD at a 1e-3 learning rate. It only falls behind backprop with SGD at 7e-3 and 3e-3 learning rates.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram with 50% smoothing showing the comparison of validation accuracy between the Marketplace V2, V1 algorithms, and backprop with SGD as the optimizer across different learning rates. The Marketplace V2 algorithm significantly outperforms the Marketplace V1 algorithm and backprop with SGD at a 1e-3 learning rate. It only falls behind backprop with SGD at 7e-3 and 3e-3 learning rates.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/cmp-loss.svg&quot; alt=&quot;A diagram with 50% smoothing showing the comparison of loss between the Marketplace V2, V1 algorithms, and backprop with SGD as the optimizer across different learning rates. The Marketplace V2 algorithm significantly outperforms the Marketplace V1 algorithm and backprop with SGD at a 1e-3 learning rate and at a 3e-3 learning rate in later steps. It only falls behind backprop with SGD at a 7e-3 learning rate.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram with 50% smoothing showing the comparison of loss between the Marketplace V2, V1 algorithms, and backprop with SGD as the optimizer across different learning rates. The Marketplace V2 algorithm significantly outperforms the Marketplace V1 algorithm and backprop with SGD at a 1e-3 learning rate. It only falls behind backprop with SGD at a 7e-3 learning rate.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Both Marketplace algorithms use the same market depth and vendor count: a market depth of 3 and 16 vendors per specification.&lt;/p&gt;

&lt;p&gt;Certainly, with the right learning rate, backprop with SGD as the optimizer still outperforms the Marketplace V2 algorithm.
However, this is already very exciting!
Unlike backprop, which requires both forward and backward passes, the Marketplace algorithm only needs the forward pass.
I haven’t spent much time on hyperparameter tuning for the Marketplace V2 algorithm because, with 16 vendors, the startup time for &lt;a href=&quot;https://github.com/tinygrad/tinygrad&quot;&gt;Tinygrad&lt;/a&gt; is lengthy due to its JIT compiler compiling kernel code for very complex compute graph, slowing down the hyperparameter tuning process (the new &lt;a href=&quot;https://x.com/__tinygrad__/status/1958718540841984393&quot;&gt;rangify feature&lt;/a&gt; could potentially solve this, but it’s not yet stable).
The fact that the Marketplace V2 algorithm shows nearly identical performance at certain learning rates suggests that this is likely a hyperparameter tuning issue.&lt;/p&gt;

&lt;p&gt;The Marketplace V2 algorithm is still new, and we are unsure how to optimally tune its hyperparameters to further improve performance.
However, I believe it’s only a matter of time before we find the right hyperparameters or optimize the algorithm itself to match or exceed the performance of backprop with SGD.
We can also scale the Marketplace V2 algorithm with more vendors and depth if a more accurate gradient direction is needed.
Additionally, I’ve experimented with techniques like using a second forward pass to adjust the learning rate, which shows promising results but requires further refinement.
Because it only requires a forward pass, the Marketplace V2 algorithm could, in theory, run at least twice as fast as backprop.
With these considerations, I assert that the Marketplace V2 algorithm is on par with backprop using SGD as the optimizer.
With further research and optimization, I firmly believe the Marketplace algorithm will even surpass backprop.&lt;/p&gt;

&lt;h2 id=&quot;what-are-the-implications&quot;&gt;What Are the Implications?&lt;/h2&gt;

&lt;p&gt;Some may ask: What’s the point of expending additional computational resources to achieve the same performance as backpropagation?
As mentioned in the previous article, the Marketplace algorithm relies solely on the forward pass to train the model.
Optimizing the forward pass is easier and more beneficial for inference performance than optimizing the backward pass.
Since backpropagation requires both forward and backward passes—and even assuming the backward pass takes the same time as the forward pass (though it typically takes much longer)—the Marketplace algorithm, by using only the forward pass, could be at least twice as fast as backpropagation.
Additionally, the cache-friendly nature of running only the forward pass could make it even faster in practice.
While we may lose some accuracy in the gradient direction, I would argue that gradient direction accuracy offers only marginal benefits to the training process once it passes a certain threshold.
The Marketplace algorithm essentially trades gradient direction accuracy for training speed and other advantages.&lt;/p&gt;

&lt;p&gt;While the forward pass in the Marketplace algorithm is more computationally intensive, it is designed to be GPU-efficient.
With a powerful GPU and highly optimized code, it should perform at least as fast as the forward pass in backprop.
We also need to compute the reconciled delta for the Marketplace algorithm, but this is only a small fraction of the forward pass and can be easily parallelized.&lt;/p&gt;

&lt;p&gt;The potential of the Marketplace algorithm is immense—running at least twice as fast as backprop is just the tip of the iceberg.
Many may not realize that GPUs are only part of the equation in large-scale training.
Bandwidth is often the bottleneck.
The primary reason large-scale distributed training isn’t conducted on idle consumer-grade GPUs is due to bandwidth limitations.
When using GPU clusters for training, network bandwidth is a critical factor.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/h100-superpod.png&quot; alt=&quot;A figure from How to Think About GPUs by Google DeepMind showing the NVIDIA H100 SuperPod architecture, a large-scale GPU cluster. It highlights the tremendously high network bandwidth between nodes, which is critical for large-scale training and expensive to implement.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A figure from &lt;a href=&quot;https://jax-ml.github.io/scaling-book/gpus/#beyond-the-node-level&quot;&gt;How to Think About GPUs by Google DeepMind&lt;/a&gt; showing the NVIDIA H100 SuperPod architecture, a large-scale GPU cluster. It highlights the tremendously high network bandwidth between nodes, which is critical for large-scale training and expensive to implement.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;With the Marketplace algorithm, the bandwidth required for syncing weights is significantly reduced.
Using our &lt;a href=&quot;https://github.com/LaunchPlatform/marketplace/pull/1&quot;&gt;seed-based random number generation&lt;/a&gt;, we only need to redistribute the seeds plus the attribution value \(A_i\) for each path to reconstruct weight updates.
This reduction in bandwidth requirements means less time spent syncing weight updates across nodes, which could revolutionize large-scale training.
Training time could be significantly reduced. Sorry,
Jensen, for the potential impact on &lt;a href=&quot;https://www.nvidia.com/en-us/data-center/nvlink/&quot;&gt;NVLink&lt;/a&gt; sales! 😅&lt;/p&gt;

&lt;p&gt;Due to the reduced bandwidth requirements, I believe this opens up a new world of distributed training.
It may become feasible to conduct large-scale training on idle consumer-grade GPUs.&lt;/p&gt;

&lt;h2 id=&quot;future-research-directions&quot;&gt;Future Research Directions&lt;/h2&gt;

&lt;p&gt;The Marketplace V2 algorithm performs comparably to backpropagation, but several questions remain unanswered.&lt;/p&gt;

&lt;h3 id=&quot;is-reconciled-delta-an-accurate-approximation-of-the-gradient-direction-in-backpropagation&quot;&gt;Is Reconciled Delta an Accurate Approximation of the Gradient Direction in Backpropagation?&lt;/h3&gt;

&lt;p&gt;As noted throughout this article, the reconciled delta performs nearly identically to the gradient direction in backpropagation with stochastic gradient descent (SGD) as the optimizer.
This raises the question: Is the reconciled delta an accurate approximation of the gradient direction in backpropagation?
To answer this, one could run backpropagation alongside the Marketplace algorithm, compare the gradient direction with the reconciled delta, and evaluate their similarity.
Another intriguing question is whether scaling up the Marketplace algorithm could improve the approximation of the gradient direction.
Due to time constraints, I leave this exploration to others interested in this topic.&lt;/p&gt;

&lt;h3 id=&quot;can-we-apply-optimization-techniques-to-the-marketplace-algorithm&quot;&gt;Can We Apply Optimization Techniques to the Marketplace Algorithm?&lt;/h3&gt;

&lt;p&gt;Assuming the reconciled delta accurately approximates the gradient direction in backpropagation, can we apply optimization techniques such as momentum, Adam, or others to the Marketplace algorithm?
If these techniques can be integrated, they could potentially enhance performance with minimal effort.&lt;/p&gt;

&lt;h3 id=&quot;scalability-of-the-v2-algorithm&quot;&gt;Scalability of the V2 Algorithm&lt;/h3&gt;

&lt;p&gt;In my earlier research, I explored the scalability of the Marketplace V1 algorithm, which is relatively straightforward to scale along two main axes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;The dataset axis&lt;/strong&gt;: Parallelizing the forward pass.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The marketplace replication axis&lt;/strong&gt;: Running multiple marketplace replicas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using seed-based random number generation, the Marketplace V1 algorithm can be replicated across multiple nodes and executed in parallel.
Due to limited access to GPUs, I simulated this by running multiple forward passes and marketplace replicas sequentially.
Increasing the number of forward passes and replicas showed performance improvements on a limited scale, but its effectiveness at a larger scale remains unclear.
The V2 algorithm’s scaling behavior differs, so I defer further investigation to future work or others interested in this topic.&lt;/p&gt;

&lt;h3 id=&quot;adaptive-learning-rate&quot;&gt;Adaptive Learning Rate&lt;/h3&gt;

&lt;p&gt;I explored the idea of using an adaptive learning rate with the Marketplace algorithm.
Since the algorithm allows testing slight parameter changes, it could also test different learning rates.
The approach involves using the first forward pass to determine the reconciled delta direction and the second forward pass to identify the optimal learning rate, essentially determining the direction in the first pass and the step size in the second.&lt;/p&gt;

&lt;p&gt;Using this second pass to test the learning rate is a promising idea.
I implemented this approach, and it significantly improved performance in the initial steps, even outperforming backpropagation with SGD as the optimizer at certain learning rates in the early steps.
However, performance degraded later due to fluctuating learning rates.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/meta-lr-accuracy.svg&quot; alt=&quot;Validation accuracy of the Marketplace V2 algorithm with adaptive learning rate compared to backpropagation with SGD.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;The validation accuracy of the Marketplace V2 algorithm with an adaptive learning rate outperforms many backpropagation configurations with SGD as the optimizer, using only 8 vendors per specification. However, performance degrades quickly due to fluctuating learning rates.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-09-02-marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/meta-lr-loss.svg&quot; alt=&quot;Loss of the Marketplace V2 algorithm with adaptive learning rate compared to backpropagation with SGD.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;The loss of the Marketplace V2 algorithm with an adaptive learning rate outperforms many backpropagation configurations with SGD as the optimizer, using only 8 vendors per specification. However, performance degrades quickly due to fluctuating learning rates.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The Marketplace V2 algorithm with an adaptive learning rate shown in the diagrams above uses only 8 vendors per specification, while the one we used in backprop comparsion uses 16 vendors, yet it still outperforms many backpropagation configurations with SGD in early steps.
This suggests a promising direction, but stabilizing the learning rate is necessary to prevent performance degradation.&lt;/p&gt;

&lt;h3 id=&quot;large-models-and-datasets&quot;&gt;Large Models and Datasets&lt;/h3&gt;

&lt;p&gt;As discussed in the previous article, I tested the Marketplace algorithm on a larger model like &lt;a href=&quot;https://github.com/tinygrad/tinygrad/blob/d0d39885c386d730da29f6a9cc1fdac589319b9e/extra/models/resnet.py&quot;&gt;ResNet-18&lt;/a&gt; with a scaled MNIST dataset, but these experiments were insufficient to assess scalability to larger models and datasets.
A common saying is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If it doesn’t work with MNIST, it probably won’t work with anything.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Having demonstrated that the Marketplace algorithm performs well with a small MNIST CNN, the next step is to test it with larger models and datasets.
Training larger models and datasets requires significant time and resources for efficient execution, so I defer this to future work or others interested in this topic.&lt;/p&gt;

&lt;h2 id=&quot;i-need-your-help-lets-advance-machine-learning-together&quot;&gt;I Need Your Help: Let’s Advance Machine Learning Together&lt;/h2&gt;

&lt;p&gt;Although I move extremely fast to write code and experiment with new ideas, I still only have 24 hours in a day and 7 days in a week.
As you can see, there are so many interesting things to explore with the Marketplace algorithm, and I can only do a small part of it.&lt;/p&gt;

&lt;p&gt;As a solo researcher, it’s also scary to think about whether I made mistakes during the process.
But I believe the best way to move forward quickly is to embrace mistakes and learn from them rapidly.
Maybe it’s a fool’s errand in the end, but I still want to try.
People used to believe that training with super-large models was not possible because they would overfit.
Until someone brave enough tried it and proved it was possible, which is why we see today’s machine learning blooming.
It’s funny that I see countless examples of scientists entering the field where their teachers, professors, or advisors told them everything was well researched and there was nothing new to be found.
But we know people have proved that wrong again and again.
So the question is not what if I make mistakes, the question is, if we don’t try, how can we know if we can make it?
And what if I am right?&lt;/p&gt;

&lt;p&gt;If you’re like me and dare to challenge the status quo, I invite you to join me.
I believe the Marketplace algorithm is just the beginning of a new era in machine learning.
All of my experiments are open-source under the MIT license and accessible to everyone.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/LaunchPlatform/marketplace&quot;&gt;https://github.com/LaunchPlatform/marketplace&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can contribute to the project, fork it, and conduct your own research.
You’re also very welcome to reproduce the experiments with your own implementation.
Please feel free to contact me if you have any questions or suggestions.
I am also open to exploring collaboration opportunities if there’s something interesting to pursue together.&lt;/p&gt;

&lt;h2 id=&quot;acknowledgments&quot;&gt;Acknowledgments&lt;/h2&gt;

&lt;p&gt;Many people have mentioned interesting prior works on X.
I think it’s helpful to list them here for others to learn from.
If you find any interesting prior works somewhat relative to my research, please feel free to let me know, I will add them to the list.&lt;/p&gt;

&lt;h3 id=&quot;spike-timing-dependent-plasticity&quot;&gt;Spike-Timing-Dependent Plasticity&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://x.com/mov_axbx&quot;&gt;Nathan Odle (@mov_axbx)&lt;/a&gt; on X &lt;a href=&quot;https://x.com/mov_axbx/status/1959551011187503248&quot;&gt;mentioned&lt;/a&gt; that my Marketplace algorithm somewhat maps to RL-STDP.
It appears that &lt;a href=&quot;https://en.wikipedia.org/wiki/Spike-timing-dependent_plasticity&quot;&gt;STDP (Spike-timing-dependent plasticity)&lt;/a&gt; is a biological process for adjusting the weights of synapses.
I haven’t had time to really dig into it, but it’s interesting to see that my Marketplace algorithm could potentially be similar to the biological processes by which the brain learns.
By the way, the author’s &lt;a href=&quot;https://www.mov-axbx.com/wopr/wopr_concept.html&quot;&gt;7 4090 AI training rack&lt;/a&gt; is so cool—be sure to check it out.&lt;/p&gt;

&lt;h3 id=&quot;random-feedback-weights-support-learning-in-deep-neural-networks&quot;&gt;Random Feedback Weights Support Learning in Deep Neural Networks&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://x.com/gradientjanitor&quot;&gt;andrew (@gradientjanitor)&lt;/a&gt; on X &lt;a href=&quot;https://x.com/gradientjanitor/status/1957960456754327841&quot;&gt;mentioned&lt;/a&gt; that my Marketplace algorithm reminds him of another paper: &lt;a href=&quot;https://arxiv.org/abs/1411.0247&quot;&gt;Random feedback weights support learning in deep neural networks&lt;/a&gt; by Lillicrap et al.
I glanced through the paper and think I understood maybe 50% of the ideas.
Regardless, it seems like an interesting paper showing that random feedback weights can support learning in deep neural networks.&lt;/p&gt;

&lt;h3 id=&quot;bucket-brigade-algorithm&quot;&gt;Bucket Brigade Algorithm&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://x.com/risi1979&quot;&gt;Sebastian Risi (@risi1979)&lt;/a&gt; on X &lt;a href=&quot;https://x.com/risi1979/status/1957810196522365423&quot;&gt;mentioned&lt;/a&gt; that it could be somewhat similar to the &lt;a href=&quot;https://gwern.net/doc/reinforcement-learning/multi-agent/1985-holland.pdf&quot;&gt;Bucket Brigade Algorithm&lt;/a&gt; by Holland.
If we view each vendor on the path to the final product as a chain of actions and the final loss as the reward, it could be somewhat similar to the Bucket Brigade Algorithm.
However, there’s no bid and tax in the Marketplace algorithm.&lt;/p&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;Personally, I feel so excited about the future of machine learning and the Marketplace algorithm’s applications.
I can’t wait to share one more thing before I end this article.
My next research direction is about “continual learning.”
In the real world, we learn new things by doing, it’s odd that machine learning requires a dedicated process to learn new things.
After I came up with the Marketplace algorithm, I’ve already had the idea of applying it to continual learning.
&lt;a href=&quot;https://x.com/SamiBelhareth/status/1957949413944619291&quot;&gt;Some people on X also realized&lt;/a&gt; the same potential of the Marketplace algorithm before I even announced it.&lt;/p&gt;

&lt;p&gt;By continual learning, I mean we can make a model learn new things through inference alone mostly.
Sounds exciting, right?
I have run the idea through in my mind, and I believe it’s possible.
The only thing left is to implement it and see if it really works as I expected.
Stay tuned for the upcoming articles.
I hope you enjoyed reading this article.&lt;/p&gt;
</description>
        <pubDate>Tue, 02 Sep 2025 07:00:00 +0000</pubDate>
        <link>https://fangpenlin.com/posts/2025/09/02/marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/</link>
        <guid isPermaLink="true">https://fangpenlin.com/posts/2025/09/02/marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/</guid>
      </item>
    
      <item>
        <title>Marketplace: my first attempt at training without backprop on GPU efficiently</title>
        <description>&lt;style type=&quot;text/css&quot;&gt;
  figure {
    text-align: center;
    margin: 0 auto;
  }
  figcaption, figcaption p {
    color: grey;
    font-size: 0.9em;
  }
  figure img {
    max-width: 100%;
  }
&lt;/style&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt;
Please read the &lt;a href=&quot;/posts/2025/09/02/marketplace-v2-is-all-you-need-a-training-algorithm-on-par-with-backprop/&quot;&gt;second article&lt;/a&gt; for the details of the V2 algorithm. Also the third article, &lt;a href=&quot;/posts/2025/09/09/continual-learning-with-marketplace-model-learns-new-data-with-mostly-inference/&quot;&gt;Continual Learning with Marketplace: Model Learns New Data with Mostly Inference&lt;/a&gt;, introduces the continual learning with the Marketplace algorithm.&lt;/p&gt;

&lt;p&gt;If you’ve read my previous articles, you know I’m a big fan of first-principles thinking.
I’ve mentioned many times that I want to eliminate backpropagation.
Many people think I’m crazy and assume I must be joking.
But no, I’m serious.
I thought about the problem from time to time.
Recently, I came up with an idea that could potentially work.
I spent two weeks implementing it and running experiments, and it worked!
While this is just a baby step, there are still many things to improve, but at least I think it’s an interesting idea that could be worth exploring and sharing.
Today, I would like to share my approach to training without backpropagation on GPUs efficiently.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/marketplace-accuracy.svg&quot; alt=&quot;A diagram shows the validation accuracy of a small MNIST CNN model training process without using backpropagation.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram shows the validation accuracy of a small MNIST CNN model training process without using backpropagation.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/marketplace-loss.svg&quot; alt=&quot;A diagram shows the validation loss of a small MNIST CNN model training process without using backpropagation.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram shows the loss of a small MNIST CNN model training process without using backpropagation.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;p&gt;Just because a solution exists and is widely used doesn’t mean it’s the best one.
From this perspective, we should challenge all existing solutions.
Having worked on numerous machine learning projects recently, I’ve found that backpropagation uses a ton of memory.
The dependencies introduced by backward propagation also make it challenging to scale effectively.
I want to train a model without backpropagation, ideally in a distributed manner.&lt;/p&gt;

&lt;p&gt;I am a very different type of person, I guess.
I really don’t like seeing the answer before coming up with my own ideas.
This is why I didn’t study any existing research papers or articles about how to train without backpropagation.
The interesting thing about research is that one can usually only do so much with existing technologies and methodologies.
For example, the concept of the &lt;a href=&quot;https://en.wikipedia.org/wiki/Convolutional_neural_network&quot;&gt;Convolutional Neural Network (CNN)&lt;/a&gt; is not new.
It was proposed by a Japanese researcher named Fukushima as early as 1979!
I saw a &lt;a href=&quot;https://x.com/SchmidhuberAI/status/1952007922721919219&quot;&gt;post about it on X&lt;/a&gt; a few days ago:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/cnn-history.png&quot; alt=&quot;CNN history from a X post mentioning that the idea of CNN was introduced long ago by a Japanese researcher, but it only implemented and grains popularity later due to advancements in hardware and training techniques.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;CNN history from a &lt;a href=&quot;https://x.com/SchmidhuberAI/status/1952007922721919219&quot;&gt;X post&lt;/a&gt; mentioning that the idea of CNN was introduced long ago by a Japanese researcher, but it only implemented and grains popularity later due to advancements in hardware and training techniques.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;At the time the concept was introduced, it would have likely cost the researcher an insane amount of money and resources just to test the idea.
The CNN was only proven useful by LeCun et al when backpropagation was introduced and computer resources became more accessible years later.&lt;/p&gt;

&lt;p&gt;It’s remarkable to think about: most of our modern-day machine learning methodologies originated decades ago.
But things change so fast.
While I was studying CUDA, I watched a &lt;a href=&quot;https://www.youtube.com/watch?v=n6M8R8-PlnE&amp;amp;t=256s&quot;&gt;video entioning that the NVIDIA A100 chip’s computing power surpasses that of a supercomputer cluster&lt;/a&gt;, which once took up an entire room, while using only a fraction of the power.
It’s insane to think about—I have a 4090 right inside my work PC.
It’s a supercomputer in this tiny box! 🤯&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/cuda-video.png&quot; alt=&quot;A YouTube video introducing CUDA and how memory access patterns significantly impact CUDA program performance. The video also mentioned that modern GPUs rival the capabilities of supercomputers from a few years ago.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A &lt;a href=&quot;https://youtu.be/n6M8R8-PlnE?si=1e9KUpEgRZ0pUIWo&amp;amp;t=256&quot;&gt;YouTube video&lt;/a&gt; introducing CUDA and how memory access patterns significantly impact CUDA program performance. The video also mentioned that modern GPUs rival the capabilities of supercomputers from a few years ago.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As you can see, given the time and context, it’s hard to imagine how fast future computer chips could become.
Even if a researcher could predict what might work, it was prohibitively expensive to test.
Most papers are products of the technologies available at their time.
That’is why it’s always a good idea to devise solutions from the ground up using today’s technologies.&lt;/p&gt;

&lt;p&gt;When I was working on &lt;a href=&quot;/posts/2025/02/06/maze-how-i-would-build-agi/&quot;&gt;my MAZE project&lt;/a&gt;, I thought a lot about how it could be more efficient.
The thing is, no matter how interesting your idea is, if it can’t run efficiently with the available resources right now, it’s just science fiction.
Sure, I could come up with a system to evolve different models a zillion times to see which one works best, but the problem is that you don’t have the resources to run it a zillion times like Mother Nature does.
I saw &lt;a href=&quot;https://www.youtube.com/watch?v=OkEGJ5G3foU&quot;&gt;a presentation&lt;/a&gt; by &lt;a href=&quot;https://x.com/danielhanchen&quot;&gt;Daniel Han&lt;/a&gt; from &lt;a href=&quot;https://unsloth.ai&quot;&gt;Unsloth AI&lt;/a&gt;, where he mentioned:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Machine learning is all about efficiency&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think that’s true.
To make machine learning work, efficiency is key.
Great presentation, by the way. If you’re interested in Large Language Models (LLMs), I highly recommend it.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/daniel-han-presentation.png&quot; alt=&quot;A screenshot of a presentation by Daniel Han from Unsloth AI, where he mentioned that machine learning is all about efficiency.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A screenshot of &lt;a href=&quot;https://www.youtube.com/watch?v=OkEGJ5G3foU&quot;&gt;a presentation&lt;/a&gt; by &lt;a href=&quot;https://x.com/danielhanchen&quot;&gt;Daniel Han&lt;/a&gt; from &lt;a href=&quot;https://unsloth.ai&quot;&gt;Unsloth AI&lt;/a&gt;, where he mentioned that machine learning is all about efficiency.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Take machine learning model mutation, for example.
Currently, MAZE can only mutate model structure.
But in the real world, I believe model weights could also be encoded in the DNA.
Therefore, I’ve been thinking hard about how to mutate not just the structure but also inherit weights from parent models.
I realized something really inefficient in my current approach: mutations may only occur at some point in the model structure.
For the layers before that mutation, we’ve already fed testing/training data to them previously.
So, you’re duplicating work by repeatedly feeding the same data to the same shared layers.
One straightforward idea is to cache the values and reuse them, but this may still require a lot of storage space.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/repeating-work.svg&quot; alt=&quot;A diagram showcasing how mutation only affects downstream layers. With the same input data, we are repeating the same work for the same model.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showcasing how mutation only affects downstream layers. With the same input data, we are repeating the same work for the same model.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Since I was also thinking about how to eliminate backpropagation, I wondered if I could apply a similar idea to training.&lt;/p&gt;

&lt;h2 id=&quot;idea-marketplace&quot;&gt;Idea: Marketplace&lt;/h2&gt;

&lt;p&gt;Other than Mother Nature, I also like to think about all kinds of efficient organic mechanisms and how they work.
A free market is another great example.
No matter how the environment changes, a free market can adapt quickly and produce in-demand goods efficiently.
I view an entire neural network as a marketplace.
Each time data flows into a model, it’s like raw materials acquired by vendors, who then produce products.
If you break the model down to a smaller scale, each layer is like a stop in the manufacturing process.
When you further break down the layer, each neuron can be seen as a vendor that takes in materials, processes them, and outputs a product to the downstream layers.
With this in mind, I’ve been thinking about how to make a neural network model mimic how a free market works.
Ideally, each vendor should evolve autonomously and compete to produce the best products.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/neuron-network-vs-vendor.svg&quot; alt=&quot;Comparing a neuron network to a vendor in a marketplace. They both take in materials, process them, and output a product.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Comparing a neuron network to a vendor in a marketplace. They both take in materials, process them, and output a product.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Sure, it’s easier said than done.
As mentioned previously, machine learning is all about efficiency.
I needed to think hard about how to make it GPU-friendly.
I tried a few different approaches and finally found something that works efficiently on a GPU.
Because the idea is inspired by how a free market works, I call it the “Marketplace.”
Here’s how it works.&lt;/p&gt;

&lt;p&gt;In a neural network, there are usually many layers.
Take a Convolutional Neural Network (CNN), for example: one image could flow through multiple layers of kernels to capture features at different hierarchical levels.
The further down the layers, the more abstract the concepts it captures.
For each layer, the output is a tensor with the same shape, regardless of the input data.
Therefore, I call this a spec.
You can think of it as a specification for a product.
In each layer, or a group of layers, for a specific set of weight values, I call it a vendor.
Think about it: vendors take input, transform it deterministically based on their weights, and output the result as their product.
Indeed, they are vendors.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/product-spec.svg&quot; alt=&quot;A diagram showcasing that you can think of a layer in a neural network as a specification for a product. Because they all take input in the same shape and output in the same shape.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showcasing that you can think of a layer in a neural network as a specification for a product. Because they all take input in the same shape and output in the same shape.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The idea is to have many vendors in a layer compete to produce the best product for the downstream layers.
But here’s the problem: what does the “best” product mean?
Before the final end-user evaluates it, nobody knows whether these intermediate products are good or bad.
It’s hard to determine how well a product performs if we adjust it slightly.
The only way to know is to let the downstream vendors consume it, produce their own products, and eventually have those consumed by the end customers, who provide their feedback.&lt;/p&gt;

&lt;p&gt;But then another problem arises. When a vendor in a hidden layer changes their formula slightly, the altered output impacts the products downstream.
It’s hard to predict whether a slight increase in the “sweetness” of your sugar supply might ruin the final boba tea product for the end consumer.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/adjusting-one-vendor.svg&quot; alt=&quot;A diagram showcasing how a slight change in a vendor&apos;s formula impacts the downstream products. And its very hard to predict the impact to the final product.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showcasing how a slight change in a vendor&apos;s formula impacts the downstream products. And its very hard to predict the impact to the final product.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;What do you do then?
Well, in the real world, vendors usually try things out.
You don’t stick with the same vendor forever.
An upstream vendor may go out of business, get acquired, or change their quality.
You still need to find a way to adapt to keep your business running.
Therefore, when things change, you need to try them out to know.
I call this process upstream sampling.
The idea is that each vendor randomly selects N intermediate products from the upstream layer and then produces N corresponding products for the downstream layers.
They can also take all the intermediate products from the upstream layer, I call this a full upstream sampling.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/upstream-sampling.svg&quot; alt=&quot;A diagram showcasing how upstream sampling works. Each vendor randomly selects N intermediate products from the upstream layer and then produces N corresponding products for the downstream layers.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram showcasing how upstream sampling works. Each vendor randomly selects N intermediate products from the upstream layer and then produces N corresponding products for the downstream layers.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/layer-structure.svg&quot; alt=&quot;A diagram shows that each spec (group of layers) has many vendors (weights variants), and each vendor runs upstream sampling to produce products based on the upstream products provided by different upstream vendors for the downstream layers.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram shows that each spec (group of layers) has many vendors (weights variants), and each vendor runs upstream sampling to produce multiple products based on the upstream products provided by different upstream vendors for the downstream layers.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We repeat the same process for all layers until the end. Then, we evaluate the final product to determine its quality.
To track which vendor and its upstream vendors are responsible for the final product, we concatenate the index of each vendor in their respective layer as a record.
After evaluating the final product, we identify the best vendor based on metrics such as loss or accuracy.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/select-best-vendors.svg&quot; alt=&quot;A diagram shows that after evaluating the final product, we identify the best vendor based on metrics such as loss or accuracy. We then copy the weights from the leading vendor to all other vendors in the same layer, introducing slight random variations.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram shows that after evaluating the final product, we identify the best vendors based on metrics such as loss or accuracy.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We then copy the weights from the leading vendor to all other vendors in the same spec, introducing slight random variations.
You can think of this is the other copycat vendors copying the leading vendors with their own mutations.
Well, it happens in the real world too.
When a product is is very successful, there will be copycat products.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/mutate.svg&quot; alt=&quot;A diagram shows that we copy the weights from the leading vendor to all other vendors in the same layer, introducing slight random variations.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A diagram shows that we copy the weights from the leading vendor to all other vendors in the same layer, introducing slight random variations △µ(i,j) corresponding to the index of the vendor.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;By repeating this process, the model learns to produce high-quality products efficiently over time.&lt;/p&gt;

&lt;p&gt;The core idea of this algorithm lies in its efficiency, as it reuses intermediate products multiple times.
If we mutated the entire model’s weights simultaneously, it would be difficult to determine which changes were beneficial or detrimental.
However, by mutating each layer or a few layers at a time individually and then fully or partially randomly remixing it with different vendor combinations, the algorithm efficiently reuses the output of each mutation.
More importantly, this entire process is intentionally designed to be GPU-efficient, meaning it can run in parallel.
No matter how fast your GPU runs, the next layer will always be waiting for the previous layer to finish its work.
Therefore, it makes perfect sense to run as many combinations as possible in parallel in each layer.
The mutation processes for each vendor within the same layer do not depend on each other.
In theory, this makes the algorithm scalable and can potentially be distributed across multiple nodes in a large-scale training cluster efficiently.&lt;/p&gt;

&lt;h2 id=&quot;implementing-with-tinygrad&quot;&gt;Implementing with Tinygrad&lt;/h2&gt;

&lt;p&gt;If you read my previous article about &lt;a href=&quot;/posts/2025/06/11/two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/&quot;&gt;building my training workstation with two AMD GPUs&lt;/a&gt;, you know I rewrote much of my PyTorch code using Tinygrad for the CakeLens project.
If you’re unfamiliar with &lt;a href=&quot;https://github.com/tinygrad/tinygrad&quot;&gt;Tinygrad&lt;/a&gt;, it’s a machine learning library created by one of my favorite legendary hackers, &lt;a href=&quot;https://en.wikipedia.org/wiki/George_Hotz&quot;&gt;George Hotz&lt;/a&gt;.
Unlike PyTorch, Tinygrad uses lazy evaluation, meaning the compute graph is constructed as a complete end-to-end graph when you call the functions.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/tinygrad-viz.png&quot; alt=&quot;Screenshot of the Tinygrad&apos;s compute graph visualization tool. It shows the compute graph and the corresponding native kernel code lowered from the graph.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;Screenshot of the Tinygrad&apos;s compute graph visualization tool. It shows the compute graph and the corresponding native kernel code lowered from the graph.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This allows you to apply its &lt;a href=&quot;https://docs.tinygrad.org/quickstart/?h=jit#jit&quot;&gt;JIT compiler&lt;/a&gt; to compile the entire compute graph into native kernel code for hardware accelerators.
With the full compute graph, there are a lot of opportunities to optimize it by fusing the operations.
For example, here’s how you put the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@TinyJIT&lt;/code&gt; decorator on a function to compile the entire compute graph into native kernel code:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TinyJit&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mutate_step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;combined_loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;combined_paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;min_loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min_loss_index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;combined_loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;topk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;largest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;min_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;combined_paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;min_loss_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;mutate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;marketplace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;marketplace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;leading_path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;min_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;jitter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min_loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;realize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;realize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I also appreciate that the library is very small, making it much easier to understand and extend.
I enjoy using Tinygrad for machine learning and other high-performance computing tasks across platforms.
This research will be way harder if I had to use PyTorch or other machine learning frameworks, as they assume training with backpropagation.
You may need to build your own CUDA kernel or other hardware-specific code to run on GPUs if you want to speed up the training process.&lt;/p&gt;

&lt;p&gt;Back to the topic: yes, I implemented the Marketplace algorithm using Tinygrad.
As like most of my machine learning projects, I’ve open-sourced it here:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/LaunchPlatform/marketplace&quot;&gt;https://github.com/LaunchPlatform/marketplace&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should be able to find all the experiment code in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;experiments&lt;/code&gt; folder.
Please pardon me, as some parts of the code might be slightly messier than my usual standards, since this is an experimental project, not a production one.
I used the &lt;a href=&quot;https://github.com/tinygrad/tinygrad/blob/21570545d376d4fc0176338c9f2d08ca3f3a1b63/examples/beautiful_mnist.py&quot;&gt;beautiful_mnist&lt;/a&gt; example from Tinygrad’s examples folder as the target model to test my algorithm.
The implementation is simple: we break down each CNN layer into a multi-model class, like this:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# layer0
&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MultiModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiConv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;structure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# layer1
&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MultiModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiConv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;structure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;upstream_sampling&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;structure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# layer2
&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MultiModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiBatchNorm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;structure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_pool2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;upstream_sampling&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;structure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# layer3
&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MultiModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiConv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;structure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;upstream_sampling&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;structure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# layer4
&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MultiModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiConv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;structure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;upstream_sampling&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;structure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# layer5
&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MultiModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiBatchNorm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;structure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_pool2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;upstream_sampling&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;structure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Layer6
&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MultiModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MultiLinear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;structure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;576&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;upstream_sampling&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;structure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In other words, we duplicate the same weights N times to test different vendor variants.
Here’s an example implementation of the multi-model version of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Conv2D&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MultiConv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MultiModelBase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Conv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;in_channels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;out_channels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;kernel_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dilation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bias&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;in_channels&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;in_channels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;out_channels&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;out_channels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;kernel_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kernel_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;dilation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dilation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bias&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repeat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bias&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bias&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repeat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We also modified the forward pass method to accept an extra index parameter, allowing switching between different weights from various vendors.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__call__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bias&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dilation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For the first layer, we feed training input data to different vendors (weights), producing an array of intermediate products.
The next layer randomly selects N intermediate products from the upstream layer, tracking the vendor indices to form a tensor representing the product’s supply chain path.
If it’s a full sampling, we sample all vendors outputs from the upstream layer.
Here’s the simplified code for producing output for a layer:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;produce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MultiModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;upstream_sampling&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;paths&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# first layer, no upstream sampling, just feed input to all vendors
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;output_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dim&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;paths&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unsqueeze&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;paths&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;upstream_sampling&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# sample all vendors from the upstream layer
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;upstream_sampling&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;input_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;input_indexes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randperm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;upstream_sampling&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dim&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;input_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_indexes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# merge different batches for the same vendor into one. not sure if this is needed, but at least it saves us
&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# from calling the model multiple times and making the graph more complex
&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;merged_batches&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reshape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:])&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;output_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;merged&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;merged&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;zip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;merged_batches&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dim&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# breaking down merged batches back to individual batches
&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;output_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reshape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:])&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;prev_paths&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_indexes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;new_paths&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unsqueeze&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repeat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;upstream_sampling&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unsqueeze&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;merged_paths&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prev_paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dim&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;merged_paths&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After processing all layers, we obtain the final products, i.e the output logits.
We apply the loss function to identify the product with the lowest loss, then copy the weights from the leading vendors to the others, adding slight random mutations to explore further improvements.
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mutate&lt;/code&gt; function is responsible for copying the weights from the leading vendors to the others, adding slight random mutations to explore further improvements.
The code looks like this:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mutate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;marketplace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;leading_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jitter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;leading_index&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;zip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;marketplace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;leading_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;multi_params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_state_dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;multi_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;leading_params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;leading_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;leading_params&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;leading_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# Do not change the leading vendor
&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# Copy from the leading vendor and add a bit jitters
&lt;/span&gt;            &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
              &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uniform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                  &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;leading_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;low&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jitter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;high&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jitter&lt;/span&gt;
              &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;realize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s it.
The implementation is surprisingly simple.&lt;/p&gt;

&lt;h2 id=&quot;learning-rate-is-critical&quot;&gt;Learning Rate Is Critical&lt;/h2&gt;

&lt;p&gt;When experimenting with the Marketplace training algorithm, I realized that the learning rate plays a key role in successfully training a model, just as it does in backpropagation.
I tested various learning rates to determine which one yields the best training performance.
In addition to the learning rate, the learning rate schedule is also crucial.
I need to reduce the learning rate over time to make the random adjustments smaller, allowing the model to fine-tune the final few decimal points of accuracy.
Here’s a diagram showing the significant differences in performance based on various learning rates and decay schedules:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/lr-accuracy.svg&quot; alt=&quot;A chart with shows the performance of different learning rates and decay schedules. The best learning rate is 1e-3 with a 1e-04 decay schedule.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A chart with shows the accuracy of different learning rates and decay schedules. The best learning rate is 1e-3 with a 1e-04 decay schedule.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/lr-loss.svg&quot; alt=&quot;A chart with shows the performance of different learning rates and decay schedules. The best learning rate is 1e-3 with a 1e-04 decay schedule.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A chart with shows the loss of different learning rates and decay schedules. The best learning rate is 1e-3 with a 1e-04 decay schedule.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/lr-lr.svg&quot; alt=&quot;A chart with shows the learning rate value of different learning rates and decay schedules.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A chart with shows the learning rate value of different learning rates and decay schedules.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;For now, I rely on a trial-and-error approach to find the optimal learning rate.&lt;/p&gt;

&lt;h2 id=&quot;batch-normalization-doesnt-work-well-with-marketplace&quot;&gt;Batch Normalization Doesn’t Work Well with Marketplace&lt;/h2&gt;

&lt;p&gt;Interestingly, during training, I noticed that the accuracy of the testing dataset was fluctuating.
I investigated the issue and identified the cause as the &lt;a href=&quot;https://pytorch.org/docs/stable/generated/torch.nn.BatchNorm2d.html&quot;&gt;Batch Normalization layer&lt;/a&gt;.
Since we are sampling various intermediate products, these products significantly affect the mean and variance of the Batch Normalization layer.
However, most of these products are eventually discarded, rendering them as noise.
During inference, Batch Normalization uses the running mean and variance, which are influenced by these noisy, discarded intermediate products.
As a result, the accuracy fluctuates.
To address this issue while retaining the benefits of normalization, I replaced the Batch Normalization layer with an &lt;a href=&quot;https://pytorch.org/docs/stable/generated/torch.nn.InstanceNorm2d.html&quot;&gt;Instance Normalization layer&lt;/a&gt;.
This change resolved the problem effectively!&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/batch-norm-vs-instance-norm-accuracy.svg&quot; alt=&quot;A chart shows the validation accuracy fluctuation for the Batch Normalization but not for the Instance Normalization.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A chart shows the validation accuracy fluctuation for the Batch Normalization but not for the Instance Normalization.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;experiments-as-code&quot;&gt;Experiments as code&lt;/h2&gt;

&lt;p&gt;I used to collect metrics with &lt;a href=&quot;https://www.tensorflow.org/tensorboard&quot;&gt;TensorBoard&lt;/a&gt; for my machine learning experiments.
But my experiment runs accumulate quickly, and I get lost figuring out which experiment is which real fast.
I realized that I needed to organize my experiments better.
I tried &lt;a href=&quot;https://mlflow.org&quot;&gt;MLflow&lt;/a&gt;, and I absolutely love it.
I highly recommend it if you’re doing any machine learning experiments.
With MLflow, I can run experiments as code.
Instead of manually running the training script with different parameters, I can write a script to run the training with different parameters.
Here’s an example for the learning rate experiment:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;exp_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ensure_experiment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Learning Rate V2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;512&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lr&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1e-2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1e-3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1e-4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decay&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1e-3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1e-4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1e-5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mlflow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;run_name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;bs-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-lr-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lr&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-decay-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decay&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;experiment_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exp_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log_system_metrics&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;marketplace&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;make_marketplace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PYRAMID32_HALF_UPSTREAM_STRUCTURE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;train&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;step_count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3_000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;initial_lr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;lr_decay_rate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;marketplace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;marketplace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since MLflow logs which commit you are currently on, you can easily reproduce the experiment by checking out the commit and running the script again.
It’s a good practice to always commit your code before running an experiment so that the commit hash is always associated with the experiment.&lt;/p&gt;

&lt;p&gt;Here’s a screenshot of the MLflow dashboard:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/mlflow-dashboard.png&quot; alt=&quot;A screenshot shows the MLflow dashboard.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A screenshot shows the MLflow dashboard.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;And with its parallel coordinates plot, you can easily see the relationship between the parameters and the performance.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/mlflow-parallel-coordinates-plot.png&quot; alt=&quot;A screenshot shows the MLflow parallel coordinates plot.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A screenshot shows the MLflow parallel coordinates plot.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The lesson I learned from this is that I should have been using MLflow all along.
With the right tool, life is much easier.&lt;/p&gt;

&lt;h2 id=&quot;optimal-marketplace-structure&quot;&gt;Optimal Marketplace Structure&lt;/h2&gt;

&lt;p&gt;I didn’t pay much attention to the marketplace structure initially.
However, I soon realized that the structure is critical.
The beauty of the marketplace lies in its efficiency, as it reuses the same output multiple times.
However, if the structure is poorly designed, GPU resources can be wasted on computing intermediate products that are never used.
For example, with a low upstream sampling number, intermediate products in the upper layers have a lower chance of being selected downstream.
Running multiple forward passes could address this, but it’s not efficient.&lt;/p&gt;

&lt;p&gt;With full upstream sampling, all outputs from the previous layer are used.
Ideally, this is what you want, as all computational results are utilized downstream.
However, this approach causes the number of intermediate and final products to grow exponentially with the number of layers.
Therefore, when designing the marketplace structure, we must carefully consider the number of layers.&lt;/p&gt;

&lt;p&gt;In the MNIST CNN network, I initially assigned each Conv2D layer—and nearly every other layer—to its own specification.
As a result, the marketplace had too many layers, forcing me to use an upstream sampling value to reduce the number of intermediate products.
When comparing this approach to mutating all weights at once, the performance was not as poor as I expected, even with full weight mutation.
This is a relatively small model, with only about 80,000 parameters.
Given the limited exploration space, I concluded that having many layers in the marketplace was unnecessary.
I eventually reduced the depth of the marketplace to three with full upstream sampling.&lt;/p&gt;

&lt;h2 id=&quot;comparison-of-mutating-all-weights-at-once-vs-the-marketplace-approach&quot;&gt;Comparison of Mutating All Weights at Once vs. the Marketplace Approach&lt;/h2&gt;

&lt;p&gt;A much simpler approach compared to the Marketplace approach is to mutate all weights simultaneously (the all-at-once approach).
We can mathematically prove that the Marketplace approach is more efficient and quantify the gain from the all-at-once approach.&lt;/p&gt;

&lt;p&gt;Consider a model with \(N\) specifications, referred to as &lt;em&gt;market depth&lt;/em&gt;. Each specification has \(M\) vendors, representing different weight configurations.
Let \(S_i\) denote the computation cost of the forward pass for specification \(i\) (where \(i = 0, 1, \dots, N-1\)).&lt;/p&gt;

&lt;h3 id=&quot;all-at-once-approach&quot;&gt;All-at-Once Approach&lt;/h3&gt;
&lt;p&gt;In the all-at-once approach, we evaluate \(M\) different weight configurations across all \(N\) specifications.
The total computation cost \(C_A\) is:&lt;/p&gt;

\[C_A = M \times \sum_{i=0}^{N-1} S_i\]

&lt;p&gt;This yields \(M\) unique final products.
The unit cost per product, \(U_A\), is:&lt;/p&gt;

\[U_A = \frac{C_A}{M} = \sum_{i=0}^{N-1} S_i\]

&lt;h3 id=&quot;marketplace-approach&quot;&gt;Marketplace Approach&lt;/h3&gt;

&lt;p&gt;In the Marketplace approach, we use &lt;em&gt;full upstream sampling&lt;/em&gt;, processing intermediate outputs sequentially across specifications.
For the first specification, we feed the input to all \(M\) vendors, with a cost of:&lt;/p&gt;

\[C_{S_0} = M \times S_0\]

&lt;p&gt;This produces \(M\) intermediate outputs.
For the second specification, each of these \(M\) outputs is processed by all \(M\) vendors, yielding a cost of:&lt;/p&gt;

\[C_{S_1} = M^2 \times S_1\]

&lt;p&gt;For the third specification, the cost is:&lt;/p&gt;

\[C_{S_2} = M^3 \times S_2\]

&lt;p&gt;And so on. The total computation cost \(C_M\) is:&lt;/p&gt;

\[C_M = \sum_{i=0}^{N-1} M^{i+1} \times S_i\]

&lt;p&gt;This produces \(M^N\) distinct final products.
The unit cost per product, \(U_M\), is:&lt;/p&gt;

\[U_M = \frac{C_M}{M^N} = \sum_{i=0}^{N-1} \frac{M^{i+1} S_i}{M^N} = \sum_{i=0}^{N-1} \frac{S_i}{M^{N-i-1}}\]

&lt;h3 id=&quot;efficiency-comparison&quot;&gt;Efficiency Comparison&lt;/h3&gt;

&lt;p&gt;The unit cost \(U_M\) is significantly lower than \(U_A\), especially for large \(M\) or \(N\). To quantify this, assume \(S_i = S\) (constant cost per specification). For the all-at-once approach:&lt;/p&gt;

\[C_A = M \times N \times S, \quad U_A = N \times S\]

&lt;p&gt;For the Marketplace approach:&lt;/p&gt;

\[C_M = S \times \sum_{i=0}^{N-1} M^{i+1} = S \times M \times \frac{M^N - 1}{M - 1}\]

\[U_M = \frac{C_M}{M^N} = S \times \frac{M}{M - 1} \times \frac{M^N - 1}{M^N}\]

&lt;p&gt;For large \(M\), \(\frac{M^N - 1}{M^N} \approx 1\) and \(\frac{M}{M - 1} \approx 1\), so:&lt;/p&gt;

\[U_M \approx S\]

&lt;p&gt;The efficiency ratio is:&lt;/p&gt;

\[\frac{U_M}{U_A} \approx \frac{S}{N \times S} = \frac{1}{N}\]

&lt;p&gt;Thus, the Marketplace approach is approximately \(N\) times more efficient when \(M\) is large and \(S_i = S\). For non-constant \(S_i\), the efficiency depends on the distribution of \(S_i\), but the scaling factors \(M^{N-i-1}\) ensure \(U_M \ll U_A\) for \(M &amp;gt; 1\).&lt;/p&gt;

&lt;p&gt;Ideally, a larger \(N\) increases efficiency, as the unit cost scales inversely with \(N\).
However, the exponential growth in product count (\(M^N\)) limits scalability in practice. The vendor count \(M\) has a some impact but diminishes beyond a threshold.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/3d-unit-cost-comparison.png&quot; alt=&quot;A 3D chart shows the relationship between N, M, and the per unit cost comparison between the Marketplace and the all-at-once approach. The x-axis is the number of layers, the y-axis is the number of vendors per layer, and the z-axis is the per unit cost comparison.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A 3D chart shows the relationship between N, M, and the per unit cost comparison between the Marketplace and the all-at-once approach. The x-axis is the depth of the marketplace, the y-axis is the number of vendors per specification, and the z-axis is the per unit cost comparison.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In the context of GPUs, compute resources are relatively inexpensive.
With many cores running in parallel, a significant portion often remains underutilized.
However, memory is a scarcer resource, as model weights consume substantial memory.
We have established that the computational cost of the Marketplace approach is significantly lower than that of the all-at-once approach.
But what about memory costs?
The memory unit cost is similar, as the all-at-once approach requires \(N\) times the memory of the Marketplace approach.&lt;/p&gt;

&lt;h3 id=&quot;benchmarking-the-marketplace-approach&quot;&gt;Benchmarking the Marketplace Approach&lt;/h3&gt;

&lt;p&gt;With the mathematical proof, we now know that the Marketplace approach is more efficient than the all-at-once approach. 
With lower computational and memory costs, the same computing resources can be used to explore a larger weight space. 
Another benefit is that the Marketplace approach allows partial mutation of the weights. 
Since the previous leading vendor continues to participate in the next round, permutations of the weights can be made by changing only the downstream weights from the previous leading vendor. 
This helps stabilize the training process in the later stages of training. 
Given these theoretical benefits, let’s benchmark the Marketplace approach against the all-at-once approach.&lt;/p&gt;

&lt;p&gt;The all-at-once approach is, in fact, a special case of the Marketplace approach, where the market depth is 1. 
Here is the specification for the all-at-once approach:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MultiModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiConv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiConv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiInstanceNorm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_pool2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiConv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiConv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiInstanceNorm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_pool2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiLinear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;576&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can imagine, given the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vendor_count&lt;/code&gt; value, we are essentially duplicating the same model &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vendor_count&lt;/code&gt; times. 
On the other hand, the Marketplace approach was designed with three specifications, each supporting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vendor_count&lt;/code&gt; vendors.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MultiModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiConv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiConv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiInstanceNorm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_pool2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MultiModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiConv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiConv2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MultiInstanceNorm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_pool2d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MultiModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MultiLinear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;576&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]),&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s try a few different configurations, and see how the training performance changes.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Market depth: \(1\), vendor count: \(8\), final product count: \(8\)&lt;/li&gt;
  &lt;li&gt;Market depth: \(1\), vendor count: \(16\), final product count: \(16\)&lt;/li&gt;
  &lt;li&gt;Market depth: \(1\), vendor count: \(32\), final product count: \(32\)&lt;/li&gt;
  &lt;li&gt;Market depth: \(1\), vendor count: \(64\), final product count: \(64\)&lt;/li&gt;
  &lt;li&gt;Market depth: \(3\), vendor count: \(8\), final product count: \(8^3=512\)&lt;/li&gt;
  &lt;li&gt;Market depth: \(3\), vendor count: \(16\), final product count: \(16^3=4096\)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here’s the result:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/market-depth-accuracy.svg&quot; alt=&quot;A chart shows the accuracy of different market depth and vendor count configurations with 50% smoothing. The x-axis is the step count, and the y-axis is the accuracy. The best accuracy is achieved with a market depth of 3 and a vendor count of 16. Followed by market depth of 3 and vendor count of 8 then other all-at-once configurations.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A chart shows the accuracy of different market depth and vendor count configurations with 50% smoothing. The x-axis is the step count, and the y-axis is the accuracy. The best accuracy is achieved with a market depth of 3 and a vendor count of 16. Followed by market depth of 3 and vendor count of 8 then other all-at-once configurations.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/market-depth-loss.svg&quot; alt=&quot;A chart shows the loss of different market depth and vendor count configurations with 50% smoothing. The x-axis is the step count, and the y-axis is the loss at log scale. The best loss is achieved with a market depth of 3 and a vendor count of 16. Followed by market depth of 3 and vendor count of 8 then other all-at-once configurations.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A chart shows the loss of different market depth and vendor count configurations with 50% smoothing. The x-axis is the step count, and the y-axis is the loss at log scale. The best loss is achieved with a market depth of 3 and a vendor count of 16. Followed by market depth of 3 and vendor count of 8 then other all-at-once configurations.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As you can see, a market depth of 3 with a vendor count of 16 outperforms the all-at-once approach with a vendor count of 64. 
Even the market depth of 3 with a vendor count of 8 outperforms the all-at-once approach with a vendor count of 64!
This demonstrates that the Marketplace approach is more efficient than the all-at-once approach. 
It is capable of exploring a larger weight space with the same computational resources.&lt;/p&gt;

&lt;h2 id=&quot;comparison-with-backpropagation&quot;&gt;Comparison with Backpropagation&lt;/h2&gt;

&lt;p&gt;Some of you may ask, what about the performance of the Marketplace approach compared to backpropagation?
I am curious about the performance of the Marketplace approach compared to backpropagation as you are.
To make the comparsion, I took the &lt;a href=&quot;https://github.com/tinygrad/tinygrad/blob/d0d39885c386d730da29f6a9cc1fdac589319b9e/examples/beautiful_mnist.py&quot;&gt;beautiful_mnist&lt;/a&gt; example code from the Tinygrad repo.
As mentioned in the previous section, because the Batch Normalization layers don’t work well with the Marketplace approach, I replaced them with Instance Normalization.
And for the Marketplace approach, it’s a three level deep marketplace with 16 vendors per specification, batch size is 512 and initial learning rate is 1e-3 with a 1e-04 decay schedule.
Here’s the result:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/backprop-cmp-accuracy.svg&quot; alt=&quot;A chart shows the accuracy of the Marketplace approach and backpropagation. The x-axis is the step count, and the y-axis is the accuracy. The backpropagation reaches 98% accuracy in 70 steps, while the Marketplace approach reaches 90% accuracy in 500 steps and 95% accuracy in 1,000 steps.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A chart shows the accuracy of the Marketplace approach and backpropagation. The x-axis is the step count, and the y-axis is the accuracy. The backpropagation reaches 98% accuracy in 70 steps, while the Marketplace approach reaches 90% accuracy in 629 steps and 95% accuracy in 2.3K steps.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/backprop-cmp-loss.svg&quot; alt=&quot;A chart shows the loss of the Marketplace approach and backpropagation. The x-axis is the step count, and the y-axis is the loss at log scale. The backpropagation reaches 0.0001 loss in 70 steps, while the Marketplace approach reaches 0.001 loss in 500 steps and 0.005 loss in 1,000 steps.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A chart shows the loss of the Marketplace approach and backpropagation. The x-axis is the step count, and the y-axis is the loss at log scale.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Well, not surprisingly, backpropagation is way better in this case.
I didn’t expect my newborn training algorithm to beat a state-of-the-art method backed by decades of research.
As you can see from the chart, it takes only 70 steps to reach 98% accuracy, while the Marketplace approach takes 629 steps to reach 90% accuracy with the same batch size.
And it takes raound 2.3K steps to reach around 95% accuracy.
Then it takes longer time to increase the accuracy slowly over time.
With 3,000 steps, the Marketplace approach reaches 96% accuracy eventually.&lt;/p&gt;

&lt;h2 id=&quot;strengths-of-the-marketplace&quot;&gt;Strengths of the Marketplace&lt;/h2&gt;

&lt;p&gt;As you can see, backpropagation outperforms the Marketplace algorithm.
However, as engineers, it’s always about the trade-offs.
Surely backpropagation is better in this case, but there are still some strengths of the Marketplace approach.
Here, I list some key strengths of the Marketplace:&lt;/p&gt;

&lt;h3 id=&quot;scalability&quot;&gt;Scalability&lt;/h3&gt;

&lt;p&gt;The Marketplace approach has a potential advantage in scalability.
Scaling backpropagation with additional GPUs is challenging because its forward and backward passes are tightly coupled, and the backward pass requires strict sequential propagation, making parallelization difficult.
In contrast, the Marketplace approach is inherently scalable.
In theory, there is no limit to the number of vendors you can run within the same layer, as each can independently generate a variant of the current leading vendor by “rolling the dice.”
However, with more vendors, you may need to increase upstream sampling in downstream layers to evaluate them more thoroughly; otherwise, you risk wasting computational resources if intermediate products are underutilized.&lt;/p&gt;

&lt;p&gt;Some may ask: how do you synchronize vendor weights across nodes when scaling out? Since the current approach generates random weight deltas based on the learning rate and applies them to the leading vendor’s weights, we can use a random seed value to deterministically generate these deltas.
By sharing the seed across nodes, we can reconstruct the weights using the same deterministic random number generator.&lt;/p&gt;

&lt;p&gt;The Marketplace approach can be scaled along different axes.
With more vendors, you can run additional vendor variants in parallel.
However, more vendors do not necessarily improve mutation quality, as a limited batch size can constrain mutation quality.
Another interesting approach is to run the same set of vendor variants across multiple nodes for different batches simultaneously.
It may not be necessary to process numerous batches in the early stages of training.
In the later stages, to optimize the final decimal points of the loss, you may need to process many batches.
I tested a bit whether we could run the same set of vendor variants across multiple nodes for different batches simultaneously to improve the training speed.
I simulated the training process with multiple instances by running multiple forward passes sequentially.
Counterintuitively, performance degraded with more forward passes.
I am still investigating the reason.
If it’s possible to scale on the dataset axis in parallel, it would be a significant improvement.&lt;/p&gt;

&lt;p&gt;I wonder if we can scale the Marketplace approach efficiently, is it possible to shorten the training time?
Like, for example, the LLM pre-training process takes 6 months, with abundant GPUs, can we shorten it to 6 weeks?
I think it’s possible, but still requires a lot of research. 🤔&lt;/p&gt;

&lt;h3 id=&quot;optimizing-the-forward-pass-enhances-training&quot;&gt;Optimizing the Forward Pass Enhances Training&lt;/h3&gt;

&lt;p&gt;With backpropagation, the computational graph for training differs significantly from the forward pass.
Typically, to deploy a model in production, people optimize the forward pass, sometimes even building &lt;a href=&quot;https://en.wikipedia.org/wiki/Application-specific_integrated_circuit&quot;&gt;Application-Specific Integrated Circuits (ASICs)&lt;/a&gt; to accelerate applications like large language model (LLM) services.
The Marketplace relies solely on the forward pass, so optimizing the forward pass has potential to improve the training process.
It would be interesting to consider the idea of building a hardware accelerator for both inference and training.&lt;/p&gt;

&lt;h3 id=&quot;better-gpu-utilization&quot;&gt;Better GPU Utilization&lt;/h3&gt;

&lt;p&gt;Another issue with backpropagation is that its two distinct compute graphs result in lower GPU utilization than is optimal.
In contrast, when observing the GPU usage rate during Marketplace training, it’s nearly always at 100%.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/gpu-usage.png&quot; alt=&quot;A screenshot shows the GPU usage rate during Marketplace training. The GPU usage rate is nearly always at 100%.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A chart shows the GPU usage rate during Marketplace training. The GPU usage rate is nearly always at 100%.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Backpropagation’s need to switch between forward and backward passes also makes it less cache-friendly.
As highlighted in the previously mentioned CUDA video, GPU performance heavily depends on memory layout and access patterns.
By relying solely on the forward pass, the Marketplace approach can better utilize GPUs.
I heard an something funny about training with a large-scale GPU cluster: fluctuating GPU usage forced engineers to keep GPUs spinning idly to avoid sudden changes in power consumption, which placed significant strain on the power supply infrastructure.
I wonder if the Marketplace could help with this by focusing on the forward pass.&lt;/p&gt;

&lt;h3 id=&quot;no-need-to-make-the-model-differentiable&quot;&gt;No Need to Make the Model Differentiable&lt;/h3&gt;

&lt;p&gt;Another benefit of the Marketplace approach is that it doesn’t rely on computing gradients, so you don’t need to make your model differentiable.
As an interesting side note, with backpropagation, some corner cases, such as &lt;a href=&quot;https://en.wikipedia.org/wiki/Rectifier_(neural_networks)&quot;&gt;ReLU&lt;/a&gt;, are not continuous but still function effectively.
Since backpropagation has been the dominant approach for decades, I guess there has been relatively little research into non-differentiable models and their benefits.
At this moment, I’m unsure of the full implications.
My gut feeling suggests that for low-precision training, or even integer-based training, the Marketplace approach could be particularly beneficial.
This is yet another intriguing topic for future research.&lt;/p&gt;

&lt;h2 id=&quot;a-blockchain-for-training-llms&quot;&gt;A Blockchain for Training LLMs&lt;/h2&gt;

&lt;p&gt;With the advantages of marketplace training, I can already envision some exciting applications.
What’s bigger than the AI hype?
How about blockchain + AI? 🤣
Seriously, though, I believe this is feasible.
Here’s how I imagine it could work.&lt;/p&gt;

&lt;p&gt;First, we need to publish the training data to make it easily accessible to all miners.
An open-source dataset like &lt;a href=&quot;https://commoncrawl.org&quot;&gt;Common Crawl&lt;/a&gt; could be used, but the specific dataset isn’t critical—the key is ensuring accessibility.
We then define the training batches in a deterministic order.
Each miner’s task is to find the next seeds for each layer that yield the lowest loss in combination.
Miners can share their seeds on a peer-to-peer network, encapsulated with their digital signatures and public key.
The miner with the lowest loss wins the mining reward.
Anyone should be able to reapply the deterministic training dataset to verify whether the proposed block is indeed the best.
If a branch occurs, the chain with the lowest loss prevails.
This approach enables a decentralized network to pre-train an LLM on a blockchain!
This sounds like an intriguing weekend project, but I’ll save it for another time. 😅&lt;/p&gt;

&lt;h2 id=&quot;future-research&quot;&gt;Future Research&lt;/h2&gt;

&lt;p&gt;Now that we know the Marketplace algorithm can search for optimal neural network weights on a GPU, here are some ideas for future exploration.
But there are still some challenges to overcome.&lt;/p&gt;

&lt;h3 id=&quot;memory-efficiency&quot;&gt;Memory Efficiency&lt;/h3&gt;

&lt;p&gt;Currently, we store each vendor’s full weights, enabling parallel computation of layers with different inputs, which is highly GPU-efficient.
However, this approach consumes valuable GPU memory.
To explore a larger weight space with more vendor variants, we could implement seed-based random number generation for each vendor’s weights as mentioned in the previous section.
This way, we can store only the seeds, freeing up GPU memory while testing N vendor variants without losing the weights.&lt;/p&gt;

&lt;h3 id=&quot;distributed-training&quot;&gt;Distributed Training&lt;/h3&gt;

&lt;p&gt;With deterministic seed-based random number generation in place, we could build a cluster to efficiently share seeds across nodes.
Each node could reconstruct weights with minimal bandwidth requirements and independently run vendor variants.
The only significant data transfer between nodes would likely be the intermediate products, especially if we allow cross-node upstream sampling of products.&lt;/p&gt;

&lt;h3 id=&quot;improved-random-weight-generation-momentum&quot;&gt;Improved Random Weight Generation: Momentum&lt;/h3&gt;

&lt;p&gt;Currently, weight deltas are generated randomly, but the best-performing deltas contain valuable information.
If a CNN filter consistently moves in a specific direction that yields good results, that direction may be worth pursuing.
Introducing a momentum concept, similar to many optimizers, such as &lt;a href=&quot;https://arxiv.org/abs/1412.6980&quot;&gt;Adam&lt;/a&gt;, could be a valid approach to accelerate training.
For example, if a direction proves effective multiple times, we could bias random weight updates toward that direction to speed up convergence.
I envision that the improvement in the delta generator doesn’t need to be substantial.
A slight enhancement, combined with vendor scaling, could lead to significant performance improvements.&lt;/p&gt;

&lt;h3 id=&quot;sub-optimal-or-bad-final-products-are-still-valuable&quot;&gt;Sub-Optimal or Bad Final Products Are Still Valuable&lt;/h3&gt;

&lt;p&gt;The current approach only keeps the best final product and its delta.
But what about the others?
While they don’t perform as well as the best final product and their corresponding vendor deltas, they still contain valuable information.
With enough delta permutations and their corresponding final products, is it possible to statistically attribute which parameter in a big chunk of delta is responsible for a better final product?
For example, we could take the loss generated from the final products and attribute it to the delta that generated each final product.
By merging the delta, weighted by the attributions from the loss, we might be able to compose a better overall delta that accounts for both good and bad outcomes.
I think this is yet another very interesting idea to explore.&lt;/p&gt;

&lt;h3 id=&quot;training-larger-and-diverse-models&quot;&gt;Training Larger and Diverse Models&lt;/h3&gt;

&lt;p&gt;The Marketplace algorithm performs well with the small MNIST CNN, but its scalability remains untested.
How effectively does it handle larger models?
Beyond CNNs, can it be applied to other architectures, such as transformers?
I’m interested in pre-training a large language model (LLM) with this algorithm to evaluate its performance and scalability.
I see no particular reason why it wouldn’t work.
Maybe it just takes longer to train, but I hope to scale out to compensate for the time.
In fact, I’ve already trained a larger model, &lt;a href=&quot;https://github.com/tinygrad/tinygrad/blob/d0d39885c386d730da29f6a9cc1fdac589319b9e/extra/models/resnet.py&quot;&gt;ResNet-18&lt;/a&gt; (approximately 11 million parameters), using the Marketplace approach on a scaled MNIST dataset with limited steps.
The results seem promising, though I didn’t run the experiment for long.
It was a quick test, and I didn’t optimize the code.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/resnet-18-accuracy.svg&quot; alt=&quot;A chart shows the accuracy of the Marketplace approach on the scaled MNIST dataset with 100% smoothing. The x-axis is the step count, and the y-axis is the accuracy. With limited steps, the accuracy is trending up steadily showing progress of the training.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A chart shows the accuracy of the Marketplace approach on the scaled MNIST dataset with 100% smoothing. The x-axis is the step count, and the y-axis is the accuracy. With limited steps, the accuracy is trending up steadily showing progress of the training.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-08-18-marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/resnet-18-loss.svg&quot; alt=&quot;A chart shows the loss of the Marketplace approach on the scaled MNIST dataset with 100% smoothing. The x-axis is the step count, and the y-axis is the loss at log scale. With limited steps, the loss is trending down steadily showing progress of the training.&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;A chart shows the loss of the Marketplace approach on the scaled MNIST dataset with 100% smoothing. The x-axis is the step count, and the y-axis is the loss at log scale. With limited steps, the loss is trending down steadily showing progress of the training.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;With a larger model, each specification has more parameters to mutate, meaning a larger space to explore.
However, we could address this by breaking it down into smaller specifications or using a larger batch size to explore the space more effectively.
Another interesting idea is to shift the focus to different layers over time.&lt;/p&gt;

&lt;h3 id=&quot;focused-mutation&quot;&gt;Focused Mutation&lt;/h3&gt;

&lt;p&gt;During the experiments, I forgot to remove a flag that prevents a layer from mutating.
Surprisingly, this led to improved training performance.
My hypothesis is that freezing some layers reduces the search space while maintaining the same capacity, allowing better focus on optimizing the remaining layers and thus improving training performance.
I wonder if it makes sense to train only a few layers at a time for very large and deep models.
Strategically switching focus to a few layers with large-scale vendor variants could simplify training.
For example, if the first layer is a convolutional layer and the second is a fully connected layer, we could focus on training the first layer for a while, then switch to the second.
In a CNN, the initial layers extract basic features, while later layers capture more complex features.
Assuming the basic feature layers are sufficiently trained, their filters may not change much.
If this assumption holds, we could focus more on the later layers in the later stages of training.
This is another intriguing idea to test.&lt;/p&gt;

&lt;h3 id=&quot;optimizing-the-performance-of-the-marketplace&quot;&gt;Optimizing the Performance of the Marketplace&lt;/h3&gt;

&lt;p&gt;Currently, I’ve implemented the Marketplace algorithm using simple Tinygrad code.
I haven’t invested much effort in optimizing the code, but I believe there are many opportunities to improve performance.
For example, we could maximize GPU utilization or optimize memory usage by analyzing memory patterns and making them cache-friendly.
Alternatively, writing custom CUDA kernels could further enhance performance.
I’m unsure if I’ll have time to pursue these optimizations, but they are worth exploring.&lt;/p&gt;

&lt;h3 id=&quot;design-models-for-the-marketplace&quot;&gt;Design Models for the Marketplace&lt;/h3&gt;

&lt;p&gt;So far, all the models we have explored are designed for training with backpropagation.
There are many factors to consider when designing models for backpropagation.
For example, the model must be differentiable, and the initial weights need to be carefully chosen to avoid exploding gradients.
We also use skip connections to mitigate vanishing gradients.
Additionally, numerous techniques exist to enhance the training process specifically for backpropagation.
But what about the Marketplace approach?
What if we design a model optimized for the Marketplace approach, specifically tailored for distributing computation across multiple GPUs and nodes?
This is yet another fascinating topic to explore.&lt;/p&gt;

&lt;h3 id=&quot;combining-the-marketplace-approach-with-backpropagation&quot;&gt;Combining the Marketplace Approach with Backpropagation&lt;/h3&gt;

&lt;p&gt;I wonder if we can combine the Marketplace approach with backpropagation to train the same model in different scenarios.
For example, if backpropagation gets trapped in a local minimum, can we switch to the Marketplace approach to escape it?
Alternatively, could we alternate between the Marketplace approach and backpropagation at different stages of training to enhance the process?
Before we can answer these questions, we need to understand the nature of the Marketplace approach.
There are already so many intriguing questions to explore.&lt;/p&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;That’s it!
I learned a lot from this project.
Regardless if people find my research side project interesting or not, it was a fun ride for myself.
It was a great exercise to think about optimizing the training process and designing an algorithm for GPU efficiency.
Now I have more confidence to think about algorithms from the lens of GPU efficiency.
There could be more interesting ideas to explore with GPUs.&lt;/p&gt;

&lt;p&gt;I believe this is the best time to be alive.
People from ten years ago could hardly have imagined that we’d have abundant supercomputers in our homes.
With modern hardware, it’s incredible that I can conduct end-to-end experiments on my own in such a short time.
We’re entering a new era of personal supercomputing.
I hope you enjoyed reading about my Marketplace algorithm.
Please leave a comment with any feedback or questions.&lt;/p&gt;
</description>
        <pubDate>Mon, 18 Aug 2025 07:00:00 +0000</pubDate>
        <link>https://fangpenlin.com/posts/2025/08/18/marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/</link>
        <guid isPermaLink="true">https://fangpenlin.com/posts/2025/08/18/marketplace-my-first-attempt-at-training-without-backprop-on-gpu-efficiently/</guid>
      </item>
    
      <item>
        <title>CakeLens V5, the AI-gen video detection model is now open-sourced!</title>
        <description>&lt;style type=&quot;text/css&quot;&gt;
  figure {
    text-align: center;
    margin: 0 auto;
  }
  figcaption, figcaption p {
    color: grey;
    font-size: 0.9em;
  }
  figure img {
    max-width: 100%;
  }
&lt;/style&gt;

&lt;p&gt;Today, I’m excited to announce that the &lt;a href=&quot;http://cakelens.ai&quot;&gt;CakeLens&lt;/a&gt; v5 AI-generated video detection model is now open-source!
Why open-source, you might ask?
Well, the CakeLens project serves multiple purposes for me. (For more background, see my earlier article: &lt;a href=&quot;/posts/2025/06/03/i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/&quot;&gt;I built an AI-gen detection model for videos, here’s what I learned&lt;/a&gt;.)
The most critical one was to teach myself how to build a machine learning-powered product from end to end, including data collection, labeling, model design, training, and inference.
It has already achieved that goal.&lt;/p&gt;

&lt;p&gt;Beyond personal learning, I hoped CakeLens could uncover some business value.
However, the reality is that most users don’t care much whether the funny videos they see online are AI-generated or not.
In an enterprise context, though, detecting AI-generated content is more critical.
For example, ID verification services often require users to hold an ID and take a photo or short video.
What happens if scammers generate realistic fake photos or videos in the future?
What if job candidates alter their face or voice?
These issues are &lt;a href=&quot;https://www.linkedin.com/feed/update/urn:li:activity:7292604406464671744/&quot;&gt;already happening&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-07-30-open-source-cakelens-v5/face-swapped-interview.png&quot; alt=&quot;Example of face-swapped job interview video&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;
      Screenshot from a LinkedIn post (&lt;a href=&quot;https://www.linkedin.com/feed/update/urn:li:activity:7292604406464671744/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;source&lt;/a&gt;) showing a job interview conducted via video call, where the candidate&apos;s face has been swapped using AI-generated imagery. This highlights real-world cases of AI-generated content being used for identity fraud in professional settings.
    &lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I believe this represents a new market category yet to be fully addressed.
CakeLens is my initial step into exploring this space.&lt;/p&gt;

&lt;p&gt;While the CakeLens v5 model for detecting AI-generated videos works reasonably well with a limited dataset (77% precision, 74% recall with 50% threshold), its accuracy is still too low for enterprise use cases.
I searched for similar open-source models but found none.
I guess cool kids are mostly focused on large language models (LLMs) right now.
Although this isn’t a flashy moonshot project, I believe open-sourcing CakeLens v5 offers educational value to others. So, here we are!&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;how-can-i-use-it&quot;&gt;How Can I Use It?&lt;/h2&gt;

&lt;p&gt;I have uploaded the model weights to &lt;a href=&quot;https://huggingface.co/fangpenlin/cakelens-v5&quot;&gt;Hugging Face&lt;/a&gt;.
The model is relatively small, with a size of only 3.25 GB and 270 million parameters.
I have also created an open-source library, &lt;a href=&quot;https://github.com/LaunchPlatform/cakelens-v5&quot;&gt;cakelens-v5&lt;/a&gt;, for using the model in your Python projects or as a command-line tool.
To use it as a command-line tool, you can run it with &lt;a href=&quot;https://docs.astral.sh/uvx/&quot;&gt;uvx&lt;/a&gt; like this:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uvx &lt;span class=&quot;nt&quot;&gt;--with&lt;/span&gt; torch &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--with&lt;/span&gt; torchvision &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--with&lt;/span&gt; torchcodec &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--with&lt;/span&gt; huggingface-hub &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  cakelens-v5 path/to/your/video.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then you will see the output like this:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-07-30-open-source-cakelens-v5/cakelens-cli-output-screenshot.png&quot; alt=&quot;Output of uvx cakelens-v5 command-line tool&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;
      Output of uvx cakelens-v5 command-line tool.
    &lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Of course, you can also use it in your Python projects.
First, install the library:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;cakelens-v5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You also need to install the dependencies:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;torch torchvision torchcodec huggingface-hub
&lt;span class=&quot;c&quot;&gt;# or, if you want to use the model with CUDA:&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# pip install torch torchvision torchcodec \&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#   huggingface-hub \&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#   --index-url https://download.pytorch.org/whl/cu128&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then you can use it with the following code:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pathlib&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;cakelens.detect&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Detector&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;cakelens.model&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Model&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Create model and load from Hugging Face Hub
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# load the model weights from Hugging Face Hub
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load_from_huggingface_hub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# or, if you have a local model file:
# model.load_state_dict(torch.load(&quot;model.pt&quot;)[&quot;model_state_dict&quot;])
&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Create detector
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Detector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cpu&quot;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# or &quot;cuda&quot;, &quot;mps&quot;, or None for auto-detection
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Run detection
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;video_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pathlib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;video.mp4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;verdict&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;detector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;video_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Access results
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Video: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;verdict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;video_filepath&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Frame count: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;verdict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frame_count&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Predictions:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prob&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;verdict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;predictions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;  Label &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;why-v5&quot;&gt;Why v5?&lt;/h2&gt;

&lt;p&gt;When designing and training this model, I tried many approaches.
For instance, I cropped a small window across multiple frames to test performance.
I experimented with time-wise CNN layers followed by spatial layers and many other variants.
After numerous iterations, I landed on v5, which makes somewhat reliable predictions.
Since I went through so many versions to get here, I decided to start with v5 as the first public release—it just sounds cooler! 🤣&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-07-30-open-source-cakelens-v5/train-batches.png&quot; alt=&quot;Screenshot of the training batches page of our internal tool showing the dataset size growing across different iterations&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;
      Screenshot of the training batches page of our internal tool showing the dataset size growing across different iterations.
    &lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;data-collection&quot;&gt;Data Collection&lt;/h2&gt;

&lt;p&gt;For v5, the dataset consists of 5,093 videos for training and 498 for testing, collected from X and labeled manually one by one.
The dataset is randomly split into two groups: 90% for training and 10% for testing.
When a post’s author indicates that a video is AI-generated and specifies the model used, I label it accordingly if the information seems reliable.
The videos come in various resolutions, such as 1080p, 720p, and others, depending on the original uploaded resolution.
Not all videos have high-resolution variants available.
I fed different resolution variants into the model to ensure it can detect patterns regardless of image size.&lt;/p&gt;

&lt;h2 id=&quot;labels&quot;&gt;Labels&lt;/h2&gt;

&lt;p&gt;When designing this model, I wanted to see if it could identify which model was used to generate a video.
Therefore, I added labels for each video generation model.
Additionally, some videos are anime or video game content.
I also wanted to know if the model could distinguish between 2D anime, 3D anime, or video game styles, so I included those labels as well.
Despite having more labels than just “AI-generated or not,” the limited dataset size means the model’s predictions for these labels aren’t very accurate yet.
Below is the list of labels:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;AI_GEN&lt;/em&gt;: Is the video AI-generated or not?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;ANIME_2D&lt;/em&gt;: Is the video in 2D anime style?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;ANIME_3D&lt;/em&gt;: Is the video in 3D anime style?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;VIDEO_GAME&lt;/em&gt;: Does the video look like a video game?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;KLING&lt;/em&gt;: Is the video generated by Kling?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;HIGGSFIELD&lt;/em&gt;: Is the video generated by Higgsfield?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;WAN&lt;/em&gt;: Is the video generated by Wan?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;MIDJOURNEY&lt;/em&gt;: Is the video generated using images from Midjourney?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;HAILUO&lt;/em&gt;: Is the video generated by Hailuo?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;RAY&lt;/em&gt;: Is the video generated by Ray?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;VEO&lt;/em&gt;: Is the video generated by Veo?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;RUNWAY&lt;/em&gt;: Is the video generated by Runway?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;SORA&lt;/em&gt;: Is the video generated by Sora?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;CHATGPT&lt;/em&gt;: Is the video generated using images from ChatGPT?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;PIKA&lt;/em&gt;: Is the video generated by Pika?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;HUNYUAN&lt;/em&gt;: Is the video generated by Hunyuan?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;VIDU&lt;/em&gt;: Is the video generated by Vidu?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, new video generation models are always emerging, such as Midjourney Video, which is too new to be included in v5.&lt;/p&gt;

&lt;h2 id=&quot;the-architecture&quot;&gt;The Architecture&lt;/h2&gt;

&lt;p&gt;The architecture of the model looks like:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/2025-07-30-open-source-cakelens-v5/model-structure.svg&quot; alt=&quot;Diagram of CakeLens v5 model architecture&quot; /&gt;
  &lt;figcaption&gt;
    &lt;p&gt;
      Diagram of the CakeLens v3 model architecture. The model processes cropped and resized video frames (9 frames at 512x512 resolution) through an initial convolutional input layer, followed by a series of six space-time convolutional blocks. Each block consists of a spatial convolution (3x3 kernel), a temporal convolution (3x1 kernel), ReLU activations, and instance normalization, with skip connections forming a residual network. The final output passes through a fully connected layer to produce predictions for each label.
    &lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As you can see, it’s a simple CNN network with six space-time layers plus one input layer.
The input layer primarily reduces computational cost by downsizing the input video.
Each space-time layer consists of a spatial layer with CNN kernels that detect features in the spatial domain, followed by a temporal layer that looks for features in the time domain.
These layers are connected with skip connections, forming a residual network that makes training more efficient.&lt;/p&gt;

&lt;p&gt;For video input, I break the file into framesets, each containing nine video frames at a resolution of 512x512 pixels.
Videos larger than this are cropped, while smaller ones are centered and padded with zeros to reach 512x512.
This resolution choice reduces computational cost.
Additionally, cropping to 512x512 helps eliminate data leakage from elements like TikTok logos, TV channel icons, news banners, or AI-generated watermarks (e.g., “Veo”) that often appear at the edges or corners.
I didn’t have time to mask these elements, so cropping minimizes their impact on the model’s decisions.&lt;/p&gt;

&lt;p&gt;Interestingly, someone asked why I used a CNN when I shared this project.
I hadn’t thought about it initially and didn’t know how to respond.
Upon reflection, I chose CNNs because I assumed subtle space-time patterns exist in video frame sequences, and CNN kernels can detect these features across larger ranges in deeper layers.
From a product perspective, though, the specific model choice matters less than delivering value and enabling data collection.
I can always swap out the model for something better later.&lt;/p&gt;

&lt;h2 id=&quot;next-version&quot;&gt;Next Version&lt;/h2&gt;

&lt;p&gt;I’ve been training the v6 model for a while using a workstation with two AMD 7900XTX GPUs, as mentioned in a &lt;a href=&quot;/posts/2025/06/11/two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/&quot;&gt;previous article&lt;/a&gt;.
It’s not as fast as an H100 from Modal, so progress is slower.
The v6 architecture is largely the same but includes minor adjustments and a larger dataset.
Limited computing resources prevent me from experimenting with larger or more complex models for now.
I’ll let v6 train for a bit while I shift focus to other projects.&lt;/p&gt;

&lt;h2 id=&quot;finally&quot;&gt;Finally&lt;/h2&gt;

&lt;p&gt;I hope you find CakeLens v5 somewhat useful or, at the very least, educational.
While I can’t dedicate much time to this project right now, it’s now open-source, and I’d love to hear your feedback on how to improve it.
Leave a comment below and let me know! Thanks!&lt;/p&gt;
</description>
        <pubDate>Wed, 30 Jul 2025 07:00:00 +0000</pubDate>
        <link>https://fangpenlin.com/posts/2025/07/30/open-source-cakelens-v5/</link>
        <guid isPermaLink="true">https://fangpenlin.com/posts/2025/07/30/open-source-cakelens-v5/</guid>
      </item>
    
      <item>
        <title>Two AMD 7900XTX GPUs in a Tinygrad-Based Training Workstation with Peer-to-Peer PCIe Communication</title>
        <description>&lt;style type=&quot;text/css&quot;&gt;
  figure {
    text-align: center;
    margin: 0 auto;
  }
  figcaption, figcaption p {
    color: grey;
    font-size: 0.9em;
  }
  figure img {
    max-width: 100%;
  }
&lt;/style&gt;

&lt;p&gt;I’ve been diving into machine learning projects lately, and I enjoy it a lot.
However, one thing bothers me: I lack the computing power to test many interesting ideas.
In my previous &lt;a href=&quot;/posts/2025/06/03/i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/&quot;&gt;article for CakeLens&lt;/a&gt;, I designed and trained a model to detect AI-generated videos.
But due to limited local computing power, I had to rent H100/A100 GPUs from Modal to experiment with different approaches.
And it’s not cheap:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/modal-cost.png&quot; alt=&quot;Screenshot of Modal&apos;s billing dashboard showing the total cost as $2.1K&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of Modal&apos;s billing dashboard showing the total cost as $2.1K&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;My PC has an RTX 4090, so I could run training locally.
However, memory constraints make it painful to train larger models.
Even when a model fits on the GPU, the intensive computation consumes all GPU resources, rendering my PC unusable.
To solve this, I need more local computing power to run machine learning experiments without breaking the bank.&lt;/p&gt;

&lt;p&gt;My first idea was to buy another RTX 4090 or perhaps an RTX 5090.
I checked prices online and was shocked.
I bought my current 4090 for around $2,000 USD, but now they’re selling for $3,600 on Amazon.
That’s insane! 🤯&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/rtx-4090-price.png&quot; alt=&quot;Screenshot of Amazon product page, featuring Asus ROG Strix RTX 4090 GPU selling at $3,599.95&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of Amazon product page, featuring Asus ROG Strix RTX 4090 GPU selling at $3,599.95&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Curious about the cost of an H100 for my home office, I checked its price:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/h100-price.png&quot; alt=&quot;FIXME&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of Amazon product page, featuring Nvidia Tesla H100 GPU selling at $25,249.95&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Heh, you know what?
I’m not planning to sell two KDNYs I have accumulated just yet 😅.&lt;/p&gt;

&lt;p&gt;I don’t have the budget for more Nvidia GPUs right now, but I still want a local setup to experiment at a lower cost.
One day, while browsing X, I found &lt;a href=&quot;https://x.com/__tinygrad__/status/1924152751472497118&quot;&gt;a post by Tinygrad&lt;/a&gt; showcasing their gradient functions defined in just 40 lines of code.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/tinygrad-gradient.png&quot; alt=&quot;A X post by @__tinygrad__ showcasing gradients for backprop defined in just 40 lines&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;A &lt;a href=&quot;https://x.com/__tinygrad__/status/1924152751472497118&quot;&gt;X post&lt;/a&gt; by &lt;a href=&quot;https://x.com/__tinygrad__&quot;&gt;@__tinygrad__&lt;/a&gt; showcasing gradients for backprop defined in just 40 lines&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;I tried it, and it was impressive—no dependencies, just an instant install with &lt;a href=&quot;https://docs.astral.sh/uv/&quot;&gt;uv&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/tinygrad-uv-install.png&quot; alt=&quot;Screenshot of installing tinygrad with uv shows it only takes 9ms&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of installing tinygrad with uv shows it only takes 9ms&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;After researching further, I really liked &lt;a href=&quot;https://tinygrad.org&quot;&gt;Tinygrad&lt;/a&gt;’s concept.
It’s like the &lt;a href=&quot;https://en.wikipedia.org/wiki/Reduced_instruction_set_computer&quot;&gt;RISC (Reduced Instruction Set Computer)&lt;/a&gt; of machine learning, while &lt;a href=&quot;https://pytorch.org&quot;&gt;PyTorch&lt;/a&gt; feels more like &lt;a href=&quot;https://en.wikipedia.org/wiki/Complex_instruction_set_computer&quot;&gt;CISC (complex instruction set computer)&lt;/a&gt;.
I appreciate its clean, minimalist design, and it seems to support AMD GPUs well.&lt;/p&gt;

&lt;p&gt;This made me wonder: why does everyone say Nvidia GPUs are the go-to for machine learning?
They claim Nvidia’s strength lies in its software.
Hmm, is that true? 🤔
Or, as some might say, is it a skill issue? 🤣&lt;/p&gt;

&lt;p&gt;I’m not sure, but I wanted to find out.
I’m curious about &lt;a href=&quot;https://tinygrad.org/#tinybox&quot;&gt;Tinygrad’s pre-built AMD training workstation&lt;/a&gt;.
It’s tempting, but it’s outside the budget I can allocate, and it’s too bulky for my home office.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/tinybox-red.png&quot; alt=&quot;Screenshot of Tinybox, a pre-built machine learning work station by the tiny corp featuring AMD and Nvidia GPU options. The AMD option is selling at $15,000 USD&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of Tinybox, a pre-built machine learning work station by the tiny corp featuring AMD and Nvidia GPU options. The AMD option is selling at $15,000 USD&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Looked at the GPUs they are using, the AMD 7900XTX seemed mature.
Best of all, the price was reasonable—just $1,100:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/amd-7900xtx-price.png&quot; alt=&quot;Screenshot of Amazon product page, featuring XFX AMD Radeon RX 7900XTX GPU selling at $1,099.54&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of Amazon product page, featuring XFX AMD Radeon RX 7900XTX GPU selling at $1,099.54&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;I had a retired PC, so I quickly purchased two 7900XTX GPUs:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/two-7900xtx-boxes.jpg&quot; alt=&quot;Two boxes of XFX AMD Radeon RX 7900XTX GPUs&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Two boxes of XFX AMD Radeon RX 7900XTX GPUs&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;I did my best with cable management:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/7900xtx-cable-management.jpg&quot; alt=&quot;Two  XFX AMD Radeon RX 7900XTX GPUs in an open PC case with PCI power cables connected to them&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Two  XFX AMD Radeon RX 7900XTX GPUs in an open PC case with PCI power cables connected to them&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;It was time-consuming, and I tried 😅&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;peer-to-peer-pcie-communication-issues&quot;&gt;Peer-to-Peer PCIe Communication Issues&lt;/h2&gt;
&lt;p&gt;I ran MNIST and other Tinygrad examples, and they worked fine with one GPU.
But with two GPUs, I kept encountering errors like this:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/tinygrad-ioctl-error.png&quot; alt=&quot;Screenshot of Python raising OSError from a fnctl.ioctl function call when running Tinygrad with the multi-GPUs MNIST example&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of Python raising OSError from a fnctl.ioctl function call when running Tinygrad with the multi-GPUs MNIST example&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;It turns out the system was trying to transfer data between GPUs using &lt;a href=&quot;https://docs.kernel.org/driver-api/pci/p2pdma.html&quot;&gt;peer-to-peer (P2P) PCIe communication&lt;/a&gt;, but this wasn’t available on my setup.
If you look at the system log messages, you will see the error message:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Failed to map peer:0000:0c:00.0 mem_domain:4&lt;/p&gt;
&lt;/blockquote&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/map-peer-failure.png&quot; alt=&quot;Screenshot from Linux kernel message showing Failed to map peer:0000:0c:00.0 mem_domain:4 error&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot from Linux kernel message showing Failed to map peer:0000:0c:00.0 mem_domain:4 error&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;I discovered a tool &lt;a href=&quot;https://github.com/ROCm/rocm_bandwidth_test&quot;&gt;rocm-bandwidth-test&lt;/a&gt; that benchmarks PCIe communication performance between the CPU and GPUs on the ROCm platform.
I tried installing it on NixOS, but it wasn’t in nixpkgs.
So, I created a Nix package and submitted a &lt;a href=&quot;https://github.com/NixOS/nixpkgs/pull/415742&quot;&gt;pull request&lt;/a&gt; for it.
After running the benchmark, I confirmed there was no link between the two GPUs:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/rocm-bandwidth-test-does-not-work.png&quot; alt=&quot;Screenshot of rocm-bandwidth-test&apos;s result, shows there&apos;s no direct connection between the two GPUs&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of rocm-bandwidth-test&apos;s result, shows there&apos;s no direct connection between the two GPUs&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;There’s &lt;a href=&quot;https://docs.kernel.org/gpu/amdgpu/module-parameters.html&quot;&gt;an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;amdgpu.pcie_p2p&lt;/code&gt; option in the Linux kernel&lt;/a&gt;, but it wasn’t available on my PC.
I searched online, but no one seemed to have encountered this issue.
When no documentation exists, even LLMs can’t provide a solution.&lt;/p&gt;

&lt;h2 id=&quot;enabling-p2p-pcie-communication-in-the-linux-kernel&quot;&gt;Enabling P2P PCIe Communication in the Linux Kernel&lt;/h2&gt;

&lt;p&gt;I read the Linux kernel config file and learned that the P2P PCIe feature is relatively new.
To enable it, you need to activate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DMABUF_MOVE_NOTIFY&lt;/code&gt; &lt;a href=&quot;https://github.com/torvalds/linux/blob/64980441d26995ea5599958740dbf6d791e81e27/drivers/gpu/drm/amd/amdkfd/Kconfig#L30&quot;&gt;option&lt;/a&gt; while compiling the kernel.&lt;/p&gt;

&lt;p&gt;With &lt;a href=&quot;https://nixos.org&quot;&gt;NixOS&lt;/a&gt;, you can configure it like this:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;boot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;kernelPatches&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;enable-hsa-amd-p2p&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;patch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;extraStructuredConfig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;lib&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;DMABUF_MOVE_NOTIFY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;yes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;HSA_AMD_P2P&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;yes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then rebuild your NixOS system and reboot.
After that, running the ROCm PCIe communication benchmark showed data exchanges between the GPUs.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/rocm-bandwidth-test-works.png&quot; alt=&quot;Screenshot of rocm-bandwidth-test&apos;s result, shows no direct connection between the two GPUs&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of rocm-bandwidth-test&apos;s result, shows no direct connection between the two GPUs&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;The transmission rate was limited due to my motherboard or possibly the &lt;a href=&quot;https://en.wikipedia.org/wiki/Socket_AM4&quot;&gt;AM4 platform&lt;/a&gt;, as PCIe bandwidth is shared between the two GPUs:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/motherboard-pcie-limits.png&quot; alt=&quot;Screenshot from the manual of ASUS ROG X570 Crosshair VIII Hero motherboard shows that with dual VGA setup, each of them have PCIe4x8 vs single VGA has PCIe4x16&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot from the manual of ASUS ROG X570 Crosshair VIII Hero motherboard shows that with dual VGA setup, each of them have PCIe4x8 vs single VGA has PCIe4x16&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;To improve PCIe communication speed, I’d likely need to upgrade to a &lt;a href=&quot;https://www.amd.com/en/products/processors/workstations/ryzen-threadripper.html&quot;&gt;Threadripper platform&lt;/a&gt;.
But that’s overkill for now.
At least it works!
To run the multi-GPU MNIST example, use:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;IOCTL_PROCESSOR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;x86_64 &lt;span class=&quot;nv&quot;&gt;IOCTL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;AMD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 python &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  examples/beautiful_mnist_multigpu.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I’m not sure why &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IOCTL&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IOCTL_PROCESSOR&lt;/code&gt; are needed, but I ran into other ioctl errors without them.
Now, with two GPUs it’s training at double the performance of a single GPU 🎉:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/mnist-training-with-two-gpus.png&quot; alt=&quot;Screenshot of output from Tinygrad&apos;s multi-GPU MNIST example on two AMD GPUs running at 61 iterations per second&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of output from Tinygrad&apos;s multi-GPU MNIST example on two AMD GPUs running at 61 iterations per second&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Compared to the RTX 4090, here’s the performance of Tinygrad’s MNIST example:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-11-two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/rtx-4090-mnist-performance.png&quot; alt=&quot;Screenshot of output from Tinygrad&apos;s multi-GPU MNIST example on a single 4090 GPU running at 29 iterations per second&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of output from Tinygrad&apos;s multi-GPU MNIST example on a single 4090 GPU running at 29 iterations per second&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Note that this isn’t a scientific benchmark.
Tinygrad may have optimization potential or settings to maximize performance on each platform.
This is just a rough comparison to gauge the training speed I’m getting.
The two PCs also have different CPUs and memory (the one with the AMD GPU has older hardware).&lt;/p&gt;

&lt;p&gt;Additionally, the approach I described above uses the amdgpu driver provided in the Linux kernel.
Tinygrad has developed its own custom userspace driver called &lt;a href=&quot;https://docs.tinygrad.org/developer/am/&quot;&gt;AM&lt;/a&gt;, which they claim is more stable than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;amdgpu&lt;/code&gt; for Tinygrad machine learning workloads.
I’ve only tested it briefly.
That’s another topic to explore later.&lt;/p&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;I’m thrilled to run training with two AMD 7900XTX GPUs at a lower cost in my home office.
I can now free up my RTX 4090 and avoid intensive ML tasks disrupting my work.
The setup cost me $1,100 × 2 for the GPUs plus a new 1600W power supply for $500, totaling $2,700.
I plan to rewrite my ML projects with Tinygrad and run them on my new AMD ML workstation.&lt;/p&gt;

&lt;p&gt;I hope AMD gains more popularity in machine learning computing.
Otherwise, we’ll be stuck with GPUs priced at $2,000 but selling for $3,600.
I appreciate open-source projects like Tinygrad leading the way.
As I mentioned in my previous article, the real barrier for enthusiasts like me, who aren’t wealthy, isn’t a PhD degree—it’s computing power.
I wrote this article because this information is hard to find online and needs to be accessible so everyone can learn.&lt;/p&gt;

&lt;p&gt;That’s it! I may write more articles if I encounter interesting challenges with my dual 7900XTX setup.
I hope you find this somewhat interesting and useful 😄&lt;/p&gt;
</description>
        <pubDate>Wed, 11 Jun 2025 07:00:00 +0000</pubDate>
        <link>https://fangpenlin.com/posts/2025/06/11/two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/</link>
        <guid isPermaLink="true">https://fangpenlin.com/posts/2025/06/11/two-amd-7900xtx-gpus-tinygrad-based-training-workstation-peer-to-peer-pcie-communication/</guid>
      </item>
    
      <item>
        <title>I built an AI-gen video detection model and browser extension in a month</title>
        <description>&lt;style type=&quot;text/css&quot;&gt;
  figure {
    text-align: center;
    margin: 0 auto;
  }
  figcaption, figcaption p {
    color: grey;
    font-size: 0.9em;
  }
  figure img {
    max-width: 100%;
  }
&lt;/style&gt;

&lt;p&gt;Have you ever wondered while browsing the internet whether the video or image you’re viewing is real or AI-generated?
People say, “Seeing is believing,” but that’s less true in the AI era. Nowadays, generating photorealistic videos with audio is easier and cheaper than ever.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/kangaroo.jpg&quot; alt=&quot;AI-generated video featuring a kangaroo denied boarding on a flight as an emotional support animal, posted by @gdb on X&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;&lt;a href=&quot;https://x.com/AutismCapital/status/1927866536137752772&quot;&gt;AI-generated video featuring a kangaroo denied boarding on a flight as an emotional support animal, posted by @gdb on X&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;If you’ve followed trends on X, you may have noticed many users liked and reposted a video of a kangaroo being denied boarding on an airplane as an emotional support animal, ticket in paw. As adorable as it was, the video was AI-generated.
The ticket’s text is gibberish. I call it “AI fonts.”
I’m no linguistics expert, but the verbal exchange also felt off.&lt;/p&gt;

&lt;p&gt;I’ve faced the same issue.
While browsing X, I’ve retweeted content, only to later realize it was AI-generated, which was embarrassing.
I wished for an easy-to-use tool to distinguish AI-generated content from real content.
I tested several online tools claiming to detect AI-generated content, but none worked as expected.
So, I spent the past month training a model and building a browser extension focused on detecting AI-generated videos on X.
I named it CakeLens, inspired by the viral “Is it a cake?” videos.
Instead of identifying cakes, it detects AI-generated content.
I chose the name because I wanted it to be as easy as “a piece of cake” to use.&lt;/p&gt;

&lt;p&gt;CakeLens is now &lt;a href=&quot;https://chromewebstore.google.com/detail/cakelens-detect-ai-gen-co/koplmoaoamjgibclpdibnimodceoaapc&quot;&gt;available on the Chrome Web Store&lt;/a&gt;.
You need to &lt;a href=&quot;https://cakelens.ai/signup/&quot;&gt;sign up&lt;/a&gt; for an account at &lt;a href=&quot;https://cakelens.ai&quot;&gt;CakeLens.ai&lt;/a&gt; to use it.
Once set up, a button appears in the upper-right corner of videos on X when you hover over them.
Click it to submit the video for AI-generated content detection.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/cakelens-extension-screenshot.jpg&quot; alt=&quot;Screenshot of X.com showing the CakeLens button on the upper-right corner when hovering on a video&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of X.com showing the CakeLens button on the upper-right corner when hovering on a video&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;View the detection results on the &lt;a href=&quot;https://cakelens.ai/account/submissions&quot;&gt;submissions page&lt;/a&gt; of your CakeLens account.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/cakelens-submissions-screenshot.jpg&quot; alt=&quot;Screenshot of the submission page of CakeLens&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of the submission page of CakeLens&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;The latest version of my model achieves 77% precision and 74% recall on the validation dataset at 50% as the threshold.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/pr-curve.png&quot; alt=&quot;Screenshot of TensorBoard PR curve for CakeLens&apos; latest model&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of TensorBoard PR curve for CakeLens&apos; latest model&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;I’ve learned a lot from this project.
Today, I’m sharing what I’ve learned from building this pet project!&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;why-i-built-it&quot;&gt;Why I Built It&lt;/h2&gt;
&lt;p&gt;Two months ago, I saw a post on X that upset me.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/earthquake.jpg&quot; alt=&quot;X post video showing Myanmar earthquake turns out to be likely AI-gen&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;&lt;a href=&quot;https://x.com/K13News/status/1905802783858516208&quot;&gt;X post&lt;/a&gt; video showing Myanmar earthquake turns out to be likely AI-gen&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;It frustrates me that while people suffer from disasters, a few uses AI tools to create fake videos for attention.
Most AI-generated content is created for positive purposes, like art, which I support.
However, what prevents bad actors from using these tools for scams or propaganda?
As AI-generated content grows, so will its misuse.
I don’t believe governments should ban new technologies due to potential misuse.
Although most AI generation providers build safety mechanisms into their systems to prevent common misuse cases, AI technologies will only become more prevalent.
Sooner or later, everyone will have access to these tools, so it’s not meaningful to focus solely on restricting them from the tool side.&lt;/p&gt;

&lt;p&gt;Instead of stopping users from misusing it, I believe in fighting technology with technology—like beating magic with magic.
That’s when I decided to build CakeLens.
Additionally, I recently built a &lt;a href=&quot;https://beanhub.io/blog/2025/04/21/new-beanhub-inbox-feature/&quot;&gt;feature using large language models (LLMs) for BeanHub&lt;/a&gt;.
However, I feel less comfortable as an engineer when using new technology without understanding the fundamentals.
For example, I wrote an article titled &lt;a href=&quot;/posts/2019/10/07/elliptic-curve-cryptography-explained/&quot;&gt;Elliptic Curve Cryptography Explained&lt;/a&gt; to understand ECC before using it at work, because I don’t like treating it as a mystical black box.
Using LLMs or other machine learning technologies is similar; it’s easy, and anyone who knows how to call an API can do it.
But what about data collection, labeling, training, and inference from the ground up?
This pet project became my &lt;a href=&quot;https://graphics8.nytimes.com/images/blogs/freakonomics/pdf/DeliberatePractice(PsychologicalReview).pdf&quot;&gt;deliberate practice&lt;/a&gt; in machine learning, as it gives me a chance to build it from end to end.&lt;/p&gt;

&lt;h2 id=&quot;building-the-chrome-extension&quot;&gt;Building the Chrome Extension&lt;/h2&gt;
&lt;p&gt;I envisioned CakeLens as a Chrome extension to automatically label AI-generated content on web pages you browse, similar to an antivirus scanning for viruses but targeting AI-generated content instead.
While this idea sounds appealing, it’s impractical for several reasons.
The internet has too much content to scan everything affordably, and users may not care if a cute cat video is AI-generated.
Privacy is another concern.
Detecting AI-generated content requires significant computing resources, likely on a server, which could resemble tracking user browsing activity.
I wasn’t comfortable with that.
To balance functionality and privacy, I made a compromise.&lt;/p&gt;

&lt;p&gt;Instead of scanning all content automatically, CakeLens only analyzes content when users request it.
Since popular AI-generated content often sparks widespread doubt, I only need to analyze it once and keep the result.
The extension can download a list of known AI-generated content and flags matches on the page, preserving user privacy.
With this approach, I began building the Chrome extension.&lt;/p&gt;

&lt;p&gt;I’ve built countless software projects, but this was my first Chrome extension.
It was more challenging than expected, largely due to security constraints.
Extensions operate in isolated sandbox environments, communicating via messaging with strict permission whitelisting to prevent malicious actions.
I also learned some &lt;a href=&quot;https://github.com/acdlite/react-fiber-architecture&quot;&gt;React Fiber&lt;/a&gt; tricks to extract the information needed for the extension.
This topic deserves its own article, but today, I’ll focus on the machine learning aspects.&lt;/p&gt;

&lt;h2 id=&quot;building-internal-tools-for-data-collection&quot;&gt;Building Internal Tools for Data Collection&lt;/h2&gt;
&lt;p&gt;With the extension built, I could send videos to my API server and store them in a database.
To train the AI model, I needed labeled data.
Reflecting on thousands of collected videos, I realized internal tools are critical but often overlooked.
Efficient data handling depends on these tools, so I built an intuitive UI for labeling data to streamline the process.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/internal-tool.png&quot; alt=&quot;Screenshot internal CakeLens tool webpage for viewing, labeling and editing submissions&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot internal of CakeLens for viewing, labeling and editing submissions&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;h2 id=&quot;evolving-the-model-with-hyperparameter-gradient-descent&quot;&gt;Evolving the Model with Hyperparameter Gradient Descent&lt;/h2&gt;
&lt;p&gt;Building the extension and internal tools was straightforward compared to designing the model.
The challenge was creating a model that generalizes pattern recognizing while balancing performance and cost.
Ideally, I’d define an evaluation metric, and a genetic algorithm would evolve the model automatically.
My previous &lt;a href=&quot;/posts/2025/02/18/maze-my-ai-models-are-finally-evolving/&quot;&gt;MAZE project&lt;/a&gt; aimed for this, but it’s not mature enough and doesn’t cover CNNs, so I designed the model manually through trial and error.&lt;/p&gt;

&lt;p&gt;My biggest initial mistake was not setting up infrastructure to collect metrics for objective model evaluation.
I’d run data, review results, guess improvements, adjust, and repeat.
Without sufficient data, progress was slow and guesses often wrong. I realized I was wasting time.
Learning from this, I built infrastructure to collect metrics and systematically test approaches.&lt;/p&gt;

&lt;p&gt;Model design involves many questions: How many CNN layers?
What kernel size?
What learning rate?
Which modules to use?
Without a systematic approach, it’s easy to get lost in the ocean of permutations.
First, I tackled metric collection. I used tools like &lt;a href=&quot;https://grafana.com&quot;&gt;Grafana&lt;/a&gt; while I was wearing hat of a DevOps engineer or backend engineer, but for machine learning, &lt;a href=&quot;https://www.tensorflow.org/tensorboard&quot;&gt;TensorBoard&lt;/a&gt; is preferred.
Its UI is tailored for ML training and testing data, so I updated my code to log TensorBoard metrics.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/tensorboard.png&quot; alt=&quot;Screenshot of TensorBoard showing histogram of training gradients&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of TensorBoard showing histogram of training gradients&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Next, I needed a method to adjust hyperparameters systematically.
I developed a technique I call “hyperparameter gradient descent.”
Starting with a baseline model, I adjust one hyperparameter across a range, running limited epochs to compare results.
I select the best performer, update the baseline, and train on the full dataset.
Then, I analyze results, hypothesize improvements, and test new hyperparameters.
This iterative process resembles gradient descent but at the hyperparameter level.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/hyperparameter-gradient-descent.svg&quot; alt=&quot;Diagram showing the approach moving from a model design, trying out with different hyperparameters, pick the best one and move to next baseline design.&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Diagram showing the approach moving from a model design, trying out with different hyperparameters, pick the best one and move to next baseline design.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;For example, while training my models, I observed that they stop learning after some steps.
By examining the gradient histogram on TensorBoard, I realized that the gradient distributions were consistently zero after a certain point.
My theory was that high loss causes large gradients at the very beginning, which wipes out many neurons and weights, making them fall below the ReLU threshold (zero in this case) and preventing activation.
It almost felt like a surge of current flowing into a circuit board, frying the electronic components.
To address this problem, I experimented with different approaches using the current version of the model.
First, I tried &lt;a href=&quot;https://docs.pytorch.org/docs/stable/generated/torch.nn.utils.clip_grad_norm_.html&quot;&gt;gradient clipping&lt;/a&gt;. This approach did help mitigate the initial gradient explosion issue.
However, since gradient clipping requires accessing all gradient values, it significantly slows down training, making it less than ideal.
Next, I tuned the learning rate. With my infrastructure, I can easily test the same model structure with different learning rates and evaluate their performance.
This allowed me to quickly identify the optimal learning rate for the current model structure.
I’m now on version 5 of my baseline model.&lt;/p&gt;

&lt;p&gt;This approach has drawbacks.
During exploration, choosing the number of epochs for experiments is challenging.
Too few may miss potential benefits, while too many waste resources.
With my limited budget, I could only test a few options, making educated guesses based on small data windows.
With more resources, I recommend running at least one full epoch to uncover late improvements.
Additionally, keep models flexible—parameterize layers, learning rates, kernel sizes, and structures as arguments rather than hard-coding values to simplify tuning.&lt;/p&gt;

&lt;p&gt;Another issue with this approach is that previously determined parameters may not remain optimal after changing the model structure.
For example, the optimal learning rate from the previous structure might not suit the new one.
Therefore, you need to continuously ask questions and explore different directions to determine the best next move.
Even when you believe you’re heading in the right direction, this may not always be true.
It still requires significant trial and error.
Sometimes, I had to revert to an earlier version and proceed from there because the new approach performed worse than expected.&lt;/p&gt;

&lt;h2 id=&quot;using-cloud-services-like-modal-to-speed-up-experiments&quot;&gt;Using Cloud Services Like Modal to Speed Up Experiments&lt;/h2&gt;
&lt;p&gt;While hyperparameter gradient descent was helpful, training on my Nvidia RTX 4090 slowed as models grew larger.
My PC struggled, sometimes becoming unusable.
Testing multiple hyperparameters sequentially was too time-consuming.
I needed a cloud-based solution and found &lt;a href=&quot;https://modal.com&quot;&gt;Modal&lt;/a&gt;, a container-based platform for GPU computing.
As someone experienced with containers, I appreciated its approach.
This article isn’t sponsored, but I recommend Modal for machine learning workloads due to its ease of use.
Modal bills only for active container time and supports running multiple containers simultaneously, enabling parallel hyperparameter testing.&lt;/p&gt;

&lt;p&gt;Here’s an sample code how to use Modal to explore different learning rate with the same training function:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;modal&lt;/span&gt;


&lt;span class=&quot;n&quot;&gt;MINUTES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;HOURS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MINUTES&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;app_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;cakelens-v5.0&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;base_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_dockerfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;./docker/modal/Dockerfile&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;torch_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pip_install&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;volume&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Volume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cakelens-v5-volume&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create_if_missing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gpu&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;H100&quot;&lt;/span&gt;


&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;volume_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;volume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gpu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gpu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HOURS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;train_model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node_rank&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# train your model here
&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;local_entrypoint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;base_kwargs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;limit_training&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;epoches&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;train_model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;starmap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;base_kwargs&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;run_name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;5.0-lr-3e-03&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;learning_rate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;3e-03&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;base_kwargs&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;run_name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;5.0-lr-5e-03&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;learning_rate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;5e-03&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;base_kwargs&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;run_name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;5.0-lr-7e-03&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;learning_rate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;7e-03&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Using Modal, I realized computing power is essential for ML.
No matter how much theory you study, without resources to experiment, progress is limited.
More computing power allows testing more approaches at scale, but the capital requirement is steep.
The barrier to ML isn’t a PhD—it’s access to computing resources.&lt;/p&gt;

&lt;h2 id=&quot;maximizing-gpu-usage-is-harder-than-expected&quot;&gt;Maximizing GPU Usage Is Harder Than Expected&lt;/h2&gt;
&lt;p&gt;With Modal’s H100 GPUs, I expected fast training, but software challenges persisted.
Even with powerful hardware, GPU usage wasn’t always 100%, sometimes much lower.
Video decoding and data transfer from disk to GPU memory caused bottlenecks.
Decoding videos takes time, and transferring data from main memory to GPU memory via &lt;a href=&quot;https://docs.pytorch.org/docs/stable/notes/cuda.html&quot;&gt;CUDA streams&lt;/a&gt; (similar to &lt;a href=&quot;https://en.wikipedia.org/wiki/Coroutine&quot;&gt;coroutines&lt;/a&gt;) can idle the GPU if not optimized.
To reduce waste, I enabled PyTorch &lt;a href=&quot;https://docs.pytorch.org/tutorials/beginner/basics/data_tutorial.html&quot;&gt;DataLoader&lt;/a&gt; workers to load video data in parallel subprocesses.
Profiling showed the GPU still waited for memory transfers.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/profile-wait-memcpy.png&quot; alt=&quot;Screenshot of Chrome&apos;s profiler UI showing the forward pass waits for memory copying of the input data from the main memory to the GPU&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of Chrome&apos;s profiler UI showing the forward pass waits for memory copying of the input data from the main memory to the GPU&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;This reminded me of CPU pipeline concepts.
While the GPU waits for data, it could process the previous batch.
I implemented a PyTorch CUDA stream generator to load data to GPU memory while processing the prior batch.&lt;/p&gt;

&lt;p&gt;Here’s the generator code:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cuda_preload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;datasets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;typing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sequence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;worker_stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cuda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;typing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Generator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;preload_stream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cuda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;worker_stream&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;worker_stream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cuda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;default_stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_to_device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;src_x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;src_y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;torch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cuda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preload_stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;src_x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;non_blocking&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;src_y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;non_blocking&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;record_stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;worker_stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;record_stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;worker_stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;iterator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;datasets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;record_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;preload_dataset&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;previous&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_to_device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iterator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;record_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;load_dataset&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iterator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;worker_stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wait_stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preload_stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_to_device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;previous&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;previous&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;StopIteration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With that, you can then use it to wrap around the datasets in the training loop like this:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cuda_preload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dataloader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;logits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, loading data to the GPU and performing the forward pass happen simultaneously, which is much more efficient!&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/profile-async-memcpy.png&quot; alt=&quot;Screenshot of Chrome&apos;s profiler UI showing the forward pass runs in the same time as memory copying of the input data from the main memory to the GPU&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of Chrome&apos;s profiler UI showing the forward pass runs in the same time as memory copying of the input data from the main memory to the GPU&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;This improved GPU usage, but transfers were still slow.
I learned &lt;a href=&quot;https://docs.pytorch.org/tutorials/intermediate/pinmem_nonblock.html&quot;&gt;pinning DataLoader memory as non-pageable speeds up transfers by avoiding an extra memory copy step&lt;/a&gt;.
In most cases, you can simply set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pin_memory&lt;/code&gt; to True with your DataLoader to do it for you, like this:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;train_dataloader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DataLoader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;training_dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;batch_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;shuffle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;num_workers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num_workers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pin_memory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pin_memory_device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To summarize it up.
Key lessons for maximizing GPU usage:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Use DataLoader workers for parallel data loading in the background processes.&lt;/li&gt;
  &lt;li&gt;Pipeline data loading and processing in an async manner.&lt;/li&gt;
  &lt;li&gt;Pin memory for faster data transfers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;reducing-memory-usage&quot;&gt;Reducing Memory Usage&lt;/h2&gt;

&lt;p&gt;As models grew, memory usage spiked, especially during backpropagation.
I frequently encountered out-of-memory errors, even on &lt;a href=&quot;https://www.nvidia.com/en-us/data-center/h100/&quot;&gt;H100&lt;/a&gt; or &lt;a href=&quot;https://www.nvidia.com/en-us/data-center/a100/&quot;&gt;A100-80GB&lt;/a&gt; GPUs.
Without backpropagation, training would be faster and easier to distribute, and I’d love to eliminate it, but that’s an interesting research topic for another day.&lt;/p&gt;

&lt;p&gt;To reduce memory usage, my first attempt was to decrease the mini-batch size.
However, I didn’t want to lose the benefits of a larger batch size.
Therefore, I performed multiple forward passes, calculated the average loss from those passes, and then executed a single backpropagation step.&lt;/p&gt;

&lt;p&gt;However, that wasn’t enough.
I also adopted &lt;a href=&quot;https://docs.pytorch.org/tutorials/recipes/recipes/amp_recipe.html&quot;&gt;Automatic Mixed Precision (AMP) in PyTorch&lt;/a&gt;, using FP16 with scaled gradients to reduce memory usage.
Still, out-of-memory errors persisted.
Distributed training across multiple GPUs was an option, but it’s complex and error-prone.
While I know most mainstream LLM training is done distributively, there’s already too much for me to learn, and I don’t have the appetite for that today.
Therefore, I’ve decided to treat the H100’s 80GB as the memory size limit for this project.&lt;/p&gt;

&lt;p&gt;To further reduce memory usage, I adopted the &lt;a href=&quot;https://docs.pytorch.org/docs/stable/checkpoint.html&quot;&gt;checkpointing approach provided by PyTorch&lt;/a&gt;.
Essentially, it avoids saving gradients during the forward pass, storing only the output values and recomputing gradients during backpropagation.
In other words, it trades computation for memory.
While this approach is slower, it allows training to proceed with just one H100 instead of two.
By the way, the term “checkpoint” is confusing, as it’s often mistaken for saving the current model state 😅.&lt;/p&gt;

&lt;p&gt;With the above measures, I successfully managed to keep memory usage in check and train my models.&lt;/p&gt;

&lt;h2 id=&quot;tips-for-recognizing-ai-generated-videos-with-human-eyes&quot;&gt;Tips for Recognizing AI-Generated Videos with Human Eyes&lt;/h2&gt;

&lt;p&gt;While CakeLens helps identify AI-generated videos, learning to spot them manually is valuable.
This is especially useful for labeling data, even with an AI model.
Having reviewed thousands of AI-generated videos during data collection, I’d like to share some key tips.&lt;/p&gt;

&lt;h3 id=&quot;harry-potter-teleportation-in-the-bokeh&quot;&gt;Harry Potter Teleportation in the Bokeh&lt;/h3&gt;

&lt;p&gt;Many AI-generated videos feature people walking or objects moving in the background.
In real videos, the camera focuses on the subject, leaving the background blurry—a phenomenon called &lt;a href=&quot;https://en.wikipedia.org/wiki/Bokeh&quot;&gt;bokeh&lt;/a&gt;.
By closely examining moving objects in the bokeh, you’ll notice unnatural movements that don’t align with natural physics.
Here’s an example of a person appears from no where in the background:&lt;/p&gt;

&lt;div class=&quot;center mb2&quot;&gt;
    &lt;video style=&quot;width: 480px; margin: 0 auto&quot; crossorigin=&quot;anonymous&quot; width=&quot;776&quot; height=&quot;438&quot; autoplay=&quot;&quot; playsinline=&quot;&quot; src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/apparition-00.mp4&quot; muted=&quot;&quot; loop=&quot;&quot; controls=&quot;&quot;&gt;&lt;/video&gt;
    &lt;div class=&quot;small text-center&quot;&gt;
        &lt;a href=&quot;https://x.com/omooretweets/status/1925972036775485945&quot;&gt;AI-gen video with Veo 3 posted by @omooretweets on X&lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Yet another example of a person disappeared.&lt;/p&gt;
&lt;div class=&quot;center mb2&quot;&gt;
    &lt;video style=&quot;width: 480px; margin: 0 auto&quot; crossorigin=&quot;anonymous&quot; width=&quot;776&quot; height=&quot;438&quot; autoplay=&quot;&quot; playsinline=&quot;&quot; src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/apparition-01.mp4&quot; muted=&quot;&quot; loop=&quot;&quot; controls=&quot;&quot;&gt;&lt;/video&gt;
    &lt;div class=&quot;small text-center&quot;&gt;
        &lt;a href=&quot;https://x.com/omooretweets/status/1925972036775485945&quot;&gt;AI-gen video with Veo 3 posted by @TheoMediaAI on X&lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Example of a human body twisted unnaturely in the background:&lt;/p&gt;

&lt;div class=&quot;center mb2&quot;&gt;
    &lt;video style=&quot;width: 480px; margin: 0 auto&quot; crossorigin=&quot;anonymous&quot; width=&quot;776&quot; height=&quot;438&quot; autoplay=&quot;&quot; playsinline=&quot;&quot; src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/apparition-02.mp4&quot; muted=&quot;&quot; loop=&quot;&quot; controls=&quot;&quot;&gt;&lt;/video&gt;
    &lt;div class=&quot;small text-center&quot;&gt;
        &lt;a href=&quot;https://x.com/PJaccetturo/status/1919071287831597481&quot;&gt;AI-gen video with Veo 3 posted by @PJaccetturo on X&lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Since these movements often feel unnatural, and sometimes the person or object even disappears, resembling the teleportation effects in Harry Potter films—hence the name.&lt;/p&gt;

&lt;div style=&quot;display: flex; justify-content: center; margin-bottom: 1.5em;&quot;&gt;
  &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/0KaDBNUjhr8?si=d3m9QGwKdeRnOFlk&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;To identify AI-generated videos, look for unusual movements in the blurry background.
This phenomenon occurs because mainstream video and image generation models rely on probabilities to determine what is most likely to appear in a given context.
These models don’t fully understand objects; they generate what seems plausible.
The fuzziness of bokeh or the limited pixels representing an object introduces great uncertainty, causing the model to produce inconsistent or unrealistic background movements.
I didn’t observe this phenomenon in all AI-generated videos.
However, when present, it’s a strong indicator that a video is AI-generated.&lt;/p&gt;

&lt;p&gt;For most video-generating models to overcome this issue, they may need to change their generation approach.
They likely need to enable the model to maintain a clear and continuous concept of scenes and actors within them, rather than guessing what should appear.
At that point, it would be harder for us to detect flaws like these.
This is yet another interesting topic to consider.&lt;/p&gt;

&lt;h3 id=&quot;ai-fonts&quot;&gt;AI Fonts&lt;/h3&gt;

&lt;p&gt;Another telltale sign of AI-generated content is the text.
As mentioned earlier, diffusion-based image and video generation models prioritize visual likelihood over meaning.
They don’t comprehend text, often producing gibberish that mimics the appearance of text—mixed characters resembling Chinese, Japanese, or other languages without meaning.
I call this “AI fonts.”&lt;/p&gt;

&lt;p&gt;Here’s an example of AI fonts from OpenAI’s Sora:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/ai-font-00.jpg&quot; alt=&quot;Screenshot of AI-gen video with Sora posted by @gdb on X&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of &lt;a href=&quot;https://x.com/gdb/status/1758193811489243408&quot;&gt;AI-gen video with Sora posted by @gdb on X&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;The kangaroo video mentioned earlier also displays AI fonts on the ticket:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/kangaroo-ticket.jpg&quot; alt=&quot;&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;&lt;a href=&quot;https://x.com/AutismCapital/status/1927866536137752772&quot;&gt;Screenshot of the Kangaroo AI-gen video zoomed to the ticket showing AI-font text posted by @AutismCapital on X&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;The text looks like text but not really.&lt;/p&gt;

&lt;p&gt;Not all AI models struggle with text.
Some, like OpenAI’s newer image-generation models, produce coherent text.
So, while AI fonts can indicate AI-generated content, meaningful text doesn’t always rule it out.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/o4-gen.webp&quot; alt=&quot;https://openai.com/index/introducing-4o-image-generation/&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;&lt;a href=&quot;https://openai.com/index/introducing-4o-image-generation/&quot;&gt;AI-gen image with OpenAI&apos;s new 4o model&lt;/a&gt; featuring a young lady writing text on a whiteboard with clear meaningful text on it&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;h3 id=&quot;incorrect-physical-behavior&quot;&gt;Incorrect Physical Behavior&lt;/h3&gt;

&lt;p&gt;AI-generated content sometimes exhibits incorrect physical behavior.
Since diffusion models rely on probability, they may lack sufficient data to accurately predict physical movements.
For example, in a Veo3-generated earthquake video, objects appear unnaturally light.&lt;/p&gt;

&lt;div class=&quot;center mb2&quot;&gt;
    &lt;video style=&quot;width: 480px; margin: 0 auto&quot; crossorigin=&quot;anonymous&quot; width=&quot;776&quot; height=&quot;438&quot; autoplay=&quot;&quot; playsinline=&quot;&quot; src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/ai-earthquake.mp4&quot; muted=&quot;&quot; loop=&quot;&quot; controls=&quot;&quot;&gt;&lt;/video&gt;
    &lt;div class=&quot;small text-center&quot;&gt;
        &lt;a href=&quot;https://x.com/t_itamiya/status/1927888648965406948&quot;&gt;AI-gen video with Veo 3 posted by @t_itamiya on X&lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Yet another great example is an iPhone unboxing video:&lt;/p&gt;

&lt;div class=&quot;center mb2&quot;&gt;
    &lt;video style=&quot;width: 480px; margin: 0 auto&quot; crossorigin=&quot;anonymous&quot; width=&quot;776&quot; height=&quot;438&quot; autoplay=&quot;&quot; playsinline=&quot;&quot; src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/iphone-unboxing.mp4&quot; muted=&quot;&quot; loop=&quot;&quot; controls=&quot;&quot;&gt;&lt;/video&gt;
    &lt;div class=&quot;small text-center&quot;&gt;
        &lt;a href=&quot;https://x.com/mattshumer_/status/1925017573478793563&quot;&gt;AI-gen video with Veo 3 posted by @mattshumer_ on X&lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Well, if you ever unbox an iPhone, you certainly know that’s not how you open the box.&lt;/p&gt;

&lt;h3 id=&quot;look-for-inconsistencies&quot;&gt;Look for inconsistencies&lt;/h3&gt;

&lt;p&gt;Another tip for spotting AI-generated videos is to look for inconsistencies in the video.
For example, some AI-generated videos may show a woman wearing blue nail polish.
After certain movements, the nail might go out of view and then reappear, but it could change color, such as to red.
The iPhone unboxing video in the previous section also exhibits inconsistencies.
The man appears to be about to peel the protective film from the iPhone, but suddenly, the protective film’s handle turns into a piece of paper.&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next?&lt;/h2&gt;

&lt;p&gt;With CakeLens now publicly available, what’s next?
First, I want to improve its accuracy.
As users submit more videos and additional data, I hope this will enhance the model’s performance.
However, I’ve already spent a few thousand dollars on this project (hoping my wife doesn’t get upset about the bill 😅).&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-06-03-i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/modal-cost.png&quot; alt=&quot;Screenshot of Modal&apos;s billing dashboard showing the total cost as $2.1K&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of Modal&apos;s billing dashboard showing the total cost as $2.1K&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;That’s a bit expensive for a pet product, but I see it as tuition for learning machine learning.
Moving forward, I’ll explore options for lower-cost local training, even if it’s slower.&lt;/p&gt;

&lt;p&gt;Here are some ideas I’d like to pursue if time and budget permits:&lt;/p&gt;

&lt;h3 id=&quot;trying-different-approaches&quot;&gt;Trying Different Approaches&lt;/h3&gt;

&lt;p&gt;My initial model detects subtle patterns in AI-generated video sequences.
I’m curious about analyzing videos in the frequency domain.
While the model could theoretically learn such transformations, it requires significant computing resources and complexity.
Preprocessing images in the frequency domain might allow a simpler model to achieve similar results with fewer resources.&lt;/p&gt;

&lt;h3 id=&quot;bigger-model&quot;&gt;Bigger model&lt;/h3&gt;

&lt;p&gt;I’ve always wanted to explore the possibility of building a larger model, such as one with deeper CNN layers.
However, my current model design is limited by computational resources.
While I’m not sure if this is the rabbit hole I want to dive into right now, I’ll likely need to explore distributed training for a much larger model at some point.
If I can get multiple instances running at a low cost, it might be worth the time to pursue.&lt;/p&gt;

&lt;h3 id=&quot;analyzing-audio&quot;&gt;Analyzing Audio&lt;/h3&gt;

&lt;p&gt;Currently, CakeLens doesn’t process audio.
Previously, missing audio or audio that sounded off was a strong indicator of AI-generated videos.
However, Google’s Veo3 now generates audio synced with visuals, making detection more challenging.
Still, I believe analyzing both video and audio could be valuable.&lt;/p&gt;

&lt;p&gt;Even though Veo3 can generate videos with synced audio, real-world sound has distinct characteristics influenced by factors such as the speaker’s skull shape (implied by their face), the surrounding environment, the microphone, and so on.
If a connection can be made between visual cues and audio, a well-designed model with sufficient data might still be able to determine how the audio should sound.&lt;/p&gt;

&lt;p&gt;However, this is challenging because videos may include music or unrelated soundtracks, or they may be heavily post-processed.
When training the model to recognize AI-generated content using both image and audio, it will need to ignore the soundtrack if it’s unrelated to the visuals&lt;/p&gt;

&lt;h3 id=&quot;understanding-how-it-works&quot;&gt;Understanding How It Works&lt;/h3&gt;

&lt;p&gt;I assume my model detects AI-generated content by identifying subtle details—such as camera focus, distortion, or frequency domain patterns—that the human eye misses.
However, I don’t fully understand what drives its decisions.
To improve the model, I need tools to visualize what triggers a positive detection.
This would clarify what works and guide next-generation designs.&lt;/p&gt;

&lt;p&gt;Without this, it’s hard to pinpoint why the model flags content with confidence.
Could it be detecting hidden watermarks embedded by AI generation models?
Or is it associating specific logos, like TikTok’s, with non-AI content?
Or does it flag content simply because certain objects appear more frequently in AI-generated videos?
This is another intriguing area to explore.&lt;/p&gt;

&lt;h3 id=&quot;expanding-detection-to-still-images-and-audio&quot;&gt;Expanding Detection to Still Images and Audio&lt;/h3&gt;

&lt;p&gt;I focused on videos first because they contain more information, making detection easier.
I’m considering training models for AI-generated images and audio.
If videos have detectable patterns, images and audio likely do too.
Solving this for images and audio would also enhance video detection.&lt;/p&gt;

&lt;h3 id=&quot;using-synthetic--augmented-data&quot;&gt;Using Synthetic &amp;amp; Augmented Data&lt;/h3&gt;

&lt;p&gt;So far, I’ve relied on internet-sourced data for training, as generating AI videos is costly.
If budget weren’t an issue, I’d explore synthetic data.
For example, I could take a real video, extract a few key frames, feed them into a video generation model to predict the next few seconds, and compare the real and AI-generated videos.
This would provide ground truth data versus the generated video, potentially helping the model learn AI-specific patterns.&lt;/p&gt;

&lt;p&gt;Another idea is to extend the video data by applying slight transformations, such as cropping or slightly altering the color.
I could also re-encode the video multiple times. Videos on the internet undergo various transformations by uploaders, and repeated re-encoding may cause the loss of critical details needed to determine if a video is AI-generated.
Simulating these changes could help the model detect patterns more robustly.&lt;/p&gt;

&lt;h3 id=&quot;identifying-the-source-model&quot;&gt;Identifying the Source Model&lt;/h3&gt;

&lt;p&gt;When labeling AI-generated videos, I include the generating model if it’s mentioned in the post.
I then built a model to predict which generating model was used.
However, the model is currently too inaccurate to be useful.
AI-generated videos are already scarce, and data for specific models is even sparser.
Without significantly more samples, accurately identifying the source model remains challenging.
A better approach might be to generate videos myself, but this would require substantial funding for video generation.
This is unlikely to happen in the short term unless I have funds available to invest.&lt;/p&gt;

&lt;h3 id=&quot;implement-flagging&quot;&gt;Implement Flagging&lt;/h3&gt;

&lt;p&gt;As mentioned previously, the browser extension should ideally flag AI-generated content automatically by downloading a list of known AI-generated content.
This feature is not yet implemented.
I may find time to build it at some point.&lt;/p&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;That’s it!
I hope you enjoyed this article and learned something new.
Building CakeLens was an incredible learning experience, and I’m glad I spent a month on this detour in my journey to build AGI.
The knowledge gained was worth it.
Machine learning simplifies many tasks, and innovations in generative AI have created new demands, such as detecting whether a video, image, or audio file is AI-generated.
It’s an exciting time to explore these possibilities!
Now that CakeLens is live, give it a try!
If you don’t want to install the Chrome extension but want to analyze a video, at &lt;a href=&quot;https://x.com/CakeLens&quot;&gt;@CakeLens&lt;/a&gt; on X, and I’ll manually process it and reply the results below.&lt;/p&gt;

&lt;p&gt;If you have feedback or questions about this project, leave a comment below or email me.
Thanks for reading!&lt;/p&gt;
</description>
        <pubDate>Tue, 03 Jun 2025 07:00:00 +0000</pubDate>
        <link>https://fangpenlin.com/posts/2025/06/03/i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/</link>
        <guid isPermaLink="true">https://fangpenlin.com/posts/2025/06/03/i-built-ai-gen-video-detection-model-and-browser-extension-in-a-month/</guid>
      </item>
    
      <item>
        <title>Nvidia GPU on bare metal NixOS Kubernetes cluster explained</title>
        <description>&lt;style type=&quot;text/css&quot;&gt;
  figure {
    text-align: center;
    margin: 0 auto;
  }
  figcaption, figcaption p {
    color: grey;
    font-size: 0.9em;
  }
  figure img {
    max-width: 100%;
  }
&lt;/style&gt;

&lt;p&gt;Since the last time I published &lt;a href=&quot;/posts/2025/02/18/maze-my-ai-models-are-finally-evolving/&quot;&gt;the second MAZE (Massive Argumented Zonal Environments) article&lt;/a&gt;, I realized that the framework is getting more mature, but I need a solution to run it on a large scale.
In the past, I built &lt;a href=&quot;/posts/2024/01/14/high-speed-usb4-mesh-network/&quot;&gt;a bare metal Kubernetes cluster on top of a three-node mini PC interconnected with USB4&lt;/a&gt;.
I have a retired workstation PC with NVIDIA GeForce RTX 2080 Ti.
I wondered why not put this PC into the three-node Kubernetes cluster and configure Kubernetes to make CUDA work.
Once I have one configured, extending the computing power capacity with more GPUs will be easier.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-03-01-nvidia-gpu-on-bare-metal-nixos-k8s-explained/btop.png&quot; alt=&quot;Screenshot of btop of my four-node Kubernetes cluster, with three mini PCs connected via USB4 and the newly added retired workstation sporting an Nvidia GeForce RTX 2080 Ti.&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of &lt;a href=&quot;https://github.com/aristocratos/btop&quot;&gt;btop&lt;/a&gt; of my four-node Kubernetes cluster, with three mini PCs connected via USB4 and the newly added retired workstation sporting an Nvidia GeForce RTX 2080 Ti.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;With that in mind, I took a week on this side quest.
It turns out it’s way harder than I expected. I have learned a lot from digging this rabbit hole, and I encountered other holes while digging this one.
Getting the &lt;a href=&quot;https://github.com/NVIDIA/k8s-device-plugin&quot;&gt;Nvidia device plugin&lt;/a&gt; up and running in Kubernetes is hard.
Not to mention, running on top of &lt;a href=&quot;https://nixos.org&quot;&gt;NixOS&lt;/a&gt; makes it even more challenging.
Regardless, I finally managed to get it to work! Seeing it running for the first time was a very joyful moment.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-03-01-nvidia-gpu-on-bare-metal-nixos-k8s-explained/cuda-on-k8s.png&quot; alt=&quot;Screenshot of the Kubernetes dashboard shows the logs of a MAZE pod  running an experiment on a CUDA device&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of the &lt;a href=&quot;https://github.com/kubernetes/dashboard&quot;&gt;Kubernetes dashboard&lt;/a&gt; shows the logs of a MAZE pod  running an experiment on a CUDA device&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;An article like this could be helpful to someone as more software engineers are getting into machine learning.
Running all of it on the cloud is a costly option.
There are also privacy concerns for personal use, so Nvidia GPU on a local bare-metal Kubernetes cluster is still a very tempting option.
Here, I would like to share my experience setting up a bare metal Kubernetes cluster with the Nvidia GPU CDI plugin enabled in NixOS.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;chain-of-rabbit-holes&quot;&gt;Chain of rabbit holes&lt;/h2&gt;

&lt;p&gt;I want to make my Kubernetes support Nvidia GPU, not because I like it.
I just need to find a way to scale MAZE to continue my research more efficiently.
In software engineering, when you encounter a problem that requires dedicated time and effort to find the solution or root cause, I call it digging a rabbit hole.
It’s a reference from &lt;a href=&quot;https://en.wikipedia.org/wiki/Alice&apos;s_Adventures_in_Wonderland&quot;&gt;Alice’s Adventures in Wonderland&lt;/a&gt;’s &lt;a href=&quot;https://en.wikipedia.org/wiki/Down_the_rabbit_hole&quot;&gt;Down the rabbit hole&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-03-01-nvidia-gpu-on-bare-metal-nixos-k8s-explained/rabbit.jpg&quot; alt=&quot;Rabbit from Alice&apos;s Adventures in Wonderland, by John Tenniel&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Rabbit from Alice&apos;s Adventures in Wonderland, by &lt;a href=&quot;https://en.wikipedia.org/wiki/John_Tenniel&quot;&gt;John Tenniel&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Interestingly, during the process of digging a rabbit hole, you would often find yourself needing to dig another due to a new obstacle.
I call it the chain of rabbit holes.
For example, I just heard in the &lt;a href=&quot;https://youtu.be/OxP55dZjqZs?si=L034C0RzrwxB2mIR&amp;amp;t=4828&quot;&gt;All-in podcast they mentioned&lt;/a&gt; that for Elon Musk to build Gork in a short time frame, they had to&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Find a place for putting one hundred GPU&lt;/li&gt;
  &lt;li&gt;Need power, so buy generators&lt;/li&gt;
  &lt;li&gt;Need cooling, so buy portable liquid cooling&lt;/li&gt;
  &lt;li&gt;Realize the need to smooth power usage, so install the Tesla powerpack&lt;/li&gt;
  &lt;li&gt;…&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a great example of a chain of rabbit holes.
The ability to dig rabbit holes and solve hard problems down the chain boils down to two words:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Execution power&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reason for Deepseek’s recent successful story also boils down to execution power.
They recently announced their &lt;a href=&quot;https://github.com/deepseek-ai/3FS&quot;&gt;customized distributed file system to optimize AI training&lt;/a&gt;.
Their ability to dig rabbit holes like this or the previous &lt;a href=&quot;https://docs.nvidia.com/cuda/parallel-thread-execution/&quot;&gt;PTX (Parallel Thread Execution)&lt;/a&gt; optimization shows great execution power.&lt;/p&gt;

&lt;p&gt;It may sound obvious, but there are many interesting aspects to consider while digging rabbit holes.
Of course, with constraints, one cannot dig all the rabbit holes, and it would take forever to finish the project.
Also, not all rabbit holes are worth digging.
Since this is an interesting topic but rarely mentioned, I would bring up a bit of my thought process on my decision going down each rabbit hole.
Before that, let’s see the chain of rabbit holes I was digging in the past week:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-03-01-nvidia-gpu-on-bare-metal-nixos-k8s-explained/chain-of-rabbit-hole.png&quot; alt=&quot;Diagram illustrating the chain of rabbit holes I encountered while setting up Nvidia GPU support on my Kubernetes cluster.&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Diagram illustrating the chain of rabbit holes I encountered while setting up Nvidia GPU support on my Kubernetes cluster.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;h2 id=&quot;running-kubernetes-with-nixos&quot;&gt;Running Kubernetes with NixOS&lt;/h2&gt;

&lt;p&gt;It is not a new rabbit hole I dug.
I have already figured out how to deploy Kubernetes on NixOS as needed while building the three-node cluster.
But since I didn’t mention details in the previous article, bringing up the details here makes sense.&lt;/p&gt;

&lt;p&gt;I love NixOS and &lt;a href=&quot;https://github.com/NixOS/nixpkgs/&quot;&gt;Nixpkgs&lt;/a&gt;.
The ability to configure a whole operation system with just configuration files, making it reproducible, is simply amazing. Here’s an example of a hello world NixOS config file:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;lib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}:&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;imports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Include the results of the hardware scan.&lt;/span&gt;
      &lt;span class=&quot;sx&quot;&gt;./hardware-configuration.nix&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# Use the systemd-boot EFI boot loader.&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;boot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;systemd-boot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;boot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;efi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;canTouchEfiVariables&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;stateVersion&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;24.11&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;While terrific, it doesn’t come without drawbacks, particularly considering deploying it to multiple machines.
The first problem is that while most configurations would be the same for a machine, many things, such as the hostname, would still be different.
One cannot write a simple configuration and apply it everywhere on all the machines.
NixOS generates configuration files in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc&lt;/code&gt; folder as read-only files with symbolic links to the actual config files in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/nix/store&lt;/code&gt; folder.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-03-01-nvidia-gpu-on-bare-metal-nixos-k8s-explained/etc-links.png&quot; alt=&quot;Screenshot of the output from listing /etc folder shows many symbolic links&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot of the output from listing /etc folder shows many symbolic links&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;I found a tool called &lt;a href=&quot;https://github.com/serokell/deploy-rs&quot;&gt;deploy-rs&lt;/a&gt; for deploying to different machines with slightly different configurations.
It allows you to define configurations for each machine with customization and deploy them easily via SSH.
Usually, this is good enough with simple stuff like hostnames or Linux configurations.
However, running a full-blown Kubernetes cluster is yet another story. Just &lt;a href=&quot;https://en.wikipedia.org/wiki/Public_key_infrastructure&quot;&gt;PKI (Public key infrastructure)&lt;/a&gt; certification generation and deployment are troublesome.&lt;/p&gt;

&lt;p&gt;Generating Kubernetes certificates also implies creating secret keys and deploying them into each node.
NixOS is very good at making a predictable environment with immutable packages and configuration files, but dealing with centralized secret values distribution is not ideal.
I certainly don’t want to commit my secret key to Git history along with the NixOS configuration file.
Therefore, we need another approach to deploy dynamically generated configurations and secret keys to all those machines.&lt;/p&gt;

&lt;p&gt;I am pretty familiar with &lt;a href=&quot;https://docs.ansible.com&quot;&gt;Ansible&lt;/a&gt; because I used it a lot in the past and even contributed to &lt;a href=&quot;https://github.com/ansible/ansible/pull/8323&quot;&gt;the omit filter&lt;/a&gt; many years ago.
So when I encountered this problem, Ansible came first into my mind for deploying Kubernetes configuration files and generating the PKI secrets plus certificates.
I don’t plan to cover too much detail, but here’s what I do.
I replace the Kubernetes services config in the NixOS configuration to make them read from a local file as the arguments like this:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;lib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;systemd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;kubelet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;serviceConfig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;Restart&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;mkForce&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;always&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;RestartSec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;mkForce&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;20s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;EnvironmentFile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/var/lib/k8s/kubelet.env&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;ExecStart&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;mkForce&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;bash&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/bash -c &quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;kubernetes&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/kubelet $_KUBE_ARGS&quot;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# ... other k8s services&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then, I have an Ansible playbook that will SSH into all machines and generate the corresponding arg and config files, along with PKI certificates and secret values.
Here’s an example of the Ansible Jinja2 template for generating the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/k8s/kubelet.env&lt;/code&gt; shown in the above Nix example:&lt;/p&gt;

&lt;div class=&quot;language-jinja highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ansible_managed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;| &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;

_KUBE_ARGS=&lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;--root-dir=/var/lib/k8s/kubelet&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;--config=/var/lib/k8s/kubelet/config.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;--kubeconfig=/var/lib/k8s/pki/kubelet.conf&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;--image-credential-provider-config=/var/lib/k8s/kubelet/image-credential-provider.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;--image-credential-provider-bin-dir=/run/current-system/sw/bin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;--runtime-cgroups=/system.slice/containerd.service&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;--node-ip={}&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;nebula1_node_ip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;| &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ansible&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;utils.ipaddr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;address&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;-v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;kubelet_log_level&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;| &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;| &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;quote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;--node-labels={}&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;,&apos;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;node_labels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))]&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;node_labels&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;defined&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[])&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;| &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;| &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;quote&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You may ask where you put the secret value.
I use &lt;a href=&quot;https://github.com/getsops/sops&quot;&gt;Sops&lt;/a&gt; and its &lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/community/sops/index.html&quot;&gt;Ansible plugin&lt;/a&gt;.
All the values are encrypted using a GPG key.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deploy k8s cluster&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;all&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;pre_tasks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;import_tasks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;tasks/sops.yaml&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sops&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;always&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  
  &lt;span class=&quot;na&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Do something with the secret value&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ansible.builtin.debug&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;secrets.my-value&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By combining NixOS, Ansible, and Sops, I can easily deploy to as many bare metal machines as I want as a Kubernetes cluster.
I even built &lt;a href=&quot;https://nixos.wiki/wiki/Creating_a_NixOS_live_CD&quot;&gt;a NixOS live CD environment for a USB thumb&lt;/a&gt; drive to help speed up the bootstrapping process.
But this article already has too much content, so I will leave it for text time.&lt;/p&gt;

&lt;h2 id=&quot;kubernetes-with-nvidia-gpu-cdi-plugin-explained&quot;&gt;Kubernetes with Nvidia GPU CDI plugin explained&lt;/h2&gt;

&lt;p&gt;Running a pod on Kubernetes with Nvidia GPU exposes sounds straightforward.
How hard could it be? But when you look closer, it immediately gives you a headache.
Just the sheer number of terms may overwhelm some people.
Be it CRI, CDI, nvidia-container-toolkit, libnvidia-container, and all the confusing terms at first glance.&lt;/p&gt;

&lt;p&gt;Although I have plenty of experience in Kubernetes and &lt;a href=&quot;https://beanhub.io/blog/2024/04/23/how-beanhub-works-part1-sandboxing/&quot;&gt;container&lt;/a&gt; &lt;a href=&quot;https://beanhub.io/blog/2024/06/26/how-beanhub-works-part2-layer-based-git-repos/&quot;&gt;technologies&lt;/a&gt;, it also took me a while to fully understand those terms and how they are interconnected.
Let’s first see the big picture of how everything works together, and then we will explore the details of how each component works together.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-03-01-nvidia-gpu-on-bare-metal-nixos-k8s-explained/nvidia-k8s-architecture.svg&quot; alt=&quot;Schematic overview of the Nvidia GPU integration architecture within Kubernetes on NixOS, showcasing the interplay between Container Runtime Interface (CRI), Container Device Interface (CDI), nvidia-container-toolkit, and the k8s-device-plugin.&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Schematic overview of the Nvidia GPU integration architecture within Kubernetes on NixOS, showcasing the interplay between Container Runtime Interface (CRI), Container Device Interface (CDI), nvidia-container-toolkit, and the k8s-device-plugin.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;The official &lt;a href=&quot;https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/arch-overview.html&quot;&gt;document from Nvidia about the architecture&lt;/a&gt; will also help you understand.&lt;/p&gt;

&lt;h3 id=&quot;container-runtime-interface&quot;&gt;Container Runtime Interface&lt;/h3&gt;

&lt;p&gt;As you are familiar with Kubernetes, there are so many CXI terms, such as &lt;a href=&quot;https://github.com/containernetworking/cni&quot;&gt;CNI (Container Network Interface)&lt;/a&gt; or &lt;a href=&quot;https://github.com/container-storage-interface/spec&quot;&gt;CSI (Container Storage Interface)&lt;/a&gt;; they are all well-defined interfaces that open up the implementation details of fundamental Kubernetes functionalities to third parties.
For instance, CNI is for networking, and CSI is for data storage.&lt;/p&gt;

&lt;p&gt;Likewise, CRI stands for &lt;a href=&quot;https://kubernetes.io/docs/concepts/architecture/cri/&quot;&gt;Container Runtime Interface&lt;/a&gt;, the plugin interface for adopting different container management systems.
The most well-known ones are &lt;a href=&quot;https://containerd.io&quot;&gt;Containerd&lt;/a&gt; and &lt;a href=&quot;https://cri-o.io&quot;&gt;CRI-O&lt;/a&gt;.
With NixOS, you can change its settings like this to make it support Nvidia runtime:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;virtualisation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;containerd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;settings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;plugins&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;io.containerd.grpc.v1.cri&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;enable_cdi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;cdi_spec_dirs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/etc/cdi&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/var/run/cdi&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;containerd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nv&quot;&gt;default_runtime_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;nvidia&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;nv&quot;&gt;runtimes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;nvidia&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;nv&quot;&gt;privileged_without_host_devices&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;nv&quot;&gt;runtime_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;io.containerd.runc.v2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;nv&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nv&quot;&gt;BinaryName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;nvidia-container-toolkit&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/nvidia-container-runtime&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;nvidia-container-toolkit&quot;&gt;nvidia-container-toolkit&lt;/h3&gt;

&lt;p&gt;We just mentioned CRI above. One of the jobs of &lt;a href=&quot;https://github.com/NVIDIA/nvidia-container-toolkit&quot;&gt;nvidia-container-toolkit&lt;/a&gt; is facilitating the CRI interface between Kubernetes’ CRI and the container runtime.
What is a container runtime?
Usually, it’s a runtime provided as a command line tool to launch an isolated container process by creating new &lt;a href=&quot;https://en.wikipedia.org/wiki/Linux_namespaces&quot;&gt;Linux namespaces&lt;/a&gt; and mounting host paths into the container.
The most commonly used ones are &lt;a href=&quot;https://github.com/opencontainers/runc&quot;&gt;runc&lt;/a&gt; and &lt;a href=&quot;https://github.com/containers/crun&quot;&gt;crun&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The nvidia-container-toolkit ship its own container runtime, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvidia-container-runtime&lt;/code&gt; executable.
It may sound confusing. Why did I say &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvidia-container-runtime&lt;/code&gt; is a container runtime but also facilitates the CRI interface between Kubernetes and the container runtime?
Well, the thing is, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvidia-container-runtime&lt;/code&gt; doesn’t implement the full-blown container runtime.&lt;/p&gt;

&lt;p&gt;It’s a thin layer on top of other lower-level runtime implementations such as runc or crun.
When Containerd or CRI-O invokes Nvidia’s CRI, the OCI containers’ spect will be modified to expose the needed Nvidia drivers, devices, and executables like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvidia-smi&lt;/code&gt; into the container.
Once the spec file is modified, it will call the underlying crun or runc to do the actual Linux namespace management and all the other container good stuff.
So, it’s like a proxy (or some may prefer to call it a shim) between the CRI and the runtime, &lt;a href=&quot;https://github.com/NVIDIA/nvidia-container-toolkit/blob/bc9ec77fdde552949022cbaf74c8b56e67702125/internal/runtime/runtime_factory.go#L44-L60&quot;&gt;performing a few modifications&lt;/a&gt; to the traffic passing through.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-03-01-nvidia-gpu-on-bare-metal-nixos-k8s-explained/nvidia-container-runtime-proxy.svg&quot; alt=&quot;Screenshot shows Containerd passes the original OCI spec file config.json to nvidia-container-runtime, and then nvidia-container-runtime modifies the config.json file and passes it along to runc&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;Screenshot shows Containerd passes the original OCI spec file config.json to nvidia-container-runtime, and then nvidia-container-runtime modifies the config.json file and passes it along to runc&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Other than the container runtime, the nvidia-container-toolkit project also provides utility tools for generating various configurations.
Such as this one for generating container runtime configuration for Containerd:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nvidia-ctk runtime configure &lt;span class=&quot;nt&quot;&gt;--runtime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;containerd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;libnvidia-container&quot;&gt;libnvidia-container&lt;/h3&gt;

&lt;p&gt;There are some confusing parts about this repository.
While its name says &lt;a href=&quot;https://github.com/NVIDIA/libnvidia-container&quot;&gt;libnvidia-container&lt;/a&gt;, they don’t use it as a library like all other projects with lib prefixes would usually do; at last, from the perspective of the Kubernetes plugin.
Instead, it provides an executable, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvidia-container-cli&lt;/code&gt;. The main purpose of this executable is to collect and provide information about Nvidia drivers, devices, and executables to be injected into the container environment.
Usually &lt;a href=&quot;https://github.com/NVIDIA/nvidia-container-toolkit/blob/bc9ec77fdde552949022cbaf74c8b56e67702125/cmd/nvidia-container-runtime-hook/main.go#L91C2-L156&quot;&gt;invoked directly&lt;/a&gt; from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvidia-container-runtime&lt;/code&gt;’s hook command.
I guess they need to use the same logic somewhere in Nvidia’s container-relative projects, and that might be why they need to make it a library.&lt;/p&gt;

&lt;p&gt;This command-line tool is used when using Nvidia as container runtime directly without CDI support.
Otherwise, it appears that if we take the CDI route, this command-line tool is not used at all.&lt;/p&gt;

&lt;h3 id=&quot;container-device-interface&quot;&gt;Container Device Interface&lt;/h3&gt;

&lt;p&gt;Similar to CRI, it’s also a well-defined plugin for Kubernetes.
Instead of providing container runtime functionalities, it offers particular device resources to expose to the container environment.
With Nvidia’s &lt;a href=&quot;https://github.com/cncf-tags/container-device-interface&quot;&gt;CDI (Container Device Interface)&lt;/a&gt; implementation, we can then expose GPU devices as resources in the container.&lt;/p&gt;

&lt;p&gt;In addition to Kubernetes, a container CLI tool like Podman can also use proper CDI configuration to have an Nvidia GPU inside the container.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ podman run --device nvidia.com/gpu=all alpine nvidia-smi
Sat Mar  1 23:51:00 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 565.77                 Driver Version: 565.77         CUDA Version: 12.7     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 2080 Ti     Off |   00000000:0A:00.0  On |                  N/A |
| 46%   57C    P2            103W /  260W |    3998MiB /  11264MiB |     33%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+

+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI        PID   Type   Process name                              GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|  No running processes found                                                             |
+-----------------------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--device&lt;/code&gt; argument with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvidia.com/gpu=all&lt;/code&gt; value tells podman to look for the resource (device) type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvidia.com/gpu&lt;/code&gt;, and we want all of it exposed inside the container.&lt;/p&gt;

&lt;h3 id=&quot;k8s-device-plugin&quot;&gt;k8s-device-plugin&lt;/h3&gt;

&lt;p&gt;With nvidia-container-toolkit and libnvidia-container, now it’s possible to run a container with Nvidia GPU exposed inside a container, but then we still need a way to let Kubernetes know there’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvidia.com/gpu&lt;/code&gt; such resource available for pods to use, and we need to know which nodes have them, how many are available, how many are in use, and what types.
The &lt;a href=&quot;https://github.com/NVIDIA/k8s-device-plugin&quot;&gt;k8s-device-plugin&lt;/a&gt; is here to solve those problems.&lt;/p&gt;

&lt;p&gt;The k8s-device-plugin deployed in the Kubernetes cluster has &lt;a href=&quot;https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/&quot;&gt;DaemonSet&lt;/a&gt; to check for available Nvidia GPU devices on each node, report on them, and manage that information with the Kubernetes API server.&lt;/p&gt;

&lt;h2 id=&quot;dig-this-hole-first---build-nix-playground-for-hacking-and-patching-any-source-code-easily&quot;&gt;Dig this hole first - build nix-playground for hacking and patching any source code easily&lt;/h2&gt;

&lt;p&gt;Now we have the background knowledge about what’s what in the context of Nvidia GPU on Kubernetes.
Do you think it’s about time to discuss adding Nvidia GPU support to your bare-metal Kubernetes cluster?
Well, not so fast. As I thought it shouldn’t be too hard, just deploy the k8s-device-plugin as &lt;a href=&quot;https://github.com/NVIDIA/k8s-device-plugin/tree/29e135d6b29b66cd84e479ecb2809267606b91ef/deployments/helm/nvidia-device-plugin&quot;&gt;a helm chart&lt;/a&gt;, generate and modify configurations as described in Nvidia’s official documents, and it should be good to go, right?
Unfortunately, I saw many errors immediately right after I did so. Like this one from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvdp-nvidia-device-plugin&lt;/code&gt; DaemonSet:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;symbol lookup error: /nix/store/nqb2ns2d1lahnd5ncwmn6k84qfd7vx2k-glibc-2.40-36/lib/libc.so.6: undefined symbol: __tunable_is_initialized, version GLIBC_PRIVATE
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It turns out we need to dig deeper 😅.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;/images/2025-03-01-nvidia-gpu-on-bare-metal-nixos-k8s-explained/go-deeper.jpg&quot; alt=&quot;The We Need To Go Deeper meme from the Inception movie&quot; /&gt;&lt;figcaption&gt;&lt;p&gt;The &lt;a href=&quot;https://knowyourmeme.com/memes/we-need-to-go-deeper&quot;&gt;We Need To Go Deeper&lt;/a&gt; meme from the Inception movie&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;I am the type of engineer who enjoys digging rabbit holes most of the time and learns a lot from doing so.
Many people don’t understand software engineering, so I have said countless times that reading code is way harder than writing code.
In the era of AI, generating code makes it even harder to read. And reading source code upstream is critical for solving hard problems.
Sometimes, you are lucky, and there are easy ways to work around problems, but sometimes, there’s no easy way around but to find the root cause.&lt;/p&gt;

&lt;p&gt;An error like this one is very tricky because the executable crashes immediately after it starts the container process in the Linux namespace.
Usually, when seeing an error like this, I just &lt;a href=&quot;https://docs.podman.io/en/v5.1.2/markdown/podman-exec.1.html&quot;&gt;exec&lt;/a&gt; or &lt;a href=&quot;https://kubernetes.io/docs/reference/kubectl/generated/kubectl_debug/&quot;&gt;debug&lt;/a&gt; into the container with a shell to see what’s happening.
But it won’t work in this case because all executables crash like that in the container, even for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/bin/sh.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I had no choice but to read the source code of Nvidia’s container projects, and I needed to modify the code to figure out what was going on.
Unfortunately, modifying source code and making a custom build is annoying.
Just to get the project to build could take hours.
You need to read the instructions to install the required dependencies.
Sometimes, there are very specific versions of those dependencies or build environments you need to get.
Very often, it could take hours or even a few days just to get a project to build, depending on how complex the project is.&lt;/p&gt;

&lt;p&gt;I wish there was a tool to help me hack the source code of any open-source projects and build them quickly with my code changes so I could try things out quickly.
Then I realized, okay, why not just build one myself?
Obviously, this is yet another rabbit hole to dig from the original one.
But if it works, this could be super useful down the road.
Considering the potential productivity gains and the leverage this tool could have.
I’ve decided to context-switch to digging this rabbit hole shortly.&lt;/p&gt;

&lt;p&gt;I love NixOS and Nixpkgs because they’re a build tree containing almost all major open-source software, from the Linux kernel to a utils command-line tool.
As far as I know, nothing else like this allows you to build the whole system from end to end under the same framework.
Thanks to the nature of this tool, it has the building scripts ready in Nix for all the projects I want to look into, including the &lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/b27ba4eb322d9d2bf2dc9ada9fd59442f50c8d7c/pkgs/by-name/nv/nvidia-container-toolkit/package.nix&quot;&gt;nvidia-container-toolkit&lt;/a&gt; and &lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/nixos-24.11/pkgs/by-name/li/libnvidia-container/package.nix&quot;&gt;libnvidia-container&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since each Nixpkgs package is just the artifact of yet another &lt;a href=&quot;https://nix.dev/manual/nix/2.22/language/derivations&quot;&gt;Nix’s derivation&lt;/a&gt;, which describes how to build the project and all its dependencies in a &lt;a href=&quot;https://nix.dev/manual/nix/2.25/protocols/derivation-aterm&quot;&gt;pure data format (ATerm)&lt;/a&gt;, I should be able to extract the source code from it.
With that in mind, I quickly built a prototype to try out, which I called &lt;a href=&quot;https://github.com/LaunchPlatform/nix-playground&quot;&gt;nix-playground&lt;/a&gt;.
And yes, I open-sourced it under the MIT license.
You can use it as a playground to easily play around and patch any open-source project.
Here’s some example:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# checkout libnvidia-container package source code locally&lt;/span&gt;
np checkout nixpkgs#libnvidia-container

&lt;span class=&quot;c&quot;&gt;# modify the code&lt;/span&gt;
vim checkout/src/cli/main.c

&lt;span class=&quot;c&quot;&gt;# build the package with changes you made in the checkout folder and try it out&lt;/span&gt;
np build

&lt;span class=&quot;c&quot;&gt;# output the patch for applying on the production environments&lt;/span&gt;
np patch &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; bugfix.patch

&lt;span class=&quot;c&quot;&gt;# clean up the generated files&lt;/span&gt;
np clean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I spent two days fixing some corner cases on this open-source project.
It now works great!
I have the best shovel for digging the k8s-device-plugin crashing bug.
It’s time to go back and dig the previous rabbit hole!&lt;/p&gt;

&lt;h2 id=&quot;hunting-the-k8s-device-plugin-crashing-bug&quot;&gt;Hunting the k8s-device-plugin crashing bug&lt;/h2&gt;

&lt;p&gt;One problem I faced when debugging the k8s-device-plugin crashing issue was that I didn’t know how it worked in detail.
So, I inserted many log writing lines into the code to see how it worked.
Some may say hey, you can use a debugger.
Well, yeah, I know, but very often, it’s also very tedious to set up a debugger.
I would use it if it comes in handy; otherwise, I would just insert logs.&lt;/p&gt;

&lt;p&gt;I have been using &lt;a href=&quot;https://x.ai/blog/grok-3&quot;&gt;Grok 3&lt;/a&gt; for a while since its launch.
I really enjoy it. Sometimes, it provides made-up or wrong results but still very often provides sound analytics.
I never intend to rely on it fully.
I take it as more like riding a bike.
I am still the one who rides the bike, not the bike riding me.
The code it generates still can’t always meet my standards.
When I jumped into debugging the Nvidia stuff, I quickly realized, well, despite that, I don’t trust the quality of the generated code from AI models, but if it’s throw-away code, it doesn’t matter.
I don’t care about the quality as long as it works.
Therefore, I &lt;a href=&quot;https://grok.com/share/bGVnYWN5_d3f969c9-fc68-41ba-aba7-78da89be5386&quot;&gt;asked Grok 3 to help me insert log entries into the source code I pasted&lt;/a&gt; from Nvidia’s GitHub repository, and it did a wonderful job.&lt;/p&gt;

&lt;p&gt;Thanks to Grok 3, I no longer have to manually write log entries for debugging purposes and can instead focus on solving the actual problem.
Here’ss the log file generated from the &lt;a href=&quot;https://github.com/NVIDIA/nvidia-container-toolkit/blob/bc9ec77fdde552949022cbaf74c8b56e67702125/cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go&quot;&gt;update ldconfig hook&lt;/a&gt; patched by Grok 3.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;2025-02-26T17:23:51-08:00: Starting update-ldcache
2025-02-26T17:23:51-08:00: Loading container state from
2025-02-26T17:23:51-08:00: Loaded container state
2025-02-26T17:23:51-08:00: Getting container root
2025-02-26T17:23:51-08:00: Determined container root: /home/fangpen/.local/share/containers/storage/overlay/5c92b5c6053ce2643bcbe516adf268ecdf03244ab3a514ff2e539b6017dbcf0e/merged
2025-02-26T17:23:51-08:00: Resolving ldconfig path from /nix/store/29mb4q8b5306f4gk2wh38h0c1akb0n97-glibc-2.40-36-bin/bin/ldconfig
2025-02-26T17:23:51-08:00: Resolved ldconfig path: /nix/store/29mb4q8b5306f4gk2wh38h0c1akb0n97-glibc-2.40-36-bin/bin/ldconfig
2025-02-26T17:23:51-08:00: Checking for /etc/ld.so.cache in container
2025-02-26T17:23:51-08:00: /etc/ld.so.cache exists, will update cache
2025-02-26T17:23:51-08:00: Creating config in /etc/ld.so.conf.d for folders: /nix/store/x5522a7p46nnbwxjv8w942p6qps7x0lw-nvidia-x11-565.77-6.6.79/lib
2025-02-26T17:23:51-08:00: Creating /etc/ld.so.conf.d if not exists
2025-02-26T17:23:51-08:00: Created /etc/ld.so.conf.d
2025-02-26T17:23:51-08:00: Creating temp config file in /etc/ld.so.conf.d
2025-02-26T17:23:51-08:00: Created temp config file: /home/fangpen/.local/share/containers/storage/overlay/5c92b5c6053ce2643bcbe516adf268ecdf03244ab3a514ff2e539b6017dbcf0e/merged/etc/ld.so.conf.d/nvcr-4265966838.conf
2025-02-26T17:23:51-08:00: Writing folders to config file: /nix/store/x5522a7p46nnbwxjv8w942p6qps7x0lw-nvidia-x11-565.77-6.6.79/lib
2025-02-26T17:23:51-08:00: Added folder: /nix/store/x5522a7p46nnbwxjv8w942p6qps7x0lw-nvidia-x11-565.77-6.6.79/lib
2025-02-26T17:23:51-08:00: Setting permissions on config file to 0644
2025-02-26T17:23:51-08:00: Set permissions on config file
2025-02-26T17:23:51-08:00: Preparing ldconfig arguments: ldconfig -r /home/fangpen/.local/share/containers/storage/overlay/5c92b5c6053ce2643bcbe516adf268ecdf03244ab3a514ff2e539b6017dbcf0e/merged -C /etc/ld.so.cache -f /etc/ld.so.conf
2025-02-26T17:23:51-08:00: Executing ldconfig with args: ldconfig -r /home/fangpen/.local/share/containers/storage/overlay/5c92b5c6053ce2643bcbe516adf268ecdf03244ab3a514ff2e539b6017dbcf0e/merged -C /etc/ld.so.cache -f /etc/ld.so.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With the logs, I tried things quickly and understood the flow of the code.
However, since the problem happens inside the container namespaces, I cannot view them from my non-namespace environment.
I need to get a shell inside the namespace to better understand what’s going on.
So, after I narrowed down the problem to the update ldconfig hook, I added a long sleep before the Nvidia container runtime makes the exec call to ldconfig so that I have time to look into it.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createContainer&lt;/code&gt; hook configuration at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/run/cdi/nvidia-container-toolkit.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;_&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;// ...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;hooks&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;hookName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;createContainer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;//nix/store/il5kz2p67hdd05c9gmg8m5c5l8gbrk90-container-toolkit-container-toolkit-1.15.0-rc.3/bin/nvidia-ctk&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;nvidia-ctk&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;hook&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;update-ldcache&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;--ldconfig-path&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/nix/store/29mb4q8b5306f4gk2wh38h0c1akb0n97-glibc-2.40-36-bin/bin/ldconfig&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;--folder&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/nix/store/x5522a7p46nnbwxjv8w942p6qps7x0lw-nvidia-x11-565.77-6.6.79/lib&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After the hook sleeps and hangs, I ran&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;lsns
        NS TYPE   NPROCS    PID USER    COMMAND
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; ... omit ...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
4026533991 mnt         1 388163 fangpen /bin/sh
4026533992 uts         1 388163 fangpen /bin/sh
4026533993 ipc         1 388163 fangpen /bin/sh
4026533994 pid         1 388163 fangpen /bin/sh
4026533995 cgroup      1 388163 fangpen /bin/sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To find out the pid of the container process, I just launched via podman to reproduce the bug.
Then, I run this to attach a shell into the namespace:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;enterns &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; &amp;lt;CONTAINER_PID&amp;gt; &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; /bin/sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, I ran the ldconfig command, which is supposed to run with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p&lt;/code&gt; argument to see the current ldconfig cache.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ldconfig -p
131 libs found in cache `/etc/ld.so.cache&apos;
        ( ... omit ...)
        libc.so.6 (libc6,x86-64, OS ABI: Linux 3.2.0) =&amp;gt; /lib64/libc.so.6
        ( ... omit ...)
Cache generated by: ldconfig (GNU libc) stable release version 2.34
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then, run the ldconfig without &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p&lt;/code&gt; again to update cache and then with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p&lt;/code&gt; again to see the current cache:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ldconfig -r /home/fangpen/.local/share/containers/storage/overlay/3e2b7a6d05ca228fafe39b47a19952d4b81a9c413618c66a7013d794e4db3d96/merged -C /etc/ld.so.cache -f /etc/ld.so.conf -p
64 libs found in cache `/etc/ld.so.cache&apos;
        ( ... omit ...)
        libc.so.6 (libc6,x86-64) =&amp;gt; /nix/store/nqb2ns2d1lahnd5ncwmn6k84qfd7vx2k-glibc-2.40-36/lib/libc.so.6
        ( ... omit ...)
Cache generated by: ldconfig (GNU libc) stable release version 2.40
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Oops.
All the caches are pointing to libraries injected by the Nvidia runtime container, including glibc.
I guess the glibc compiled inside the plugin container image is different from the one shipped with the Nvidia driver in the Nix store.
As a result, any executable that relies on glibc would then link to the wrong one, and literally, almost all executables would rely on glibc, and therefore, they all crash immediately.&lt;/p&gt;

&lt;p&gt;After learning the root cause, I quickly drafted &lt;a href=&quot;https://github.com/NVIDIA/k8s-device-plugin/issues/1182&quot;&gt;an issue&lt;/a&gt;, reported it to Nvidia’s nvidia-container-toolkit repository, and found an easy fix.
Most containers come with the config file in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/ld.so.conf.d&lt;/code&gt; pointing to the existing library paths.
The Nvidia runtime hook generates its own config file pointing to the library path provided in the argument.
But without the one pointing to the existing library path, there will only be a cache pointing to the injected lib.
To solve the problem, I &lt;a href=&quot;https://github.com/NVIDIA/k8s-device-plugin/pull/1183&quot;&gt;created a PR&lt;/a&gt; with a config file pointing to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/lib64&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-Dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/lib64&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /etc/ld.so.conf.d/lib64.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It was just a line code change, and it took me a few days to find out, including building the nix-playground tool 😅
That’s why one should never measure productivity with the number of lines added.&lt;/p&gt;

&lt;h2 id=&quot;the-missing-runtimeclass&quot;&gt;The missing RuntimeClass&lt;/h2&gt;

&lt;p&gt;Suppose you follow many online guides about running Kubernetes pods with Nvidia drivers.
In that case, you may think you should be able to run it after you update the Containerd configuration to include the Nvidia CRI and set it as the default like this:&lt;/p&gt;

&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;[plugins.&quot;io.containerd.grpc.v1.cri&quot;]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;default_runtime_name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;nvidia&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# other stuff ..&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Unfortunately, it doesn’t work. I haven’t dug into this yet, but according to Nvidia’s k8s-device-plugin readme page:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If the nvidia runtime should be set as the default runtime (with non-cri docker versions, for example), the –set-as-default argument must also be included in the commands above. If this is not done, a RuntimeClass needs to be defined.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s just one line in the README, and they’re very easy to overlook.
So, without an explicit &lt;a href=&quot;https://kubernetes.io/docs/concepts/containers/runtime-class/&quot;&gt;RuntimeClass&lt;/a&gt; defined and assigned to a pod in Kubernetes,
it won’t pick up the Nvidia runtime. I didn’t have time to find out how to make a runtime default on a node yet, but for now, I had to define a RuntimeClass like this:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;node.k8s.io/v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;RuntimeClass&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nvidia&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nvidia&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then, I will assign the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtimeClassName&lt;/code&gt; to it so that it will pick up my CRI configuration for Containerd.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Pod&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gpu-pod&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# the `runtimeClassName` is needed, otherwise default runtime will still be used&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;runtimeClassName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nvidia&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cuda-container&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda12.5.0&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;limits&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# requesting 1 GPU&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tolerations&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nvidia.com/gpu&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Exists&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;effect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;NoSchedule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;yet-another-rabbit-hole---pycharm-cannot-open-my-wsl-nixos-anymore&quot;&gt;Yet another rabbit hole - PyCharm cannot open my WSL NixOS anymore&lt;/h2&gt;

&lt;p&gt;Interestingly, after the recent update, my &lt;a href=&quot;https://www.jetbrains.com/pycharm/&quot;&gt;PyCharm&lt;/a&gt; stopped working with my Python projects in my NixOS distro, &lt;a href=&quot;https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux&quot;&gt;WSL (Windows Subsystem Linux)&lt;/a&gt;.
While it sounds a bit unrelated to the main focus of getting the Nvidia GPU to run on Kubernetes, I needed to build nix-playground in Python, and it can only work in a Linux environment.
I am bringing this up because this is the third level deep rabbit hole from Nvidia GPU on Kubernetes.&lt;/p&gt;

&lt;p&gt;I &lt;a href=&quot;https://youtrack.jetbrains.com/issue/PY-78867/Cannot-open-my-projects-in-WSL2-folder&quot;&gt;reported this problem&lt;/a&gt; a while back, but I decided to dig this hole while building nix-playground because not being able to use PyCharm with WSL projects greatly impacts my productivity.
After digging briefly, I realized that PyCharm expected many executables at certain locations, such as in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/bin&lt;/code&gt;.
I addressed the problem by adding them to WSL NixOS config’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extraBin&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;lib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;wsl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;extraBin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;coreutils&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/mkdir&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;coreutils&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/cat&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;coreutils&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/whoami&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;coreutils&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/ls&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;busybox&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/addgroup&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;su&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/groupadd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;su&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/usermod&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;busybox&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/busybox&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;busybox&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/chmod&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;busybox&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/cp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;busybox&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/cut&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;busybox&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/getent&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;busybox&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/head&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;busybox&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/mktemp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;busybox&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/rm&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;busybox&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/sed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;busybox&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/tail&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;busybox&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/uname&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;busybox&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/bin/which&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;the-rule-of-thumbs-for-digging-the-rabbit-holes&quot;&gt;The rule of thumbs for digging the rabbit holes&lt;/h2&gt;

&lt;p&gt;Now, you’ve seen how I dug many rabbit holes in the past week.
I tried to summarize my approach to digging the chain of rabbit holes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Consider the productivity impact. If it provides a great positive impact within a manageable timeframe, do it as soon as possible (nix-playground as a great example)&lt;/li&gt;
  &lt;li&gt;Not all rabbit holes are worth digging; consider the cost-effect ratio. For example, I won’t jump into learning PTX and optimizing my MAZE code before it runs on a huge scale.&lt;/li&gt;
  &lt;li&gt;Evaluate the timeframe of digging a hole to carefully decide whether to go down a hole. Sometimes, it’s hard to tell from the surface; a quick prototype and probing could help determine how hard it is.&lt;/li&gt;
  &lt;li&gt;Enjoy digging rabbit holes because it’s the best way to learn new things&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;final-thoughts&quot;&gt;Final thoughts&lt;/h1&gt;

&lt;p&gt;I intentionally left out many details from this article because otherwise, it would be too long.
If anyone is interested in learning more about running Nvidia CDI on a local Kubernetes cluster, please leave a comment and let me know.
I am considering open-sourcing some of my work to make it much easier for anyone to have a local Kubernetes cluster enabled with Nvidia CDI.&lt;/p&gt;

&lt;p&gt;This deviates a bit from building MAZE, but the outcome is great! Now I have an infra for running MAZE on a bigger scale—well, at least slightly bigger on the scale I can afford 😅
There are still other rabbit holes, like power consumption. With the constraints, I can only try to be creative.&lt;/p&gt;

&lt;p&gt;Next, I will go back to improving MAZE and soon publish the third MAZE article.
I have been running the experiment for the past week, and it has already accumulated some data with updated MAZE code, including mutations and some other good stuff.
Likewise, I will publish the data along with the next article. I am still thinking about the next focus of the following research.
I have some interesting ideas for eliminating backpropagation with the MAZE approach.
I am also thinking that maybe it’s time to introduce more freedom (more operations) to MAZE’s neuron network so that I can break the accuracy barrier it’s facing.
Anyway, we will see.&lt;/p&gt;

&lt;p&gt;Regardless, thanks for reading.
I hope you like my journey of digging the rabbit hole and sharing knowledge about running Nvidia GPU on Kubernetes.
Please help me share this article if you find it interesting.
Stay tuned because we will have tons of fun on the journey of learning machine learning by building a novel project like MAZE! 😄&lt;/p&gt;
</description>
        <pubDate>Sat, 01 Mar 2025 07:00:00 +0000</pubDate>
        <link>https://fangpenlin.com/posts/2025/03/01/nvidia-gpu-on-bare-metal-nixos-k8s-explained/</link>
        <guid isPermaLink="true">https://fangpenlin.com/posts/2025/03/01/nvidia-gpu-on-bare-metal-nixos-k8s-explained/</guid>
      </item>
    
  </channel>
</rss>
