<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Ghif Lab</title>
<link>https://ghif.github.io/</link>
<atom:link href="https://ghif.github.io/index.xml" rel="self" type="application/rss+xml"/>
<description>Muhammad Ghifary&#39;s personal notes on AI research, machine learning, computer vision, implementations, and the evolution of intelligent systems</description>
<generator>quarto-1.8.27</generator>
<lastBuildDate>Fri, 07 Aug 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>Building Counterfactual Image Generative Models with JAX</title>
  <dc:creator>Muhammad Ghifary</dc:creator>
  <dc:creator>Vanya Valindria</dc:creator>
  <link>https://ghif.github.io/posts/2026-08-07-counterfactual-image-generative-models/</link>
  <description><![CDATA[ 




<p>Many image generation tasks sound causal before they are formalized. What would a chest radiograph look like if an abnormality were absent? What would this face look like under different lighting? What would this handwritten digit look like if it were thinner, while still preserving the writer’s style?</p>
<p>An ordinary conditional generator can create a plausible image with a requested attribute. That’s useful, but it doesn’t answer the individual-level question behind explanation, simulation, and intervention: what would have happened to this observed sample if one cause had been different? Suppose an image has thickness 3.6 and intensity 245. Drawing a digit with thickness 2.1 is not the same as asking:</p>
<blockquote class="blockquote">
<p>What would this same handwritten digit have looked like if its thickness had been 2.1?</p>
</blockquote>
<p>This “same” carries most of the difficulty. <strong>A useful answer should preserve the observation-specific factors that the intervention does not cause, e.g., writing style, pose, fine image detail, and other latent variation, while changing the target variable and only its causal descendants</strong>. A model that changes everything is an editor or a model that preserves the wrong things is not a causal explanation either.</p>
<p>Consider a clinical decision-support system reviewing a chest radiograph for pneumonia. Asking for an image that merely <strong>looks less like pneumonia</strong> may also alter the patient’s anatomy, image positioning, or acquisition conditions, making it impossible to tell what visual evidence drove the prediction. A meaningful counterfactual instead asks: “What would this same patient’s radiograph look like if the pneumonia-related finding were absent?” That question requires a causal intervention on the finding while preserving the patient-specific anatomy and unrelated imaging factors.</p>
<p>This is why causal and counterfactual modeling is relevant. It makes the assumptions behind a “what if” image explicit: which factors are causes, which variation belongs to the observed individual, and which change is being contemplated. That does not guarantee a correct answer — the causal graph and mechanisms must still be justified — but it gives the questions a meaning that ordinary conditional generation lacks.</p>
<p>In this article, we take a close look at an image counterfactual method of (<a href="https://proceedings.mlr.press/v202/de-sousa-ribeiro23a.html">Ribeiro et al.&nbsp;2023</a>) — they also share the code implementation here: https://github.com/biomedia-mira/causal-gen</p>
<p>We port the code to be a pure JAX/Flax implementation with not merely a translation of APIs, but also creating a cleaner path to efficient execution on TPU hardware: https://github.com/ghif/causal-genx/</p>
<section id="a.-causal-modeling-foundations" class="level2">
<h2 class="anchored" data-anchor-id="a.-causal-modeling-foundations">A. Causal Modeling Foundations</h2>
<section id="a.1.-observation-vs.-intervention-seeing-vs.-doing" class="level3">
<h3 class="anchored" data-anchor-id="a.1.-observation-vs.-intervention-seeing-vs.-doing">A.1. Observation vs.&nbsp;Intervention: Seeing vs.&nbsp;Doing</h3>
<p>To appreciate causal inference, one should understand the difference between <strong>observation (”seeing”)</strong> and <strong>intervention</strong> <strong>(”doing”)</strong>. Observation means watching the world naturally unfold and collecting data without changing anything. When we observe, we might ask:</p>
<blockquote class="blockquote">
<p>Given that I just noticed event <img src="https://latex.codecogs.com/png.latex?A"> happening, how likely is it that event <img src="https://latex.codecogs.com/png.latex?B"> is also happening?</p>
</blockquote>
<p>This type of question is typically answered formally through probabilistic models, through a conditional distribution <img src="https://latex.codecogs.com/png.latex?P(B%20%7C%20A)">.</p>
<p>Let us use the <strong>rooster (A) and sunrise (B) analogy</strong>. Imagine a rooster that crows every morning right before the sun rises. For every single day for 10 years, we write down two things:</p>
<ul>
<li>Did the rooster crow? (Yes)</li>
<li>Did the sun rise? (Yes)</li>
</ul>
<p>We can statistically conclude 100% correlation: <img src="https://latex.codecogs.com/png.latex?P(B%20=%20%5Ctexttt%7BSunrise%7D%20%7C%20A%20=%5Ctexttt%7BRooster%20crow%7D)%20=%201.0">. If we stay purely in the world of observation, the data tells us that the rooster crowing and the sunrise are tightly linked. But, does the rooster <em>cause</em> the sun to rise?</p>
<p>Intervention means stepping into the world, breaking the natural routine, and forcing a variable to take a specific value regardless of what normally happens. When we intervene, we are asking</p>
<blockquote class="blockquote">
<p>If I actively step in and force <img src="https://latex.codecogs.com/png.latex?A"> to happen, what will happen to <img src="https://latex.codecogs.com/png.latex?B">?</p>
</blockquote>
<p>Mathematically, Judea Pearl (<a href="https://www.jstor.org/stable/2337329">Pearl 1995</a>) represented this using the <strong>do-operator</strong>: <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Bdo%7D(A%20=%20%5Ctexttt%7BRooster%20crow%7D)">.</p>
<p>In a real world, we can test if the rooster actually causes the sunrise by performing this intervention:</p>
<ol type="1">
<li>Scenario A (Forced Silence): We step in and hold the rooster’s beak shut at 5:00 AM, i.e., <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Bdo%7D(A%20=%20%5Ctexttt%7BRooster%20silent%7D)">. Does the sun still rise? Yes.</li>
<li>Scenario B (Forced Crowing): We wake the rooster up at 2:00 AM in pitch darkness and force it to crow, i.e., <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Bdo%7D(A%20=%20%5Ctexttt%7BRooster%20crow%7D)">. Does the sun immediately rise? No.</li>
</ol>
<p>Through intervention, we discover that:</p>
<ul>
<li>Observational probability: <img src="https://latex.codecogs.com/png.latex?P(B=%5Ctexttt%7BSunrise%7D%20%7C%20A=%5Ctexttt%7BRooster%20crow%7D)%20=%20%5Ctexttt%7BHigh%7D"></li>
<li>Interventional probability: <img src="https://latex.codecogs.com/png.latex?P(B=%5Ctexttt%7BSunrise%7D%20%7C%20%5Coperatorname%7Bdo%7D(A%20=%20%5Ctexttt%7BRooster%20crow%7D))%20=%20%5Ctexttt%7BUnchanged%7D"></li>
</ul>
<p>By stepping in, we cut the connection and proved the rooster does not control the sun 🙂</p>
</section>
<section id="a.2.-from-probability-to-causal-mechanism" class="level3">
<h3 class="anchored" data-anchor-id="a.2.-from-probability-to-causal-mechanism">A.2. From Probability to Causal Mechanism</h3>
<p>More formally, a traditional probabilistic graphical model (PGM) represents a joint distribution such as <img src="https://latex.codecogs.com/png.latex?p(x_1,%5Cldots,x_n)"> and uses conditioning, for example <img src="https://latex.codecogs.com/png.latex?p(Y%5Cmid%20X=x)">, to answer predictive or diagnostic questions. A causal framework adds the claim that the graph’s arrows correspond to data-generating mechanisms. That extra claim gives meaning to an intervention <img src="https://latex.codecogs.com/png.latex?do(X:=x)"> and to a unit-level counterfactual; conditioning alone cannot distinguish “units that happened to have <img src="https://latex.codecogs.com/png.latex?X=x">” from “the same unit after its cause was set to <img src="https://latex.codecogs.com/png.latex?x">.” Even two DAG factorizations with the same observational distribution can imply different interventions (<a href="https://www.cambridge.org/core/books/causality/B0046844FAE10CBF274D4ACBDAEB5F5B">Pearl 2009</a>, <a href="https://www.amazon.com/dp/0262013193">Koller &amp; Friedman 2009</a>).</p>
<p>Pearl introduced a causal framework called Structural Causal Model (SCM), which adds the missing semantic layer: it is a model of how variables are generated, not merely how they co-occur (<a href="https://www.amazon.com/dp/0262037319">Petters et al.&nbsp;2017</a>). The model can be represented as a tuple</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%20M=%5Clangle%5Cmathcal%20X,%5Cmathcal%20U,%5Cmathcal%20F,P(%5Cmathcal%20U)%5Crangle%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%20X=%5C%7BX_1,%5Cldots,X_N%5C%7D"> is the set of <strong>endogenous variables</strong> generated by the model; <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%20U=%5C%7BU_1,%5Cldots,U_N%5C%7D"> is the set of <strong>exogenous variables</strong>, representing background causes and individual-specific randomness; <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%20F=%5C%7Bf_1,%5Cldots,f_N%5C%7D"> is the set of structural mechanisms; and <img src="https://latex.codecogs.com/png.latex?P(%5Cmathcal%20U)"> specifies how causal worlds are distributed. Each endogenous variable is assigned one mechanism,</p>
<p>$$ X_k := f_k (_k, U_k), </p>
<p>$$</p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Bpa%7D_k"> denotes the direct endogenous parents of <img src="https://latex.codecogs.com/png.latex?X_k">. The associated DAG records this mechanism-level dependency: an arrow <img src="https://latex.codecogs.com/png.latex?X_j%5Cto%20X_k"> means that changing <img src="https://latex.codecogs.com/png.latex?X_j"> can change the input to <img src="https://latex.codecogs.com/png.latex?f_k">, not merely that the variables are statistically associated.</p>
<p>Let’s consider a simple example of an SCM with 3 endogenous variables <img src="https://latex.codecogs.com/png.latex?X_1,%20X_2,%20Y"> drawn as a graph below:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    U1(("U₁")) --&gt; X1(("X₁"))
    U2(("U₂")) --&gt; X2(("X₂"))
    X1 --&gt; X2
    U3(("U₃")) --&gt; Y(("Y"))
    X1 --&gt; Y
    X2 --&gt; Y
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>In this example, the mechanisms are <img src="https://latex.codecogs.com/png.latex?X_1:=f_1(U_1)">, <img src="https://latex.codecogs.com/png.latex?X_2:=f_2(X_1,U_2)">, and <img src="https://latex.codecogs.com/png.latex?Y:=f_Y(X_1,X_2,U_3)">. Solid arrows between endogenous variables encode the causal graph; the arrows from <img src="https://latex.codecogs.com/png.latex?U_1,U_2,U_3"> represent background influences/noises that are not generated by another endogenous mechanism.</p>
<p>In the common acyclic Markovian case, which is a useful assumption in practice, the exogenous variables are mutually independent,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP(%5Cmathcal%20U)=%5Cprod_%7Bk=1%7D%5E%7BN%7DP(U_k),%0A"></p>
<p>and the mechanisms induce the familiar observational factorization</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP_%7B%5Cmathcal%20M%7D(x_1,%5Cldots,x_N)=%5Cprod_%7Bk=1%7D%5E%7BN%7DP(x_k%5Cmid%5Coperatorname%7Bpa%7D_k).%5Ctag%7B3%7D%0A"></p>
<p>The factorization alone is not what makes the model causal. Its causal meaning comes from the invariance assumption: a mechanism remains in force unless an intervention explicitly replaces it. This distinction is summarized below.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th><strong>Question</strong></th>
<th><strong>Standard Probabilistic Model</strong></th>
<th><strong>Structural Causal Model</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>What is likely after observing X=x?</td>
<td>Computes p(Y</td>
<td>X = x)</td>
</tr>
<tr class="even">
<td>What happens after setting X:=x?</td>
<td>Not defined without extra causal assumptions</td>
<td>Replaces the mechanism for X and computes p(Y</td>
</tr>
<tr class="odd">
<td>What would have happened to this sample if X had differed?</td>
<td>Cannot preserve an individual’s latent realization by conditioning alone</td>
<td>Abducts exogenous factors, intervenes, then predicts a counterfactual</td>
</tr>
</tbody>
</table>
<p>That distinction separates <strong>association from intervention</strong>. Pearl’s ladder of causation makes the distinction even more clearly:</p>
<ol type="1">
<li><strong>Association (seeing):</strong> use observed data to evaluate <img src="https://latex.codecogs.com/png.latex?P(Y%5Cmid%20X=x)">. This answers what is likely among units for which <img src="https://latex.codecogs.com/png.latex?X=x"> was observed.</li>
<li><strong>Intervention (doing):</strong> evaluate <img src="https://latex.codecogs.com/png.latex?P(Y%5Cmid%20do(X=x))">. The expression <img src="https://latex.codecogs.com/png.latex?do(X=x)"> replaces the mechanism for <img src="https://latex.codecogs.com/png.latex?X"> with the constant assignment <img src="https://latex.codecogs.com/png.latex?X:=x">, while leaving all other mechanisms intact. This answers what would occur after actively setting <img src="https://latex.codecogs.com/png.latex?X">.</li>
<li><strong>Counterfactuals (imagining):</strong> evaluate <img src="https://latex.codecogs.com/png.latex?P(Y_%7Bdo(A=%5Cwidetilde%20a)%7D%5Cmid%20E=e)">. This asks what would have occurred in the same factual unit under a different action.</li>
</ol>
<p>The third level requires more than graph surgery because the question is tied to a particular observation. Its answer follows the <strong>abduction–action–prediction</strong> procedure:</p>
<ol type="1">
<li><strong>Abduction:</strong> condition on the factual evidence <img src="https://latex.codecogs.com/png.latex?E=e"> to infer the exogenous state, <img src="https://latex.codecogs.com/png.latex?P(u%5Cmid%20E=e)">. This recovers the background realization that makes the observed unit distinct.</li>
<li><strong>Action:</strong> form the intervened SCM by replacing the selected structural assignment with <img src="https://latex.codecogs.com/png.latex?do(A:=%5Cwidetilde%20a)">. All non-intervened mechanisms and the abducted exogenous state are retained.</li>
<li><strong>Prediction:</strong> evaluate the modified mechanisms in causal order with that same <img src="https://latex.codecogs.com/png.latex?u">. The resulting value or distribution is the counterfactual outcome.</li>
</ol>
<p>Formally, this produces</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP%5C!%5Cleft(Y_%7Bdo(A=%5Cwidetilde%20a)%7D%5Cmid%20E=e%5Cright)=%5Cint%20P%5C!%5Cleft(Y_%7Bdo(A=%5Cwidetilde%20a)%7D%5Cmid%20u%5Cright)P(u%5Cmid%20E=e)%5C,du.%5Ctag%7B4%7D%0A"></p>
</section>
<section id="a.3.-classical-causal-models" class="level3">
<h3 class="anchored" data-anchor-id="a.3.-classical-causal-models">A.3. Classical Causal Models</h3>
<p>The SCM equations above are abstract. These can be made concrete through classical statistical models, for examples:</p>
<p><strong>Linear structural-equation models (SEM).</strong> In a linear SEM, a causal mechanism is written as <img src="https://latex.codecogs.com/png.latex?X_k%20=%20%5Cbeta%5E%5Ctop%20%5Coperatorname%7Bpa%7D_k%20+%20U_k">. Abduction is residual estimation <img src="https://latex.codecogs.com/png.latex?%5Chat%7BU%7D_k=%20X_k%20-%20%5Cbeta%5E%5Ctop%20%5Coperatorname%7Bpa%7D_k"> . Action replaces the selected equation by a constant and prediction evaluates the descendants using the same residuals. These models are attractive for econometrics, mediation, and small tabular systems because coefficients have direct effect interpretations and inference can be closed form (<a href="https://www.amazon.com/dp/0471011711">Bollen 1989</a>). This is one of the simplest SCM frameworks but can be less suitable when mechanisms are strongly non-linear or multimodal.</p>
<p><strong>Discrete Bayesian networks.</strong> A Bayesian network with conditional probability tables can perform the same operations exactly on small discrete graphs. Posterior inference over unobserved variables is the abduction step, graph surgery implements the intervention, and variable elimination or belief propagation computes the post-intervention distribution. Pearl’s early probabilistic-inference framework and the Lauritzen–Spiegelhalter junction-tree algorithm established practical procedures for these computations (<a href="https://www.sciencedirect.com/book/monograph/9780080514895/probabilistic-reasoning-in-intelligent-systems">Pearl 1988</a>, <a href="https://www.eecis.udel.edu/~shatkay/Course/papers/Lauritzen1988.pdf">Lauritzen 1988</a>). The limitation is computational and representational: exact inference can grow exponentially with graph width, and finite tables do not naturally express continuous, high-dimensional mechanisms.</p>
<p><strong>Additive-noise and LiNGAM models.</strong> Additive-noise models use <img src="https://latex.codecogs.com/png.latex?X_k=f_k(%5Coperatorname%7Bpa%7D_k)+U_k">, where independence between the residual <img src="https://latex.codecogs.com/png.latex?U_k"> and the parents can identify directions under suitable assumptions. Linear Non-Gaussian Acyclic Model (LiNGAM) specializes this idea to linear non-Gaussian systems and exploits non-Gaussian asymmetry for causal discovery (<a href="https://www.jmlr.org/papers/volume7/shimizu06a/shimizu06a.pdf">Shimizu et al.&nbsp;2006</a>). Once the functions and graph are known, residual inversion gives abduction, replacing a mechanism gives action, and downstream evaluation gives prediction. These models are useful when causal discovery and interpretable low-dimensional mechanisms matter more than expressive generation.</p>
<p><strong>Potential outcomes.</strong> Rubin’s framework represents the outcomes a unit would have under alternative treatments, <img src="https://latex.codecogs.com/png.latex?Y(a)"> and <img src="https://latex.codecogs.com/png.latex?Y(a')">, even though only one is observed (<a href="https://onlinelibrary.wiley.com/doi/epdf/10.1002/j.2333-8504.1972.tb00631.x">Rubin 1972</a>). Covariate adjustment, matching, or randomized assignment provides the abduction/identification link from observed data to a unit or population; choosing <img src="https://latex.codecogs.com/png.latex?a'"> is the action; and estimating <img src="https://latex.codecogs.com/png.latex?Y(a')"> is prediction. This framework is foundational for treatment-effect estimation, but it does not by itself prescribe a generative mechanism for every downstream variable. SCMs provide the graph-structured mechanism view that connects such potential outcomes across multiple variables.</p>
<p>While the fore-mentioned approaches differ in assumptions and inference procedures, each has the common causal operations: <strong>abduction-action-prediction</strong>.</p>
</section>
</section>
<section id="b.-towards-causal-deep-learning" class="level2">
<h2 class="anchored" data-anchor-id="b.-towards-causal-deep-learning">B. Towards Causal Deep Learning</h2>
<p>Classical causal models are powerful precisely because their assumptions are explicit. Linear SEMs expose interpretable path coefficients, Bayesian networks support exact of approximate inference on compact graphs, and additive-noise models can identify directions under suitable distributional assumptions. Their common weakness is that the mechanisms are usually linear, table-based, or hand-designed. As the variables become high-dimensional and heterogeneous, these mechanisms struggle to represent nonlinear dependencies, multimodal uncertainty, latent factors, and complex observation process. Inference can also become expensive: exact graphical-model algorithms scale poorly with graph width, while direct inversion of a high-dimensional mechanism is rarely available.</p>
<p>Deep learning offers a natural response to these limitations. Neural networks provide flexible approximations to nonlinear structural assignments. Variational inference (<a href="https://people.eecs.berkeley.edu/~jordan/papers/variational-intro.pdf">Jordan et al.&nbsp;1999</a>) turns hidden-state estimation into an amortized encoder, and hierarchical generative models distribute information across multiple scales (…). Instead of specifying eery conditional density manually, a model can learn a mechanism from data while retaining a causal graph that determines which mechanisms may respond to an intervention. This is especially important when the outcome is not a single scalar but a rich observation whose individual-specific details must be preserved in a counterfactual.</p>
<p>Deep learning does not automatically make a model causal. Unsupervised representations are generally non-identifiable (<a href="https://arxiv.org/pdf/1811.12359">Locatello et al.&nbsp;2019</a>), and a predictor that is accurate under observation may still fail under intervention. The goal of causal deep learning is therefore more precise: combine expressive learned mechanisms with explicit graph structure, identifiable or supervised anchors where possible, and an inference procedure that recovers the factual latent state before changing a cause. These requirements motivated the early neural causal-generative models discussed next and eventually led to tractable deep structural causal models.</p>
<section id="b.1.-early-deep-causal-generative-models" class="level3">
<h3 class="anchored" data-anchor-id="b.1.-early-deep-causal-generative-models">B.1. Early Deep Causal Generative Models</h3>
<p>The first generation of deep causal models established that neural generative models could represent causal variables, but each addressed only part of the counterfactual problem.</p>
<p><strong>CEVAE</strong> introduced a variational autoencoder for causal effect inference with a latent confounder (<a href="https://arxiv.org/pdf/1705.08821">Louizos et al.&nbsp;2017</a>). Its encoder performs approximate abduction of the latent confounder, while treatment and outcome decoders represent the structural mechanisms. CEVAE is useful for estimating treatment effects from observational tabular data, but its latent-variable design is not intended to preserve fine-grained identity in a high-dimensional observation.</p>
<p><strong>CausalGAN</strong> imposed a known causal graph on an implicit GAN generator (<a href="https://arxiv.org/pdf/1709.02023">Kocaoglu et al.&nbsp;2018</a>). Sampling parent variables and feeding them through the generator provides an intervention mechanism, and the adversarial objective encourages realistic samples. However, GAN generation is not naturally invertible: recovering the latent realization of a particular factual sample is difficult, so counterfactual identity preservation is not guaranteed.</p>
<p><strong>CausalVAE</strong> and related causal-disentanglement models placed causal variables in a structured latent space and used weak supervision or graph constraints to encourage the desired dependencies (<a href="https://arxiv.org/pdf/2004.08697">Yang et al.&nbsp;2020</a>). They improve controllable generation, but latent disentanglement alone does not ensure that a factual exogenous realization can be abducted exactly or that all non-descendant details remain unchanged.</p>
<p>Taken together, these early approaches left several gaps. Adversarial generators made factual latent recovery difficult, latent-variable models could depend on strong identifiability or confounding assumptions, and shallow VAE decoders often underfit fine-scale variation. Many methods also modeled only a small number of direct cause–effect relationships, without a general procedure for inverting every mechanism and reusing the same exogenous realization after an intervention. A more complete approach therefore needs tractable abduction, expressive conditional likelihoods, and explicit residual preservation across a whole causal graph.</p>
</section>
<section id="b.2.-deep-structural-causal-models" class="level3">
<h3 class="anchored" data-anchor-id="b.2.-deep-structural-causal-models">B.2. Deep Structural Causal Models</h3>
<p>Those requirements motivate deep structural causal model (DSCM) by (<a href="https://arxiv.org/pdf/2006.06485">Pawlowski et al.&nbsp;2020</a>). Unlike the earlier neural causal generators, Deep SCM treats every node as a modular structural assignment that can be composed according to a causal DAG, selects a mechanism class according to the trade-off between expressive likelihoods and invertibility, and makes abduction tractable enough to execute the complete abduction–action–prediction procedure. It is therefore not merely a neural generator with causal labels, but a way to construct a counterfactual-capable SCM from learned deep mechanisms while preserving the semantics of the original graph.</p>
<p>The central technical problem is still abduction: after observing a factual value, the model must infer the exogenous noise that produced it. DSCMs address this with several mechanism classes rather than one fixed architecture. The following subsections explain those classes, their training objectives, and how their different abduction operations are composed during counterfactual inference.</p>
<section id="the-common-structural-assignment" class="level4">
<h4 class="anchored" data-anchor-id="the-common-structural-assignment"><strong>The common structural assignment</strong></h4>
<p>For every endogenous variable <img src="https://latex.codecogs.com/png.latex?X_i"> in a DAG, the SCM retains the assignment</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AX_i=f_i(%5Cepsilon_i;%5Coperatorname%7Bpa%7D_i),%5Cqquad%20%5Cepsilon_i%5Csim%20P(%5Cepsilon_i),%0A%5Ctag%7BB.1%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Bpa%7D_i"> are the parents and <img src="https://latex.codecogs.com/png.latex?%5Cepsilon_i"> is variable-specific exogenous noise. A counterfactual for an intervention <img src="https://latex.codecogs.com/png.latex?do(A:=%5Cwidetilde%20a)"> follows the same three steps as a classical SCM:</p>
<ol type="1">
<li><strong>Abduction:</strong> infer <img src="https://latex.codecogs.com/png.latex?P(%5Cepsilon_i%5Cmid%20x_i,%5Coperatorname%7Bpa%7D_i)"> for the factual unit.</li>
<li><strong>Action:</strong> replace the mechanism for <img src="https://latex.codecogs.com/png.latex?A"> with the intervention and leave the other mechanisms unchanged.</li>
<li><strong>Prediction:</strong> propagate the abducted noise through the modified graph.</li>
</ol>
<p>The challenge is that a nonlinear neural causal mechanism <img src="https://latex.codecogs.com/png.latex?f_i"> may not be invertible and may not have a tractable likelihood. DSCM therefore organize deep mechanisms according to how they solve this abduction problem.</p>
</section>
<section id="invertible-explicit-mechanisms" class="level4">
<h4 class="anchored" data-anchor-id="invertible-explicit-mechanisms">Invertible explicit mechanisms</h4>
<p>The first class uses a conditional normalizing flow directly as the structural mechanism in equation (B.1) with <img src="https://latex.codecogs.com/png.latex?f_i"> invertible in <img src="https://latex.codecogs.com/png.latex?%5Cepsilon_i">. Its conditional density is evaluated exactly by the change-of-variables identity,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap(x_i%5Cmid%5Coperatorname%7Bpa%7D_i)=p_%5Cepsilon%5C!%5Cleft(f_i%5E%7B-1%7D(x_i;%5Coperatorname%7Bpa%7D_i)%5Cright)%5Cleft%7C%5Cdet%5Cfrac%7B%5Cpartial%20f_i%5E%7B-1%7D(x_i;%5Coperatorname%7Bpa%7D_i)%7D%7B%5Cpartial%20x_i%7D%5Cright%7C.%0A"></p>
<p>The same inverse gives exact abduction,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cepsilon_i=f_i%5E%7B-1%7D(x_i;%5Coperatorname%7Bpa%7D_i).%20%5Ctag%7BB.2%7D%0A"></p>
<p>This is the precise role of normalizing flows in the DSCM framework: they are invertible probabilistic structural mechanisms, not the causal graph itself. Their likelihood can be optimized by exact maximum likelihood. (<a href="https://arxiv.org/pdf/1505.05770">Rezende and Mohamed 2016</a>) provide the general normalizing-flow foundation, while (<a href="https://arxiv.org/pdf/1802.04908">Trippe and Turner 2018</a>) discuss conditional flow constructions.</p>
</section>
<section id="amortised-explicit-mechanisms-for-high-dimensional-variables" class="level4">
<h4 class="anchored" data-anchor-id="amortised-explicit-mechanisms-for-high-dimensional-variables">Amortised explicit mechanisms for high-dimensional variables</h4>
<p>A full flow can be expensive when <img src="https://latex.codecogs.com/png.latex?X_i"> is a high-dimensional observation such as an image. DSCMs therefore decompose the mechanism into a high-level non-invertible part and a low-level invertible part:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AX_i=h_i%5C!%5Cleft(U_i;%5C,g_i(Z_i,%5Coperatorname%7Bpa%7D_i),%5Coperatorname%7Bpa%7D_i%5Cright),%5Cqquad%5Cepsilon_i=(U_i,Z_i),%0A"></p>
<p>with independent priors <img src="https://latex.codecogs.com/png.latex?P(U_i)P(Z_i)">. The roles are:</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?g_i"> captures high-level structure and may be a neural decoder.</li>
<li><img src="https://latex.codecogs.com/png.latex?h_i"> is an invertible low-level transformation, often a conditional affine flow.</li>
<li><img src="https://latex.codecogs.com/png.latex?Z_i"> is a high-level latent noise variable.</li>
<li><img src="https://latex.codecogs.com/png.latex?U_i"> is low-level residual noise that can be inverted exactly.</li>
</ul>
<p>Conditioned on <img src="https://latex.codecogs.com/png.latex?Z_i">, the low-level likelihood remains tractable:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap(x_i%5Cmid%20z_i,%5Coperatorname%7Bpa%7D_i)=p_U%5C!%5Cleft(h_i%5E%7B-1%7D(x_i;g_i(z_i,%5Coperatorname%7Bpa%7D_i),%5Coperatorname%7Bpa%7D_i)%5Cright)%5Cleft%7C%5Cdet%5Cfrac%7B%5Cpartial%20h_i%5E%7B-1%7D%7D%7B%5Cpartial%20x_i%7D%5Cright%7C.%0A"></p>
<p>However, <img src="https://latex.codecogs.com/png.latex?Z_i"> must be marginalized to obtain <img src="https://latex.codecogs.com/png.latex?p(x_i%5Cmid%5Coperatorname%7Bpa%7D_i)">, which is generally intractable. DSCM introduces an amortized posterior <img src="https://latex.codecogs.com/png.latex?Q_%5Ceta(z_i%5Cmid%20x_i,%5Coperatorname%7Bpa%7D_i)">, usually produced by an encoder, and optimizes the conditional ELBO:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Clog%20p(x_i%5Cmid%5Coperatorname%7Bpa%7D_i)%5Cge%5Cmathbb%20E_%7BQ_%5Ceta%7D%5Cleft%5B%5Clog%20p(x_i%5Cmid%20z_i,%5Coperatorname%7Bpa%7D_i)%5Cright%5D-D_%7B%5Cmathrm%7BKL%7D%7D%5Cleft%5BQ_%5Ceta(z_i%5Cmid%20x_i,%5Coperatorname%7Bpa%7D_i)%5C,%5C%7C%5C,P(Z_i)%5Cright%5D.%0A"></p>
<p>This is the DSCM’s key construction for structured high-dimensional data: the decoder handles global structure, the low-level flow preserves an exactly invertible residual, and the encoder supplies approximate abduction for the high-level noise.</p>
</section>
<section id="amortised-implicit-mechanisms" class="level4">
<h4 class="anchored" data-anchor-id="amortised-implicit-mechanisms">Amortised implicit mechanisms</h4>
<p>The third class removes the explicit-likelihood requirement. A non-invertible mechanism <img src="https://latex.codecogs.com/png.latex?f_j"> is paired with an encoder <img src="https://latex.codecogs.com/png.latex?e_j(x_j;%5Coperatorname%7Bpa%7D_j)"> and trained adversarially. The discriminator distinguishes encoded real tuples</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(x_j,e_j(x_j;%5Coperatorname%7Bpa%7D_j),%5Coperatorname%7Bpa%7D_j)%0A"></p>
<p>from generated tuples</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(f_j(%5Cepsilon_j;%5Coperatorname%7Bpa%7D_j),%5Cepsilon_j,%5Coperatorname%7Bpa%7D_j).%0A"></p>
<p>This class can represent flexible mechanisms, but it does not provide an exact likelihood or exact noise inversion. It is therefore a different tractability trade-off from the flow-based and amortised-explicit classes.</p>
</section>
<section id="mathematical-training-objective" class="level4">
<h4 class="anchored" data-anchor-id="mathematical-training-objective">Mathematical training objective</h4>
<p>Let <img src="https://latex.codecogs.com/png.latex?%5CTheta_i"> denote the parameters of mechanism <img src="https://latex.codecogs.com/png.latex?f_i">. Under the Markovian SCM assumption, the observational distribution factorizes over the graph:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap_%5CTheta(x)=%5Cprod_%7Bi=1%7D%5E%7BK%7Dp_%7B%5CTheta_i%7D(x_i%5Cmid%5Coperatorname%7Bpa%7D_i).%0A"></p>
<p>The training objective is therefore a sum of mechanism objectives,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%20L_%7B%5Cmathrm%7BDSCM%7D%7D(%5CTheta)=-%5Csum_%7Bi=1%7D%5E%7BK%7D%5Cmathbb%20E_%7B%5Cmathcal%20D%7D%5Cleft%5B%5Cell_i(%5CTheta_i;x_i,%5Coperatorname%7Bpa%7D_i)%5Cright%5D,%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cell_i"> depends on the mechanism class. For an invertible explicit mechanism,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cell_i=%5Clog%20p_%7B%5CTheta_i%7D(x_i%5Cmid%5Coperatorname%7Bpa%7D_i)%0A"></p>
<p>is the exact change-of-variables log-likelihood. For an amortised explicit mechanism, the contribution is its conditional ELBO,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cell_i=%5Cmathbb%20E_%7BQ_%7B%5Ceta_i%7D(z_i%5Cmid%20x_i,%5Coperatorname%7Bpa%7D_i)%7D%5Cleft%5B%5Clog%20p_%7B%5CTheta_i%7D(x_i%5Cmid%20z_i,%5Coperatorname%7Bpa%7D_i)%5Cright%5D-D_%7B%5Cmathrm%7BKL%7D%7D%5C!%5Cleft(Q_%7B%5Ceta_i%7D(z_i%5Cmid%20x_i,%5Coperatorname%7Bpa%7D_i)%5C,%5C%7C%5C,P(Z_i)%5Cright).%0A"></p>
<p>For an amortised implicit mechanism, <img src="https://latex.codecogs.com/png.latex?%5Cell_i"> is replaced by the corresponding conditional adversarial objective. Thus, Deep SCM training is one joint objective over the chosen mechanisms; it is not a universal sequence of parent-model and image-model stages. In the amortised explicit case, <img src="https://latex.codecogs.com/png.latex?g_i">, <img src="https://latex.codecogs.com/png.latex?h_i">, and the encoder parameters <img src="https://latex.codecogs.com/png.latex?%5Ceta_i"> are learned together through the equation above.</p>
</section>
<section id="counterfactual-inference" class="level4">
<h4 class="anchored" data-anchor-id="counterfactual-inference"><strong>Counterfactual inference</strong></h4>
<p>The trained mechanisms implement the same abduction–action–prediction sequence introduced above. For each observed node <img src="https://latex.codecogs.com/png.latex?X_i=x_i">, use an exact inverse when available, infer the high-level latent with <img src="https://latex.codecogs.com/png.latex?Q_%5Ceta"> and invert <img src="https://latex.codecogs.com/png.latex?h_i"> for an amortised explicit mechanism, or use the encoder representation for an amortised implicit mechanism. The resulting noise estimates are reused after the intervention; changing them would generate a different unit rather than a counterfactual of the observed one.</p>
<p>Mathematically, let <img src="https://latex.codecogs.com/png.latex?%5Cwidehat%7B%5Cepsilon%7D"> denote the factual noise inferred from <img src="https://latex.codecogs.com/png.latex?x">. The intervention changes only the structural assignment for <img src="https://latex.codecogs.com/png.latex?A">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Af_A%5E%7B%5C,do%7D(%5Cepsilon_A;%5Coperatorname%7Bpa%7D_A)=%5Cwidetilde%20a,%5Cqquad%20f_i%5E%7B%5C,do%7D=f_i%5Cquad(i%5Cneq%20A).%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?F_%7BG_%7Bdo%7D%7D"> means evaluating the modified assignments in topological order. For an amortised explicit node, <img src="https://latex.codecogs.com/png.latex?%5Cwidehat%20z_i"> is sampled from <img src="https://latex.codecogs.com/png.latex?Q_%7B%5Ceta_i%7D"> and <img src="https://latex.codecogs.com/png.latex?%5Cwidehat%20u_i"> is then obtained by inverting <img src="https://latex.codecogs.com/png.latex?h_i">; for a flow node, <img src="https://latex.codecogs.com/png.latex?%5Cwidehat%5Cepsilon_i"> is obtained directly by (B.2). The only approximation in the idealized formula comes from the variational or encoder-based abduction used by non-invertible mechanisms.</p>
</section>
<section id="algorithm-fitting-and-using-a-dscm" class="level4">
<h4 class="anchored" data-anchor-id="algorithm-fitting-and-using-a-dscm"><strong>Algorithm: fitting and using a DSCM</strong></h4>
<p>The following pseudocode summarizes the mechanism-level procedure in DSCMs. The mechanism type may be invertible explicit, amortised explicit, or amortised implicit; the corresponding objective and abduction operation are selected accordingly.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown"><span id="cb1-1"></span>
<span id="cb1-2">Algorithm 1: Deep Structural Causal Model</span>
<span id="cb1-3"></span>
<span id="cb1-4">Input:</span>
<span id="cb1-5">    DAG G = (V, E)</span>
<span id="cb1-6">    Observational samples D = {x^(n)}</span>
<span id="cb1-7">    Mechanism type M_i for every variable X_i</span>
<span id="cb1-8"></span>
<span id="cb1-9">Training:</span>
<span id="cb1-10">    for every variable X_i, define X_i = f_i(epsilon_i ; pa_i)</span>
<span id="cb1-11">    choose the mechanism-specific contribution ell_i</span>
<span id="cb1-12">        (exact likelihood, ELBO, or adversarial objective)</span>
<span id="cb1-13">    jointly maximize the sum of all ell_i terms</span>
<span id="cb1-14"></span>
<span id="cb1-15">Counterfactual query: do(A := a_tilde) for factual x</span>
<span id="cb1-16">    # Abduction</span>
<span id="cb1-17">    for each variable X_i in topological order do</span>
<span id="cb1-18">        if f_i is invertible then</span>
<span id="cb1-19">            epsilon_i &lt;- f_i^{-1}(x_i ; pa_i)</span>
<span id="cb1-20">        else if f_i is amortised explicit then</span>
<span id="cb1-21">            sample or infer z_i ~ Q_eta_i(. | x_i, pa_i)</span>
<span id="cb1-22">            u_i &lt;- h_i^{-1}(x_i ; g_i(z_i, pa_i), pa_i)</span>
<span id="cb1-23">        else</span>
<span id="cb1-24">            epsilon_i &lt;- e_i(x_i, pa_i)       # approximate abduction</span>
<span id="cb1-25">        end if</span>
<span id="cb1-26">    end for</span>
<span id="cb1-27">        # Action and prediction</span>
<span id="cb1-28">    replace the assignment for A by A := a_tilde</span>
<span id="cb1-29">    for each descendant X_i in topological order do</span>
<span id="cb1-30">        x_i_tilde &lt;- f_i(epsilon_i ; pa_i_tilde)</span>
<span id="cb1-31">    end for</span>
<span id="cb1-32"></span>
<span id="cb1-33">Output: counterfactual sample x_tilde</span></code></pre></div></div>
<p>The pseudocode makes the division of labour explicit: the training objective is joint over the selected mechanisms, while the abduction operation depends on each mechanism’s invertibility. DSCM is therefore a mechanism-level framework whose defining choice is the trade-off between expressive structural assignments and tractable abduction.</p>
</section>
</section>
</section>
<section id="c.-causal-gen-high-fidelity-image-counterfactuals" class="level2">
<h2 class="anchored" data-anchor-id="c.-causal-gen-high-fidelity-image-counterfactuals"><strong>C. Causal-Gen: High-fidelity Image Counterfactuals</strong></h2>
<p>DSCM makes neural structural mechanisms counterfactual-capable: it provides a graph of learned assignments, tractable or amortised abduction, and reuse of factual exogenous noise after an intervention. That solves the problem of <strong>counterfactual validity</strong>, but it does not guarantee <strong>counterfactual effectiveness</strong> for high-fidelity images.</p>
<p>There are three practical failure modes. First, the image likelihood can be optimized mainly through latent variables or pixel residuals, so changing a causal parent produces a plausible reconstruction but barely changes the requested attribute. Second, a high-dimensional decoder can bypass the intended causal pathway: the graph says that a parent should change, yet the generated image hides that change in an unconstrained latent or residual channel. Third, forcing the parent change strongly enough to become visible can damage identity, texture, or other factual details. The generic abduction–action–prediction recipe tells us how to construct a valid counterfactual, but it supplies no separate observable loss that measures whether the requested visual effect is actually present.</p>
<p>Causal-Gen (<a href="https://arxiv.org/pdf/2306.15764">Ribeiro et al.&nbsp;2023</a>) is needed precisely to close this gap. It retains the DSCM semantics while adding a practical training organization through modular staged optimization: a low-dimensional causal parent PGM makes interventions explicit, supervised image-to-parent predictors measure the attributes expressed by generated images, and a fourth, counterfactual fine-tuning process improves those measured effects without abandoning factual fidelity.</p>
<p>Below are the detailed training stages; each can run independently / in parallel, except the finetuning.</p>
<section id="c.1.-training-stage-1-causal-parent-pgm" class="level3">
<h3 class="anchored" data-anchor-id="c.1.-training-stage-1-causal-parent-pgm"><strong>C.1. Training stage 1: Causal parent PGM</strong></h3>
<p>The first process learns the low-dimensional causal distribution that will drive the image mechanism. Let <img src="https://latex.codecogs.com/png.latex?P=(P_1,%5Cldots,P_K)"> denote the endogenous parent variables. Causal-Gen factorizes their distribution according to the chosen DAG,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap_%5Comega(P)=%5Cprod_%7Bk=1%7D%5E%7BK%7Dp_%7B%5Comega_k%7D(P_k%5Cmid%5Coperatorname%7Bpa%7D_k),%0A"></p>
<p>and represents each continuous mechanism with an invertible map,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP_k=g_%7B%5Comega_k%7D(U_k;%5Coperatorname%7Bpa%7D_k),%5Cqquad%20U_k%5Csim%20p_%7BU_k%7D.%0A"></p>
<p>The parent objective is maximum likelihood,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%20L_%7B%5Cmathrm%7BPGM%7D%7D(%5Comega)=-%5Cmathbb%20E_%7BP%5Csim%5Cmathcal%20D%7D%5Cleft%5B%5Csum_%7Bk=1%7D%5E%7BK%7D%5Clog%20p_%7B%5Comega_k%7D(P_k%5Cmid%5Coperatorname%7Bpa%7D_k)%5Cright%5D.%0A"></p>
<p>For a continuous mechanism, the density is evaluated by change of variables and abduction uses the inverse <img src="https://latex.codecogs.com/png.latex?u_k=g_%7B%5Comega_k%7D%5E%7B-1%7D(p_k;%5Coperatorname%7Bpa%7D_k)">. Categorical parents use a categorical mechanism rather than a continuous flow. The resulting PGM determines how an intervention propagates to descendants; it does not generate pixels.</p>
</section>
<section id="c.2.-training-stage-2-supervised-anticausal-predictors" class="level3">
<h3 class="anchored" data-anchor-id="c.2.-training-stage-2-supervised-anticausal-predictors"><strong>C.2. Training stage 2: Supervised anticausal predictors</strong></h3>
<p>The second process learns predictors in the reverse observational direction. Given an image <img src="https://latex.codecogs.com/png.latex?X">, a predictor estimates the parent attributes expressed by that image,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aq_%5Cpsi(P%5Cmid%20X)=%5Cprod_%7Bk=1%7D%5E%7BK%7Dq_%7B%5Cpsi_k%7D(P_k%5Cmid%20X,%5Coperatorname%7Bchild%7D(P_k)).%0A"></p>
<p>The predictor objective is supervised negative log-likelihood,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%20L_%7B%5Cmathrm%7Bpred%7D%7D(%5Cpsi)=-%5Cmathbb%20E_%7B(X,P)%5Csim%5Cmathcal%20D%7D%5Clog%20q_%5Cpsi(P%5Cmid%20X).%0A"></p>
<p>These predictors are not structural causal mechanisms and do not replace the PGM. They serve two roles: infer attributes from a factual image when those attributes are not supplied, and provide a frozen, differentiable measurement of whether a generated counterfactual expresses the requested parent values. This is a practical addition by (Ribeiro et al.&nbsp;2023) beyond the generic DSCM formulation.</p>
</section>
<section id="c.3.-training-stage-3-conditional-hierarchical-image-model" class="level3">
<h3 class="anchored" data-anchor-id="c.3.-training-stage-3-conditional-hierarchical-image-model"><strong>C.3. Training stage 3: Conditional hierarchical image model</strong></h3>
<p>The third process learns the high-dimensional observation mechanism conditioned on the causal parents. Let <img src="https://latex.codecogs.com/png.latex?Z_%7B1:L%7D"> be the hierarchical latent variables and <img src="https://latex.codecogs.com/png.latex?X"> the image:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AZ_%7B1:L%7D%5Csim%20p_%5Ctheta(z_%7B1:L%7D%5Cmid%20P),%5Cqquad%20X%5Csim%20p_%5Ctheta(x%5Cmid%20Z_%7B1:L%7D,P).%0A"></p>
<p>The latent prior is factorized top-down,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap_%5Ctheta(z_%7B1:L%7D%5Cmid%20P)=p_%5Ctheta(z_L%5Cmid%20P)%5Cprod_%7B%5Cell=1%7D%5E%7BL-1%7Dp_%5Ctheta(z_%5Cell%5Cmid%20z_%7B%3E%5Cell%7D,P),%0A"></p>
<p>and an inference network approximates the factual posterior,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aq_%5Cphi(z_%7B1:L%7D%5Cmid%20X,P)=q_%5Cphi(z_L%5Cmid%20X,P)%5Cprod_%7B%5Cell=1%7D%5E%7BL-1%7Dq_%5Cphi(z_%5Cell%5Cmid%20z_%7B%3E%5Cell%7D,X,P).%0A"></p>
<p>The image model is trained with the conditional negative ELBO,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%20L_%7B%5Cmathrm%7BHVAE%7D%7D(%5Ctheta,%5Cphi)=-%5Cmathbb%20E_%7Bq_%5Cphi(Z%5Cmid%20X,P)%7D%5Cleft%5B%5Clog%20p_%5Ctheta(X%5Cmid%20Z,P)%5Cright%5D+%5Cbeta%20D_%7B%5Cmathrm%7BKL%7D%7D%5Cleft(q_%5Cphi(Z%5Cmid%20X,P)%5C,%5C%7C%5C,p_%5Ctheta(Z%5Cmid%20P)%5Cright).%0A"></p>
<p>This is the image-generation component inherited from the amortised explicit Deep SCM idea: the encoder provides approximate abduction of high-level latent noise, while the decoder models the conditional image likelihood. In Causal-Gen, the image model is trained as its own process using factual image–parent pairs; it does not learn the parent PGM or the supervised predictors.</p>
</section>
<section id="c.4.-training-stage-4-counterfactual-fine-tuning" class="level3">
<h3 class="anchored" data-anchor-id="c.4.-training-stage-4-counterfactual-fine-tuning">C.4. Training stage 4: Counterfactual fine-tuning</h3>
<p>The fourth process is a principal practical addition. A generic image likelihood can be good even when changing a parent produces little visible change, because latent variables or residual pathways may explain away the intervention. Causal-Gen addresses this gap with a <strong>constrained alignment layer</strong>: the parent PGM specifies the causal change, the image model renders it, the predictors measure whether it is visible, and a fidelity constraint protects the factual identity. The PGM and predictors remain fixed; only the image-model parameters are adjusted so these signals agree.</p>
<p>For an intervention producing <img src="https://latex.codecogs.com/png.latex?%5Cwidetilde%20P">, let <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%20L_%7B%5Cmathrm%7Bcf%7D%7D"> be the frozen predictors’ negative log-probability of the requested counterfactual attributes. Fine-tuning solves a constrained problem,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmin_%7B%5Ctheta,%5Cphi%7D%5C;%5Cmathcal%20L_%7B%5Cmathrm%7Bcf%7D%7D(%5Ctheta,%5Cphi;%5Cpsi)%5Cquad%5Ctext%7Bsubject%20to%7D%5Cquad%5Cmathcal%20L_%7B%5Cmathrm%7BHVAE%7D%7D(%5Ctheta,%5Cphi)%5Cleq%5Ctau,%0A"></p>
<p>or its Lagrangian form,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%20J(%5Ctheta,%5Cphi,%5Clambda)=%5Cmathcal%20L_%7B%5Cmathrm%7Bcf%7D%7D+%5Clambda%5Cleft(%5Cmathcal%20L_%7B%5Cmathrm%7BHVAE%7D%7D-%5Ctau%5Cright),%5Cqquad%20%5Clambda%5Cgeq0.%0A"></p>
<p>The constraint prevents the effectiveness objective from producing an image that satisfies the requested attribute but no longer represents the factual individual. If the pretrained image model already expresses interventions clearly, the alignment stage makes only small updates; it is needed when observational likelihood leaves a gap between causal intent and visible effect.</p>
</section>
<section id="c.5-inference-abduction-intervention-and-rendering" class="level3">
<h3 class="anchored" data-anchor-id="c.5-inference-abduction-intervention-and-rendering"><strong>C.5 Inference: abduction, intervention, and rendering</strong></h3>
<p>After the four training processes, Causal-Gen composes the learned components for a counterfactual query. Given a factual image <img src="https://latex.codecogs.com/png.latex?X=x">, factual parents <img src="https://latex.codecogs.com/png.latex?P=p">, and intervention <img src="https://latex.codecogs.com/png.latex?do(P_A:=%5Cwidetilde%20p_A)">:</p>
<ol type="1">
<li><strong>Abduce parent noise.</strong> Invert each continuous PGM mechanism to obtain <img src="https://latex.codecogs.com/png.latex?u_k=g_%7B%5Comega_k%7D%5E%7B-1%7D(p_k;%5Coperatorname%7Bpa%7D_k)">; infer missing factual attributes with <img src="https://latex.codecogs.com/png.latex?q_%5Cpsi(P%5Cmid%20X)"> when necessary.</li>
<li><strong>Abduce image latents and residual.</strong> Infer or sample <img src="https://latex.codecogs.com/png.latex?Z%5Csim%20q_%5Cphi(Z%5Cmid%20x,p)">. Decode the factual state to obtain <img src="https://latex.codecogs.com/png.latex?(%5Cmu_%5Ctheta,%5Csigma_%5Ctheta)"> and compute</li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?%0Au_X=(x-%5Cmu_%5Ctheta(Z,p))%5Coslash%5Csigma_%5Ctheta(Z,p).%0A"></p>
<ol type="1">
<li><strong>Apply the intervention.</strong> Replace the selected parent mechanism and propagate all unchanged mechanisms with the same parent noises, producing <img src="https://latex.codecogs.com/png.latex?%5Cwidetilde%20p">.</li>
<li><strong>Select the effect.</strong> For a direct effect, retain the factual <img src="https://latex.codecogs.com/png.latex?Z">; for a total effect, evaluate the latent mechanism under <img src="https://latex.codecogs.com/png.latex?%5Cwidetilde%20p"> while reusing the same latent exogenous noise.</li>
<li><strong>Render.</strong> Decode under the intervened parents and reuse the factual residual:</li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cwidetilde%20x%20%20%20=%5Cwidetilde%5Cmu_%5Ctheta(%5Cwidetilde%20Z,%5Cwidetilde%20p)%20%20%20+%5Cwidetilde%5Csigma_%5Ctheta(%5Cwidetilde%20Z,%5Cwidetilde%20p)%5Codot%20u_X.%0A"></p>
<p>The low-dimensional PGM supplies causal propagation, the HVAE supplies high-dimensional image generation, the predictors measure whether the requested effect is visible, and fine-tuning aligns effectiveness with factual fidelity. This is how Causal-Gen extends the DSCM foundation without changing its abduction–action–prediction semantics.</p>
</section>
</section>
<section id="d.-causal-gen-implementation-on-morphomnist" class="level2">
<h2 class="anchored" data-anchor-id="d.-causal-gen-implementation-on-morphomnist"><strong>D. Causal-Gen Implementation on MorphoMNIST</strong></h2>
<p>Now let us dive deeper into how Causal-Gen is applied to the MorphoMNIST image dataset. We will move from the dataset’s measured attributes to the four Causal-Gen components, and then ask how an intervention can change one attribute while preserving the rest of the observed digit.</p>
<p>MorphoMNIST is an MNIST-derived grayscale-digit benchmark augmented with morphometric measurements (<a href="https://arxiv.org/pdf/1809.10780">Castro et al.&nbsp;2019</a>). Each example combines a raster digit, its class label, and measurements that describe how that digit is drawn. This pairing matters: the class label identifies the digit, whereas thickness and intensity provide continuous attributes on which Causal-Gen can intervene and condition the image mechanism.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-08-07-counterfactual-image-generative-models/media/Screenshot_2026-08-07_at_16.13.11.png" class="img-fluid figure-img"></p>
<figcaption>MorphoMNIST counterfactual modeling overview</figcaption>
</figure>
</div>
<p>The raw observation is a single-channel <img src="https://latex.codecogs.com/png.latex?28%5Ctimes28"> digit image. The benchmark is deliberately small enough to inspect directly, yet it separates a full pixel realization from interpretable, low-dimensional attributes. That separation is what lets Causal-Gen distinguish “draw another plausible digit” from “change this particular digit under an intervention.”</p>
<section id="d.1.-morphomnist-causal-variables" class="level3">
<h3 class="anchored" data-anchor-id="d.1.-morphomnist-causal-variables">D.1. MorphoMNIST Causal Variables</h3>
<p>For Causal-Gen, the digit label is the categorical variable <img src="https://latex.codecogs.com/png.latex?Y">, alongside two measured morphology variables: thickness <img src="https://latex.codecogs.com/png.latex?T"> and intensity <img src="https://latex.codecogs.com/png.latex?I">. In the implemented low-dimensional PGM, <img src="https://latex.codecogs.com/png.latex?Y"> and <img src="https://latex.codecogs.com/png.latex?T"> are separate parent variables, and thickness drives intensity; the image-parent contract then makes all three variables direct inputs to <img src="https://latex.codecogs.com/png.latex?X">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AY%20%5Clongrightarrow%20X,%5Cqquad%20T%20%5Clongrightarrow%20I,%5Cqquad%20T%20%5Clongrightarrow%20X,%5Cquad%20I%20%5Clongrightarrow%20X,%20%5C%5C%20p(Y,T,I)=p(Y)p(T)p(I%5Cmid%20T).%0A"></p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    UY(("digit noise U_Y")) --&gt; Y(("digit Y"))
    UT(("thickness noise U_T")) --&gt; T(("thickness T"))
    UI(("intensity noise U_I")) --&gt; I(("intensity I"))
    T --&gt; I
    Y --&gt; X(("image X"))
    T --&gt; X
    I --&gt; X
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>Here, <strong>thickness</strong> is the width-like morphological measurement and <strong>intensity</strong> is the brightness measurement recorded alongside the image. Thus an intervention such as <img src="https://latex.codecogs.com/png.latex?do(T=t')"> means “set the digit’s thickness to <img src="https://latex.codecogs.com/png.latex?t%5E%5Cprime">”; intensity is then a downstream property that should be regenerated consistently. The digit <img src="https://latex.codecogs.com/png.latex?Y"> is also a direct parent of the image <img src="https://latex.codecogs.com/png.latex?X">, so changing the class can change the rendered digit even when the morphology values are held fixed. The continuous attributes are normalized to <img src="https://latex.codecogs.com/png.latex?%5B-1,1%5D">, and the image mechanism receives the parent state <img src="https://latex.codecogs.com/png.latex?(T,I,Y)">, with <img src="https://latex.codecogs.com/png.latex?Y"> represented by its ten digit classes.</p>
<p>The Causal-Gen setting treats <img src="https://latex.codecogs.com/png.latex?(T,I,Y)"> as the low-dimensional parent state for the image mechanism. The parent PGM propagates thickness to intensity, the image model maps all three parents directly to pixels, and the predictor checks whether the requested attribute is visible in those pixels.</p>
</section>
<section id="d.2.-code-implementation-with-jax" class="level3">
<h3 class="anchored" data-anchor-id="d.2.-code-implementation-with-jax">D.2. Code Implementation with JAX</h3>
<p>The code implementation of Causal Gen is provided by the original authors, written in <a href="https://pytorch.org/">PyTorch</a> and <a href="https://pyro.ai/examples/intro_long.html">Pyro</a>: <a href="https://github.com/biomedia-mira/causal-gen">https://github.com/biomedia-mira/causal-gen</a>. It already provides the complete recipe: the parent PGM, supervised image-to-attribute predictors, conditional image model, counterfactual fine-tuning, and inference.</p>
<p>We port the code into a pure JAX version to make the same workflow practical across CPUs, GPUs, and TPUs through one compiled array-programming stack: <a href="https://github.com/ghif/causal-genx">https://github.com/ghif/causal-genx</a>. JAX/XLA can compile the numerically intensive training steps, while explicit parameter trees, optimizer state, random keys, moving averages, and checkpoint state make accelerator exectuion and multi-device replication easier to reason about. The rewrite also removes the Pyro runtime dependency from deployment and creates a native Flax/Optax/Orbax implementation that can be tested against the original numerical behaviour.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th><strong>Stage</strong></th>
<th><strong>Artifact</strong></th>
<th><strong>Purpose on MorphoMNIST</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1</td>
<td>Parent SCM</td>
<td>Fits the thickness and intensity mechanisms used to make intervened parent vectors.</td>
</tr>
<tr class="even">
<td>2</td>
<td>Image-to-parent predictor</td>
<td>Reads thickness, intensity, and digit from an image; used as an effectiveness critic.</td>
</tr>
<tr class="odd">
<td>3</td>
<td>Conditional HVAE</td>
<td>Generates&nbsp;<img src="https://latex.codecogs.com/png.latex?X%5E*">&nbsp;from the 12-value parent vector while retaining image-level variation in latents.</td>
</tr>
<tr class="even">
<td>4</td>
<td>Counterfactual fine-tuning</td>
<td>Updates only the HVAE so edited images satisfy the frozen predictor without losing reconstruction quality.</td>
</tr>
</tbody>
</table>
<section id="stage-1---fit-the-parent-scm" class="level4">
<h4 class="anchored" data-anchor-id="stage-1---fit-the-parent-scm">Stage 1 - Fit the parent SCM</h4>
<p>For observed <img src="https://latex.codecogs.com/png.latex?(t,i,y)">, the PGM minimizes the negative log likelihood of the implemented mechanisms:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%20L_%7B%5Crm%20scm%7D=-%5Cmathbb%20E%5B%5Clog%20p(Y)+%5Clog%20p(T)+%5Clog%20p(I%5Cmid%20T)%5D.%0A"></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> scm_loss(graphdef, params, batch):</span>
<span id="cb2-2">    model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> materialize_nnx(graphdef, params)</span>
<span id="cb2-3">    logp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.log_prob(</span>
<span id="cb2-4">        batch[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thickness"</span>], batch[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"intensity"</span>], batch[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"digit"</span>]</span>
<span id="cb2-5">    )</span>
<span id="cb2-6">    loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>jnp.mean(logp[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"joint"</span>])</span>
<span id="cb2-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> loss, {<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"logp(</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span>: jnp.mean(logp[name])</span>
<span id="cb2-8">                  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"digit"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thickness"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"intensity"</span>)}</span></code></pre></div></div>
<p>Architecturally, the parent model is a hybrid rather than a pure neural network: digit uses learned categorical logits, thickness uses a monotonic rational-quadratic spline flow, and intensity uses an affine conditional flow whose location and scale are predicted by a small two-hidden-layer dense network (an MLP). The Flax NNX module keeps these pieces in one parameter graph. A compact architecture sketch is:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> ParentSCM(nnx.Module):</span>
<span id="cb3-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, rngs):</span>
<span id="cb3-3">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.digit_logits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nnx.Param(jnp.zeros((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>)))</span>
<span id="cb3-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.unnormalized_widths <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nnx.Param(jax.random.normal(rngs.params(), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)))</span>
<span id="cb3-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.unnormalized_heights <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nnx.Param(jax.random.normal(rngs.params(), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)))</span>
<span id="cb3-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.unnormalized_derivatives <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nnx.Param(jax.random.normal(rngs.params(), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)))</span>
<span id="cb3-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.context0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nnx.Linear(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>, rngs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rngs)</span>
<span id="cb3-8">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.context1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nnx.Linear(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>, rngs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rngs)</span>
<span id="cb3-9">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.context_out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nnx.Linear(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, rngs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rngs)</span>
<span id="cb3-10"></span>
<span id="cb3-11">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> intensity_params(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, thickness):</span>
<span id="cb3-12">        h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.nn.gelu(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.context0(thickness[..., <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>]))</span>
<span id="cb3-13">        h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.nn.gelu(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.context1(h))</span>
<span id="cb3-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> jnp.split(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.context_out(h), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span></code></pre></div></div>
<p>At counterfactual time the SCM first recovers the factual noise for thickness and intensity. Reusing intensity’s noise after <img src="https://latex.codecogs.com/png.latex?do(T=t')"> produces the descendant intensity <img src="https://latex.codecogs.com/png.latex?I'"> associated with the requested thickness, rather than copying the factual intensity.</p>
</section>
<section id="stage-2-train-the-predictor-image-critic" class="level4">
<h4 class="anchored" data-anchor-id="stage-2-train-the-predictor-image-critic"><strong>Stage 2 — Train the predictor (image critic)</strong></h4>
<p>The predictor is not a second causal model. It learns to estimate the MorphoMNIST variables from pixels: <img src="https://latex.codecogs.com/png.latex?q(T%5Cmid%20X,I)">, <img src="https://latex.codecogs.com/png.latex?q(I%5Cmid%20X)">, and <img src="https://latex.codecogs.com/png.latex?q(Y%5Cmid%20X)">. The conditioning <img src="https://latex.codecogs.com/png.latex?I"> in the thickness head follows the dataset’s <img src="https://latex.codecogs.com/png.latex?T%5Crightarrow%20I"> relationship. Its supervised negative log likelihood makes it a differentiable check that a generated image visibly has the requested thickness, intensity, and digit.</p>
<p>The predictor is a convolutional neural network: each <code>CNNEncoder</code> uses six strided convolutional blocks, BatchNorm, global spatial averaging, and dense output layers. The thickness head receives intensity as a one-dimensional context; the intensity and digit heads use the image alone. It minimizes the supervised negative log likelihood of the three attributes. There is no KL term here; the predicted scales are made positive and the CNN’s BatchNorm statistics are carried as mutable state:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> predictor_loss(model, batch):</span>
<span id="cb4-2">    logp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.anticausal_log_probs(</span>
<span id="cb4-3">        x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>batch[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span>], thickness<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>batch[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thickness"</span>],</span>
<span id="cb4-4">        intensity<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>batch[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"intensity"</span>], digit<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>batch[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"digit"</span>],</span>
<span id="cb4-5">    )</span>
<span id="cb4-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>jnp.mean(logp[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"joint"</span>]), logp</span></code></pre></div></div>
<p>The three heads share the convolutional encoder pattern but expose the graph-aware inputs required by the predictors:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> MorphoPredictor(nnx.Module):</span>
<span id="cb5-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, rngs):</span>
<span id="cb5-3">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.thickness <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> CNNEncoder(num_outputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, context_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, rngs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rngs)</span>
<span id="cb5-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.intensity <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> CNNEncoder(num_outputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, context_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, rngs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rngs)</span>
<span id="cb5-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.digit <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> CNNEncoder(num_outputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, context_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, rngs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rngs)</span>
<span id="cb5-6"></span>
<span id="cb5-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__call__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, intensity):</span>
<span id="cb5-8">        t_loc, t_raw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.split(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.thickness(x, intensity), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb5-9">        i_loc, i_raw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.split(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.intensity(x), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb5-10">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thickness"</span>: (jnp.tanh(t_loc), t_raw),</span>
<span id="cb5-11">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"intensity"</span>: (jnp.tanh(i_loc), i_raw),</span>
<span id="cb5-12">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"digit_logits"</span>: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.digit(x)}</span></code></pre></div></div>
</section>
<section id="stage-3-train-the-conditional-image-hvae" class="level4">
<h4 class="anchored" data-anchor-id="stage-3-train-the-conditional-image-hvae"><strong>Stage 3 — Train the conditional image HVAE</strong></h4>
<p>The HVAE is a hierarchical convolutional variational autoencoder. Its encoder and decoder are built from convolutional residual <code>Block</code>s at multiple resolutions; decoder blocks parameterize the Gaussian latent priors and posteriors, while <code>DGaussNet</code> is a convolutional output head for the discretized-Gaussian pixel likelihood. It models <img src="https://latex.codecogs.com/png.latex?p_%5Ctheta(X%5Cmid%20T,I,Y,Z)">, conditioning on measured morphology and digit while latent variables <img src="https://latex.codecogs.com/png.latex?Z"> retain stroke shape, placement, and other detail not represented by the three parents. Its objective is the usual reconstruction-plus-rate objective. The Flax module is composed from these three network families:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> ConditionalHVAE(nnx.Module):</span>
<span id="cb6-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, rngs, context_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>):</span>
<span id="cb6-3">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.encoder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Encoder(input_channels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, input_res<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>, rngs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rngs)</span>
<span id="cb6-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.decoder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Decoder(context_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>context_dim, rngs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rngs)</span>
<span id="cb6-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.likelihood <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> DGaussNet(input_channels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, rngs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rngs)</span>
<span id="cb6-6"></span>
<span id="cb6-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> loss(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, parents, beta<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, rng<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb6-8">        acts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.encoder(x)</span>
<span id="cb6-9">        features, stats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.decoder(</span>
<span id="cb6-10">            parents<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>parents, x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>acts, rng<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rng, training<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb6-11">        )</span>
<span id="cb6-12">        nll <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.likelihood.nll(features, x).mean()</span>
<span id="cb6-13">        kl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(jnp.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(s[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"kl"</span>], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)).mean()</span>
<span id="cb6-14">                 <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> s <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> stats)</span>
<span id="cb6-15">        kl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> kl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> np.prod(x.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:])</span>
<span id="cb6-16">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> nll <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> beta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> kl, {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"nll"</span>: nll, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"kl"</span>: kl}</span></code></pre></div></div>
<p>The HVAE regularizer is the hierarchical KL term, weighted by <img src="https://latex.codecogs.com/png.latex?%5Cbeta">. With <code>kl_free_bits &gt; 0</code>, each latent block applies a floor to its KL contribution before summation. The supplied MorphoMNIST configuration uses <code>cond_prior: true</code>, so the prior networks also receive the parent state.</p>
</section>
<section id="stage-4-fine-tune-for-an-effective-edit" class="level4">
<h4 class="anchored" data-anchor-id="stage-4-fine-tune-for-an-effective-edit"><strong>Stage 4 — Fine-tune for an effective edit</strong></h4>
<p>Stage 4 introduces no new architecture: it reuses the spline-plus-MLP PGM, convolutional predictor, and hierarchical convolutional HVAE. The SCM and predictor are frozen. For a factual batch, the code creates an intervened parent vector with the SCM, abducts the HVAE latents, renders with the intervened parents, and asks the predictor to recognize those new MorphoMNIST attributes. Only HVAE parameters and a non-negative Lagrange multiplier are optimized:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> counterfactual_loss(vae, pgm, predictor, batch, intervention,</span>
<span id="cb7-2">                        lmbda, beta, elbo_constraint, damping, rng):</span>
<span id="cb7-3">    vae_out, cf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> counterfactual_forward(</span>
<span id="cb7-4">        vae, pgm, batch, intervention, beta<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>beta, rng<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rng</span>
<span id="cb7-5">    )</span>
<span id="cb7-6">    aux_loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>jnp.mean(predictor.model_anticausal(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>cf)[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"joint"</span>])</span>
<span id="cb7-7">    constraint <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> elbo_constraint <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> vae_out[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"elbo"</span>]</span>
<span id="cb7-8">    loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> aux_loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> (</span>
<span id="cb7-9">        lmbda <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> damping <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> jax.lax.stop_gradient(constraint)</span>
<span id="cb7-10">    ) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> constraint</span>
<span id="cb7-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> loss, {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aux_loss"</span>: aux_loss, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"constraint"</span>: constraint}</span></code></pre></div></div>
<p>The auxiliary loss asks the frozen predictor to recognize the intervened attributes. The damped Lagrangian keeps the HVAE negative ELBO near its factual constraint; only the HVAE parameters and non-negative multiplier are optimized, while the PGM and predictor remain frozen. AdamW weight decay is applied in the optimizer transformation rather than added to any of these loss expressions.</p>
<p>In JAX, freezing the model weights is expressed by the arguments that a loss closes over and the arguments passed to <code>value_and_grad</code>. Stage 4 captures the PGM and predictor bundles as constants, so their parameters receive no gradient and no optimizer state:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> frozen_cf_loss(vae_params, lmbda, batch, intervention, rng):</span>
<span id="cb8-2">    vae <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Bundle(vae_graphdef, vae_params)       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># trainable argument</span></span>
<span id="cb8-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># pgm_bundle and predictor_bundle are captured, frozen values.</span></span>
<span id="cb8-4">    out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _cf_forward(</span>
<span id="cb8-5">        args, vae, pgm_bundle, predictor_bundle, batch, intervention, rng,</span>
<span id="cb8-6">        beta<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>args.beta, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>args.alpha, lmbda<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>lmbda,</span>
<span id="cb8-7">        cf_particles<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>args.cf_particles, training<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>,</span>
<span id="cb8-8">    )</span>
<span id="cb8-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> out[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"loss"</span>], out</span>
<span id="cb8-10"></span>
<span id="cb8-11">(loss, out), (vae_grads, lmbda_grads) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.value_and_grad(</span>
<span id="cb8-12">    frozen_cf_loss, argnums<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), has_aux<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb8-13">)(vae_params, lmbda, batch, intervention, rng)</span></code></pre></div></div>
</section>
<section id="example-of-counterfactual-query" class="level4">
<h4 class="anchored" data-anchor-id="example-of-counterfactual-query">Example of Counterfactual Query</h4>
<p>For a thickness edit, the operational sequence is simple:</p>
<ol type="1">
<li>Read factual <img src="https://latex.codecogs.com/png.latex?x,t,i,y"> and infer the SCM noise plus HVAE latents.</li>
<li>Apply <img src="https://latex.codecogs.com/png.latex?do(T=t')">.</li>
<li>Recompute <img src="https://latex.codecogs.com/png.latex?i'"> through the implemented thickness-to-intensity mechanism with the factual intensity noise held fixed.</li>
<li>Decode the factual latents under <img src="https://latex.codecogs.com/png.latex?%5Bt',i',y%5D"> and reuse the factual pixel residual.</li>
</ol>
<p>The result changes the morphology selected by the intervention while aiming to keep digit identity and incidental visual detail stable. A direct intervention on intensity, <img src="https://latex.codecogs.com/png.latex?do(I=i')">, instead bypasses that descendant calculation. A digit edit changes the digit parent supplied to the image model; as noted above, the current PGM does not automatically derive a new thickness from digit.</p>
</section>
</section>
<section id="d.3.-jax-training-and-inference-workflows" class="level3">
<h3 class="anchored" data-anchor-id="d.3.-jax-training-and-inference-workflows">D.3. JAX Training and Inference Workflows</h3>
<p>We provide an intuitive CLI for running the complete training pipeline with <a href="https://github.com/ghif/causal-genx"><code>causal-genx</code></a> by setting appropriate configurations. It is compatible to run on CPU, GPU, or TPU.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb9-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> causal-genx</span>
<span id="cb9-2"></span>
<span id="cb9-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Parent SCM/PGM</span></span>
<span id="cb9-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> scripts/run.py train-scm <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> configs/morphomnist_scm.yaml dataset.root=/path/to/morphomnist</span>
<span id="cb9-5"></span>
<span id="cb9-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Predictor</span></span>
<span id="cb9-7"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> scripts/run.py train-predictor <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> configs/morphomnist_predictor.yaml dataset.root=/path/to/morphomnist</span>
<span id="cb9-8"></span>
<span id="cb9-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># HVAE image generator</span></span>
<span id="cb9-10"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> scripts/run.py train-image-model <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> configs/morphomnist_image_model.yaml dataset.root=/path/to/morphomnist</span>
<span id="cb9-11"></span>
<span id="cb9-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Counterfactual finetuning</span></span>
<span id="cb9-13"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> scripts/run.py finetune-counterfactual <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> configs/morphomnist_counterfactual.yaml dataset.root=/path/to/morphomnist</span></code></pre></div></div>
<p>This workflow will generate a few trained model artifact that will be used later for the inference workflow / counterfactual image generation.</p>
<p>Our JAX version also improves steady-state throughput on simple benchmarks. On matched CPU settings, the parent PGM rises from 3,563 samples per second in the original PyTorch implementation to 25,685 samples per second in JAX (about 7.2x), while HVAE rises from 17.1 to 179.4 samples per second (about 10.5x).</p>
<p>That significant performance gain shows that the JAX implementation is a substantially optimized execution path for this workload, including compilation, batching, and state-management choices. They should not be read as a claim that “core JAX” is always faster than PyTorch: performance depends on the model, kernels, backend, configuration, threading, input pipeline, and warm-up policy — there may be a room for optimizing the speed of the original PyTorch implementation.</p>
<section id="scaling-from-one-tpu-core-to-a-v6e-4" class="level4">
<h4 class="anchored" data-anchor-id="scaling-from-one-tpu-core-to-a-v6e-4">Scaling from one TPU core to a v6e-4</h4>
<p>On TPU, the same HVAE workload reached about 382 samples/s on a single TPU v6e-1 at batch size 128 and about 7,418 samples/s on a four-chip v6e-4 at global batch size 512 after warm-up. This scale-up gain is achieved through the following strategy: replicate the model state once, split each global batch across the four chips, and average gradients inside the compiled step. Thus, a batch of 512 becomes four local batches of 128, while the optimizer and EMA update remain synchronous. The global batch must be divisible by the number of local devices:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1">use_tpu_pmap <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb10-2">    args.accelerator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tpu"</span></span>
<span id="cb10-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> jax.local_device_count() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb10-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> args.execution_mode <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"single_device"</span></span>
<span id="cb10-5">)</span>
<span id="cb10-6">devices <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.local_devices() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> use_tpu_pmap <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> []</span>
<span id="cb10-7">device_count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(devices) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> use_tpu_pmap <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb10-8"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> use_tpu_pmap <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> args.bs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> device_count:</span>
<span id="cb10-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValueError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"global batch must be divisible by local device count"</span>)</span></code></pre></div></div>
<p>The input pipeline reshapes the leading batch dimension and places the shards on a one-dimensional device mesh. Replicating parameters and optimizer state keeps the training loop free of host-side copies:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">mesh <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.sharding.Mesh(np.asarray(devices), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"devices"</span>,))</span>
<span id="cb11-2">sharding <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.sharding.NamedSharding(</span>
<span id="cb11-3">    mesh, jax.sharding.PartitionSpec(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"devices"</span>)</span>
<span id="cb11-4">)</span>
<span id="cb11-5"></span>
<span id="cb11-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> shard_batch(x):</span>
<span id="cb11-7">    per_device <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(devices)</span>
<span id="cb11-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> jax.device_put(</span>
<span id="cb11-9">        x.reshape((<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(devices), per_device) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> x.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:]), sharding</span>
<span id="cb11-10">    )</span></code></pre></div></div>
<p>The following is a shortened version of the repository’s <code>pmap</code> step. It performs the same loss and optimizer update as the single-device step, but uses collective reductions for data-parallel synchronization. The state is replicated before the first call and stays resident on the TPU; the production function also updates EMA parameters and skips unsafe non-finite updates:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> pmap_step(params, opt_state, batch, rng):</span>
<span id="cb12-2">    (loss, aux), grads <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.value_and_grad(loss_fn, has_aux<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)(</span>
<span id="cb12-3">        params, batch, rng</span>
<span id="cb12-4">    )</span>
<span id="cb12-5">    grads <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.lax.pmean(grads, axis_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"devices"</span>)</span>
<span id="cb12-6">    aux <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.tree_util.tree_map(</span>
<span id="cb12-7">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> value: jax.lax.pmean(value, axis_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"devices"</span>), aux</span>
<span id="cb12-8">    )</span>
<span id="cb12-9">    updates, opt_state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tx.update(grads, opt_state, params)</span>
<span id="cb12-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> optax.apply_updates(params, updates), opt_state, aux</span>
<span id="cb12-11"></span>
<span id="cb12-12">train_step <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.pmap(</span>
<span id="cb12-13">    pmap_step, axis_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"devices"</span>, in_axes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), devices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>devices</span>
<span id="cb12-14">)</span></code></pre></div></div>
<p>Only compact metrics are copied back to the host after each step (the trainer reads the first local replica), avoiding an unnecessary all-device gather.</p>
<p>The v6e-4 result is therefore a scaling outcome of replication, sharding, collective gradient averaging, and a larger global batch.</p>
</section>
</section>
<section id="d.4.-interactive-counterfactual-demo" class="level3">
<h3 class="anchored" data-anchor-id="d.4.-interactive-counterfactual-demo">D.4. Interactive Counterfactual Demo</h3>
<p>We also build a simple web prototype that turns the inference pipeline into a small demonstration of interactive, counterfactual image generation: <a href="https://ghif.github.io/causal-genx/">https://ghif.github.io/causal-genx/</a>. All the trained model checkpoints are stored in Google Cloud Storage (GCS), which are then served via a backend FastAPI service deployed on Cloud Run with a CPU-only machine. The backend APIs are then utilized by a web frontend in a Github service.</p>
<p>The browser-to-cloud request flow is:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    User["web browser"] --&gt; Pages["GitHub Pages&lt;br/&gt;static frontend"]
    Pages --&gt;|HTTPS + CORS| API["Cloud Run&lt;br/&gt;FastAPI service"]
    API --&gt; GCS["private GCS&lt;br/&gt;model checkpoints"]
    API --&gt; Result["PNG +&lt;br/&gt;factual/CF parents"]
    Result --&gt; Pages
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<video controls="" preload="metadata" width="100%">
<source src="media/counterfactual-digit-demo-compressed.mp4" type="video/mp4">
<p>Your browser does not support embedded video.</p>
<video controls="" preload="metadata" width="100%">
<source src="media/render-counterfactual.mov" type="video/quicktime">
<p>Your browser does not support embedded video.</p>
<p>In “<strong>Generate image</strong>” tab, the page sends digit, thickness, intensity, and seed values to the generation endpoint. The parent SCM can supply the linked intensity after a thickness change. In “<strong>Render counterfactual</strong>”, the page uploads or selects a 28 x 28 or 32 x 32 grayscale digit, asks the service to infer factual parents, and then submits one physical-unit intervention. The backend performs abduction, intervention, and rendering, returning a Base64 PNG together with factual and counterfactual parent values for display.</p>
</video></video></section>
</section>
<section id="conclusion-and-acknowledgement" class="level2">
<h2 class="anchored" data-anchor-id="conclusion-and-acknowledgement"><strong>Conclusion and Acknowledgement</strong></h2>
<p>Our attempt on implementing a counterfactual image generation with a pure JAX approach (causal-genx) keeps the original causal-gen scientific recipe intact: an invertible parent SCM, supervised image-to-parent predictors, a hierarchical image model, and constrained counterfactual fine-tuning, but turns that recipe into an accelerator-ready, inspectable system. Relative to the original PyTorch/Pyro implementation, this work contributes a native JAX/Flax/Optax/Orbax codebase with explicit parameter, optimizer, EMA, random-key, and checkpoint state; a single workflow that runs on CPU, GPU, single-core TPU, and replicated multi-core TPU; and a documented four-stage MorphoMNIST implementation whose losses, architectures, intervention semantics, and freeze/unfreeze boundaries can be read directly from the code.</p>
<p>The result is more than a framework translation. Compilation and batch-sharding choices in JAX make the workload practical at scale. The same artifacts can be served through the interactive GitHub Pages and Google Cloud Run demo, allowing a user to inspect factual parents, apply an intervention, and compare the resulting image with its source. These additions improve portability, throughput, and usability.</p>
<p>We thank the authors of the original <a href="https://github.com/biomedia-mira/causal-gen">Causal-Gen</a> work for releasing the PyTorch/Pyro reference implementation that made this port and its numerical checks possible. We also gratefully acknowledge Google for providing the cloud credits used for TPU experiments and the public demonstration. The Causal-GenX repository extends that foundation as an open, reproducible engineering companion: the original work supplies the causal blueprint, while this implementation supplies a modern accelerator and deployment path for exploring it.</p>


</section>

 ]]></description>
  <category>causal-inference</category>
  <category>generative-ai</category>
  <category>computer-vision</category>
  <category>jax</category>
  <guid>https://ghif.github.io/posts/2026-08-07-counterfactual-image-generative-models/</guid>
  <pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate>
  <media:content url="https://ghif.github.io/posts/2026-08-07-counterfactual-image-generative-models/media/Screenshot_2026-08-07_at_16.13.11.png" medium="image" type="image/png" height="49" width="144"/>
</item>
<item>
  <title>Optimizing 3D Gaussian Splatting on TPU with JAX/XLA</title>
  <dc:creator>Muhammad Ghifary</dc:creator>
  <link>https://ghif.github.io/posts/2026-04-30-optimizing-3d-gaussian-splatting-tpu-jax/</link>
  <description><![CDATA[ 




<p>3D Gaussian Splatting (3DGS) has emerged as a state-of-the-art technique for real-time radiance field rendering, offering a compelling alternative to neural volume integration methods like NeRF. In the previous <a href="https://app.notion.com/p/Beyond-the-Triangle-Building-a-3D-Gaussian-Splatting-Engine-from-Scratch-in-JAX-2dca59d37360803b9ad8ec11ab117758?pvs=21">attempt</a>, I implemented the 3DGS algorithm purely in JAX. However, that version was a naive approach and did not fully harness JAX’s performance on accelerators. There is still plenty of room to improve runtime.</p>
<p>This article discusses how to further optimize 3DGS across multiple Tensor Processing Units (TPUs). In general, the strategy involves restructuring the rasterization implementation and exploiting batched data parallelism. The <a href="https://github.com/ghif/jax-gs"><code>jax-gs</code></a> project addresses these challenges by reformulating 3DGS within the JAX framework and leveraging XLA (Accelerated Linear Algebra) to compile the entire training and rendering pipeline into highly optimized machine code.</p>
<p>This transition from a dynamic, CUDA-centric model to a static-shape, JIT-compiled architecture allows <code>jax-gs</code> to exploit the massive parallel processing power of TPUs while maintaining numerical stability and structural consistency. The codebase is also research-friendly that benefits from JAX’s composable transformations (e.g., <code>vmap</code>, <code>pmap</code>, <code>grad</code>).</p>
<section id="about-tensor-processing-units-tpus" class="level2">
<h2 class="anchored" data-anchor-id="about-tensor-processing-units-tpus">About Tensor Processing Units (TPUs)</h2>
<p>Before diving into the optimization strategy, let’s briefly discuss the hardware accelerator itself. A basic understanding of TPU architecture will help us plan the optimization more effectively.</p>
<p>TPUs are Google’s custom-developed application-specific integrated circuits (ASICs) designed specifically to accelerate machine learning workloads. Unlike general-purpose GPUs, TPUs are architected around the requirements of deep learning, prioritizing high-throughput matrix multiplications and low-latency interconnects.</p>
<section id="tpu-evolution" class="level3">
<h3 class="anchored" data-anchor-id="tpu-evolution">TPU Evolution</h3>
<p>Since their debut in 2015, TPUs have evolved from specialized inference engines into the backbone of global AI:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-04-30-optimizing-3d-gaussian-splatting-tpu-jax/media/Screenshot_2026-05-04_at_17.20.09.png" class="img-fluid figure-img"></p>
<figcaption>Screenshot 2026-05-04 at 17.20.09.png</figcaption>
</figure>
</div>
<ul>
<li><strong>TPU v1:</strong> A pure inference chip that powered Google Search and AlphaGo</li>
<li><strong>TPU v2:</strong> The first version capable of larger-scale training, introducing the <strong>bfloat16</strong> format.</li>
<li><strong>TPU v3:</strong> Doubled performance and introduced liquid cooling to handle the heat of massive scale.</li>
<li><strong>TPU v4:</strong> Introduced <strong>3D Torus</strong> network topology and <strong>SparseCore</strong> for embedding acceleration.</li>
<li><strong>TPU v5:</strong> Split into v5e (efficient/cost-optimized) and v5p (performance flagship for models like Gemini).</li>
<li><strong>Trillium / TPU v6e:</strong> The recent generation, featuring a 256x256 MXU and 4.7x the peak compute of v5e.</li>
<li><strong>Ironwood / TPU7x:</strong> Employs a dual-chiplet architecture and 192GB of HBM3e, optimized for massive-scale inference and frontier “reasoning” models.</li>
</ul>
<p>Here are the key architectural innovations in TPUs.</p>
<ul>
<li><strong>Systolic Array &amp; MXU</strong>: Unlike the many-core SIMT architecture of GPUs, TPUs use a systolic array design where data flows through a grid of multiply-accumulators (MXUs). This minimizes memory access during matrix multiplications.</li>
<li><strong>Optical Circuit Switching (OCS)</strong>: Starting with TPU v4, Google replaced traditional electronic switches with OCS, allowing the interconnect topology (3D Torus) to be reconfigured dynamically. This facilitates Inter-chip interconnect (ICI) resiliency by routing around optical faults.</li>
</ul>
<p>Unlike general-purpose GPUs, TPUs are built around <strong>Matrix Multiplication Units (MXUs)</strong>. These hardware blocks use a systolic array design to perform massive matrix multiplications with incredibly high throughput.</p>
</section>
<section id="tpu-chips-pods-and-slices" class="level3">
<h3 class="anchored" data-anchor-id="tpu-chips-pods-and-slices">TPU Chips, Pods, and Slices</h3>
<p>A TPU chip is specifically engineered with key components to accelerate machine learning (ML) workloads. The following is the schematic architecture of a TPU chip:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-04-30-optimizing-3d-gaussian-splatting-tpu-jax/media/TPU-chip.png" class="img-fluid figure-img"></p>
<figcaption>TPU-chip.png</figcaption>
</figure>
</div>
<ol type="1">
<li><strong>TensorCore:</strong> The primary processing unit, handling the bulk of the computational acceleration. Each TPU chip contains one or more cores. The exact number varies depending on the chip version.</li>
<li><strong>Scalar unit:</strong> Handles control flow, calculates memory addresses, and manages other essential “housekeeping” operations.</li>
<li><strong>Vector Processing Unit (VPU) and Vector Memory (VMEM):</strong> Together form a Vector Unit, used for general computations that aren’t matrix multiplications, such as activation functions and softmax.</li>
<li><strong>Matrix-multiply units (MXUs):</strong> The workhorses, providing the bulk of the computational power. They are structured as systolic arrays of multiply-accumulators.</li>
<li><strong>High Bandwidth Memory (HBM) access interface:</strong> Providing fast access to memory for the TPU.</li>
<li><strong>BarnaCore/SparseCore:</strong> Specialized dataflow processors designed to accelerate computations involving sparse data in deep learning tasks (e.g., embedding computation).</li>
<li><strong>Inter-chip-interconnect (ICI):</strong> Enabling seamless communication between multiple TPU chips.</li>
</ol>
<p>While a single TPU chip is powerful, real-world AI models often require even more compute. This is where TPU cubes, pods and slices come in.</p>
<section id="tpu-cube" class="level4">
<h4 class="anchored" data-anchor-id="tpu-cube">TPU cube</h4>
<p>A cube is a physical unit that contains 64 chips. It is a 4x4x4 topology of interconnected TPU chips. This is only applicable to 3D topologies (beginning with TPU v4). They are also known as a “rack”.</p>
<p><strong>TPU pod</strong></p>
<p>A pod is a collection of TPUs that are physically grouped together and connected by a specialized, high-speed network. The total number of TPU chips within a pod varies by TPU version. Imagine a data center as a city. A TPU pod is like a dedicated, super-fast neighborhood within that city, designed specifically for AI compute.</p>
<p><strong>TPU slice</strong></p>
<p>A slice is a subset of chips within a single TPU pod, all connected by incredibly fast ICI. A TPU slice refers to any group of TPUs ranging from 4 chips up to the size of a Superpod. Continuing the city analogy, if a TPU pod is a neighborhood, then a slice is like a tighly integrated block or a cluster of interconnected buildings within that neighborhood, where information (data) can be shared almost instantly between the residents (chips).</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-04-30-optimizing-3d-gaussian-splatting-tpu-jax/Optimizing 3D Gaussian Splatting on TPU with JAX X/T-AIHYPE_Rise_M3_60_(1).png" class="img-fluid figure-img"></p>
<figcaption>T-AIHYPE_Rise_M3_60 (1).png</figcaption>
</figure>
</div>
<p>In this article, we mainly use the Trillium (v6e) to support 3D Gaussian Splatting computation.</p>
</section>
</section>
<section id="accessing-cloud-tpu-in-gcp" class="level3">
<h3 class="anchored" data-anchor-id="accessing-cloud-tpu-in-gcp">Accessing Cloud TPU in GCP</h3>
<p>One way to access TPUs is through Cloud TPU VMs in GCP. Below is the <code>gcloud</code> command to create a queued TPU VM with a v6e-4 slice (the “-4” suffix denotes the number of chips in the TPU slice).</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gcloud</span> alpha compute tpus queued-resources create tpu-southamerica-queue <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb1-2">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--zone</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>southamerica-east1-c <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb1-3">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--accelerator-type</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>v6e-4 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb1-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--runtime-version</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>v2-alpha-tpuv6e <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb1-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--node-id</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>my-tpu-node <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb1-6">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--provisioning-model</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>flex-start <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb1-7">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--max-run-duration</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>96h <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb1-8">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--valid-until-duration</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>96h <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb1-9">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--labels</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>purpose=flex-start</span></code></pre></div></div>
<p>This queue uses the flex-start provisioning model, meaning resources start on demand from the queue rather than being pinned up front. This option can reduce cost and administrative overhead. It can also improve resource sharing and orchestration for teams. Multiple users or jobs can request capacity from the same queue instead of each user reserving an expensive dedicated node. Tradeoffs to be aware of include startup latency while the queued resource is provisioned, zone-dependent availability, and applicable policies and quotas.</p>
<p>In the command example above, the cloud TPU instance targets the zone at <code>southamerica-east1-c</code> and will be available for 4 days (<code>96h</code>).</p>
</section>
</section>
<section id="optimization-strategy" class="level2">
<h2 class="anchored" data-anchor-id="optimization-strategy">Optimization Strategy</h2>
<p>The initial implementation used a traditional deep learning training loop structure:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> pbar:</span>
<span id="cb2-2">    idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> random.randin(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(jax_cameras)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb2-3">    cam <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax_cameras[idx]</span>
<span id="cb2-4">    target <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax_targets[idx]</span>
<span id="cb2-5">    </span>
<span id="cb2-6">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Single step dispatched to device</span></span>
<span id="cb2-7">    state, loss, metrics <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> train_step(state, target, cam.W2C, camera_static, optimizer)</span>
<span id="cb2-8">    </span>
<span id="cb2-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb2-10">        img <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> render(state[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], jax_cameras[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Synchronous render</span></span>
<span id="cb2-11">        save_ply(...) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Symchronous I/O</span></span>
<span id="cb2-12">    </span></code></pre></div></div>
<p>While functional, this approach has a few drawbacks or fails to capitalize on JAX’s core strengths.</p>
<ol type="1">
<li><strong>Dispatch Overhead:</strong> Python has to tell the TPU what to do at every step.</li>
<li><strong>Host-Device Communication:</strong> Sampling images on the CPU and sending them to the TPU creates a bottleneck.</li>
<li><strong>Synchronous I/O:</strong> The entire training process pauses to wait for disk writes.</li>
</ol>
<p>To achieve faster performance, we must transition to a JAX-native architecture designed for TPU efficiency. The primary goal of <code>jax-gs</code> is to minimize host-accelerator communication and maximize the utilization of TPU systolic arrays (MXUs). We achieve this through several core optimization approaches that restructure the training and rendering pipeline into high-throughput, block-compile execution units.</p>
<section id="action-i-jit-compiles-training-blocks" class="level3">
<h3 class="anchored" data-anchor-id="action-i-jit-compiles-training-blocks">Action I: JIT-Compiles Training Blocks</h3>
<p>In JAX, every Python-to-accelerator dispatch incurs a non-trivial overhead. To mitigate this, we aggregate multiple training iterations into a single <code>jax.lax.scan</code> loop, which is then JIT-compiled into a single XLA graph.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># From train.py</span></span>
<span id="cb3-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@partial</span>(jax.jit, static_argnums<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>))</span>
<span id="cb3-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> train_block(state, rng_key, all_targets, all_w2cs, steps_per_block, camera_static, optimizer, fast_tpu_rasterizer, sh_degree):</span>
<span id="cb3-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> one_step(carry, _):</span>
<span id="cb3-5">        state, key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> carry</span>
<span id="cb3-6">        key, subkey <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.random.split(key)</span>
<span id="cb3-7">        </span>
<span id="cb3-8">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Sample camera index</span></span>
<span id="cb3-9">        idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.random.randint(subkey, (), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, all_targets.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb3-10">        target <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> all_targets[idx]</span>
<span id="cb3-11">        w2c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> all_w2cs[idx]</span>
<span id="cb3-12">        </span>
<span id="cb3-13">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Perform training step</span></span>
<span id="cb3-14">        state, loss, metrics <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> train_step(state, target, w2c, camera_static, optimizer, fast_tpu_rasterizer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>fast_tpu_rasterizer, sh_degree<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>sh_degree)</span>
<span id="cb3-15">        </span>
<span id="cb3-16">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (state, key), loss</span>
<span id="cb3-17"></span>
<span id="cb3-18">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The entire loop runs on the TPU as a single XLA program</span></span>
<span id="cb3-19">    (state, rng_key), losses <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.lax.scan(one_step, (state, rng_key), <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>steps_per_block)</span>
<span id="cb3-20">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> state, rng_key, losses</span></code></pre></div></div>
<p>This effectively deletes dispatch overhead from the performance equation. The TPU runs for <em>m</em> iterations without ever looking back at the host.</p>
</section>
<section id="action-ii-on-device-data-sampling" class="level3">
<h3 class="anchored" data-anchor-id="action-ii-on-device-data-sampling">Action II: On-Device Data &amp; Sampling</h3>
<p>To avoid the bottleneck of transferring images and camera matrices from the CPU host to the TPU device on every iteration, we store the entire dataset on-device memory. Index sampling for mini-batches is performed using <code>jax.random</code> primitives directly on the TPU, ensuring that the training loop remains entirely self-contained within the accelerator.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># From train.py</span></span>
<span id="cb4-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. Prepare data on device (all images and matrices loaded once)</span></span>
<span id="cb4-3">all_targets <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.stack(jax_targets)</span>
<span id="cb4-4">all_w2cs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.stack([c.W2C <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> jax_cameras])</span>
<span id="cb4-5"></span>
<span id="cb4-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. Inside the JIT-compiled train_block:</span></span>
<span id="cb4-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Sampling happens entirely on-device</span></span>
<span id="cb4-8">idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.random.randint(subkey, (), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, all_targets.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb4-9">target <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> all_targets[idx]</span>
<span id="cb4-10">w2c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> all_w2cs[idx]</span></code></pre></div></div>
</section>
<section id="action-iii-asynchronous-io" class="level3">
<h3 class="anchored" data-anchor-id="action-iii-asynchronous-io">Action III: Asynchronous I/O</h3>
<p>While the TPU handles the intensive computation, monitoring progress requires periodic rendering of validation views and saving <code>.ply</code> snapshots. We offload these tasks to a background thread pool on the host CPU, preventing I/O operations from blocking the primary training pipeline.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Background executor for I/O and rendering</span></span>
<span id="cb5-2">executor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> concurrent.futures.ThreadPoolExecutor(max_workers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb5-3"></span>
<span id="cb5-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Inside training loop:</span></span>
<span id="cb5-5"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> curr_iter <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb5-6">    snap_gaussians_dict <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_active_gaussians(curr_state)</span>
<span id="cb5-7">    fut <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> executor.submit(</span>
<span id="cb5-8">        save_artifacts_task, </span>
<span id="cb5-9">        snap_gaussians_dict, curr_iter, progress_dir, ply_path, </span>
<span id="cb5-10">        jax_cameras[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], fast_tpu_rasterizer, render, save_ply, sh_degree</span>
<span id="cb5-11">    )</span></code></pre></div></div>
</section>
<section id="action-iv-hardware-specific-rasterization-cpugpu-vs.-tpu" class="level3">
<h3 class="anchored" data-anchor-id="action-iv-hardware-specific-rasterization-cpugpu-vs.-tpu">Action IV: Hardware-Specific Rasterization (CPU/GPU vs.&nbsp;TPU)</h3>
<p>A critical realization during development was that the optimal JAX code for a GPU is not always the optimal code for a TPU. To maximize performance across platforms, we implemented two distinct rasterizers: <code>rasterizer.py</code> (standard) and <code>rasterizer_tpu.py</code> (TPU-optimized).</p>
<p>The core differences lie in how they structure memory access and vectorization for the XLA compiler:</p>
<p><strong>Vectorization Strategy (Nested vs.&nbsp;Flat)</strong></p>
<ul>
<li><strong>Standard (<code>rasterizer.py</code>)</strong>: Uses a nested approach. It parallelizes over tiles using&nbsp;<code>jax.vmap</code>, and inside that, parallelizes over the 256 pixels within the tile using another&nbsp;<code>jax.vmap</code>. This is natural and works well on GPUs.</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># From jax_gs/renderer/rasterizer.py</span></span>
<span id="cb6-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> rasterize_single_tile(tile_idx):</span>
<span id="cb6-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... logic for one tile ...</span></span>
<span id="cb6-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> blend_pixel(p_coord, p_valid):</span>
<span id="cb6-5">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... blending logic for one pixel ...</span></span>
<span id="cb6-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> final_color</span>
<span id="cb6-7"></span>
<span id="cb6-8">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Parallelize over pixels in the tile</span></span>
<span id="cb6-9">    tile_image <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.vmap(blend_pixel)(pixel_coords, pixel_valid)</span>
<span id="cb6-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> tile_image.reshape(tile_size, tile_size, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb6-11"></span>
<span id="cb6-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Parallelize over all tiles in the image</span></span>
<span id="cb6-13">all_tiles <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.vmap(rasterize_single_tile)(jnp.arange(num_tiles))</span></code></pre></div></div>
<ul>
<li><strong>TPU (<code>rasterizer_tpu.py</code>)</strong>: TPUs prefer massive, continuous matrix operations to saturate their Matrix Multiply Units (MXUs). The TPU rasterizer flattens the tile and pixel dimensions into a single massive axis (<code>[num_tiles, 256]</code>). The core blending loop (<code>scan_fn</code>) processes a single Gaussian across <em>all pixels in all tiles simultaneously</em>.</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># From jax_gs/renderer/rasterizer_tpu.py</span></span>
<span id="cb7-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pre-calculate global pixel coordinates for EVERY pixel in the image, </span></span>
<span id="cb7-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># grouped by tile. Shape: [num_tiles, 256]</span></span>
<span id="cb7-4">px <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (tx[:, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> TILE_SIZE).astype(jnp.float32) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> TILE_SIZE)[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :].astype(jnp.float32) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span></span>
<span id="cb7-5">py <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (ty[:, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> TILE_SIZE).astype(jnp.float32) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> TILE_SIZE)[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :].astype(jnp.float32) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span></span>
<span id="cb7-6"></span>
<span id="cb7-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@jax.checkpoint</span></span>
<span id="cb7-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> scan_fn(carry, i):</span>
<span id="cb7-9">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Vectorized across [num_tiles, 256] flat dimension</span></span>
<span id="cb7-10">    dx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> px <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu_x </span>
<span id="cb7-11">    dy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> py <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu_y</span>
<span id="cb7-12">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... blending logic ...</span></span></code></pre></div></div>
<p><strong>Memory Access Patterns (Random vs.&nbsp;Broadcasted Gather)</strong></p>
<ul>
<li><strong>Standard</strong>: Inside the inner loop, it dynamically slices (<code>jnp.take</code>) the specific Gaussians that overlap the current tile.</li>
<li><strong>TPU:</strong> Dynamic slicing inside a fast loop is terrible for TPU performance. Instead, we use a <em>Broadcasted Gather</em>. Before the loop starts, we prefetch all Gaussian parameters for every tile into massive tensors (e.g., <code>[num_tiles, BLOCK_SIZE, 3</code> for colors).</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># From jax_gs/renderer/rasterizer_tpu.py</span></span>
<span id="cb8-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># BROADCASTED GATHER: Construct indices for all Gaussians across all tiles.</span></span>
<span id="cb8-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Resulting shape: [num_tiles, BLOCK_SIZE]</span></span>
<span id="cb8-4">all_tile_indices <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tile_starts[:, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> jnp.arange(BLOCK_SIZE)[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :]</span>
<span id="cb8-5"></span>
<span id="cb8-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Prefetch Gaussian data for all tiles at once</span></span>
<span id="cb8-7">tile_gids <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> valid_ids[all_tile_indices] </span>
<span id="cb8-8">g_means <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> means2D[tile_gids]            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [num_tiles, BLOCK_SIZE, 2]</span></span>
<span id="cb8-9">g_cols <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> colors[tile_gids]              <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [num_tiles, BLOCK_SIZE, 3]</span></span>
<span id="cb8-10"></span>
<span id="cb8-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@jax.checkpoint</span></span>
<span id="cb8-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> scan_fn(carry, i):</span>
<span id="cb8-13">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Processes the i-th Gaussian for ALL pixels in ALL tiles simultaneously</span></span>
<span id="cb8-14">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># No random access lookups here!</span></span>
<span id="cb8-15">    mu_x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g_means[:, i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][:, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [num_tiles, 1]</span></span>
<span id="cb8-16">    dx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> px <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu_x                   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [num_tiles, 256]</span></span>
<span id="cb8-17">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... blending logic ...</span></span></code></pre></div></div>
<p><strong>Memory Efficiency (<code>jax.checkpoint</code>)</strong></p>
<p>Because the TPU rasterizer trades memory for speed via the Broadcasted Gather, it risks Out-Of-Memory (OOM) errors during the backward pass (autodiff). We solve this by heavily applying <code>@jax.checkpoint</code> to the inner <code>scan</code> loop. This forces JAX to discard intermediate activations during the forward pass and recompute them on-the-fly during backpropagation, keeping memory scaling linear rather than exploding.</p>
</section>
<section id="action-v-multi-tpu-parallelism" class="level3">
<h3 class="anchored" data-anchor-id="action-v-multi-tpu-parallelism">Action V: Multi-TPU Parallelism</h3>
<p>For large-scale scenes or faster convergence, we utilize <code>jax.pmap</code> for data-parallel training across multiple TPU cores. Gradients and metrics are synchronized across the high-speed Torus network using <code>jax.lax.pmean</code>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># From train_parallel.py</span></span>
<span id="cb9-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@partial</span>(jax.pmap, axis_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"batch"</span>, ...)</span>
<span id="cb9-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> train_block(state, ...):</span>
<span id="cb9-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... computation ...</span></span>
<span id="cb9-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> one_step(carry, inputs):</span>
<span id="cb9-6">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... train_step_internal performs pmean on gradients ...</span></span>
<span id="cb9-7">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> state, (loss, metrics)</span>
<span id="cb9-8"></span>
<span id="cb9-9">    state, (losses, metrics) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.lax.scan(one_step, state, (batch_targets, batch_w2cs))</span>
<span id="cb9-10">    avg_metrics <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.tree_util.tree_map(<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> x: jnp.mean(x, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), metrics)</span>
<span id="cb9-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> state, losses, avg_metrics</span>
<span id="cb9-12"></span>
<span id="cb9-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># From jax_gs/training/trainer.py</span></span>
<span id="cb9-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Gradients and metrics are averaged across devices</span></span>
<span id="cb9-15">grads <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.tree_util.tree_map(<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> x: jax.lax.pmean(x, axis_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'batch'</span>), grads)</span>
<span id="cb9-16">loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.lax.pmean(loss, axis_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'batch'</span>)</span>
<span id="cb9-17">metrics <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.tree_util.tree_map(<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> x: jax.lax.pmean(x, axis_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'batch'</span>), metrics)</span></code></pre></div></div>
<p>Adaptive density control (cloning/splitting/pruning) is performed by unreplicating the state to a single “authoritative” device, executing the logic, and then re-broadcasting the result.</p>
</section>
</section>
<section id="benchmark-results" class="level2">
<h2 class="anchored" data-anchor-id="benchmark-results">Benchmark Results</h2>
<p>Our benchmarks on the LLFF <code>room</code> dataset (504x378 resolution) demonstrate the massive performance gains achieved by our JAX-native optimizations. On a <strong>Google Cloud TPU v6e-4 (Trillium)</strong>, we observed the following:</p>
<section id="rasterizer-optimization" class="level3">
<h3 class="anchored" data-anchor-id="rasterizer-optimization">Rasterizer Optimization</h3>
<p>The “Fast TPU Rasterizer” achieves a <strong>~100x speedup</strong> over the standard JAX implementation by maximizing MXU utilization and minimizing HBM latency.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th><strong>Metric</strong></th>
<th><strong>Standard Rasterizer</strong></th>
<th><strong>Fast TPU Rasterizer</strong></th>
<th><strong>Speedup</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Throughput (Steady State)</strong></td>
<td>~0.09 it/s</td>
<td>~9.6 it/s</td>
<td>~107x</td>
</tr>
<tr class="even">
<td><strong>Convergence Time (3k steps)</strong></td>
<td>~9.2 hours</td>
<td>~5.2 minutes</td>
<td>~107x faster</td>
</tr>
</tbody>
</table>
</section>
<section id="multi-device-scaling" class="level3">
<h3 class="anchored" data-anchor-id="multi-device-scaling">Multi-Device Scaling</h3>
<p>By utilizing <code>jax.pmap</code> and <code>jax.lax.scan</code> , we achieve near-linear scaling across multiple TPU cores. The efficiency remains high even as the complexity of the scene increases.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th><strong>Phase</strong></th>
<th><strong>Active Gaussians</strong></th>
<th><strong>Single Device Throughput</strong></th>
<th><strong>Multi-Device (4 TPUs)</strong></th>
<th><strong>Scaling Efficiency</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>SH Degree 0</strong></td>
<td>~17.5k</td>
<td>9.6 img/s</td>
<td>38.4 img/s</td>
<td>4.0x</td>
</tr>
<tr class="even">
<td><strong>SH Degree 1</strong></td>
<td>~34k</td>
<td>6.6 img/s</td>
<td>21.6 img/s</td>
<td>3.3x</td>
</tr>
</tbody>
</table>
<p>The “Fast TPU Rasterizer” achieves near-peak MXU saturation by replacing irregular memory access patterns with contiguous tensor operations, while&nbsp;<code>train_parallel.py</code>&nbsp;effectively hides communication overhead at scale.</p>
<video controls="" preload="metadata" width="100%">
<source src="media/fern_wiggle.mp4" type="video/mp4">
<p>Your browser does not support embedded video.</p>
<video controls="" preload="metadata" width="100%">
<source src="media/room_final_ref_wiggle.mp4" type="video/mp4">
<p>Your browser does not support embedded video.</p>
</video></video></section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>The transformation of 3D Gaussian Splatting into a static-shape, JIT-compatible architecture within JAX enables efficient training and rendering on modern accelerators. By prioritizing systolic array saturation, minimizing host-device communication, and leveraging the low-latency TPU interconnects, <a href="https://github.com/ghif/jax-gs"><code>jax-gs</code></a> provides a robust and scalable foundation for the next generation of radiance field research.</p>
</section>
<section id="acknowledgments" class="level2">
<h2 class="anchored" data-anchor-id="acknowledgments">Acknowledgments</h2>
<p>Google Cloud credits are provided for this project.&nbsp;<strong>#TPUSprint</strong></p>


</section>

 ]]></description>
  <category>machine-learning</category>
  <category>deep-learning</category>
  <category>computer-vision</category>
  <category>implementation</category>
  <category>research-notes</category>
  <guid>https://ghif.github.io/posts/2026-04-30-optimizing-3d-gaussian-splatting-tpu-jax/</guid>
  <pubDate>Thu, 30 Apr 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>On-Device Medical Intelligence: Converging MedGemma 1.5 and LiteRT</title>
  <dc:creator>MUHAMMAD GHIFARY, ABIL SUDARMAN</dc:creator>
  <link>https://ghif.github.io/posts/2026-03-28-on-device-medical-intelligence-medgemma-litert/</link>
  <description><![CDATA[ 




<blockquote class="blockquote">
<p><em>This article originally comes from</em> <a href="https://ai4medresearch.github.io/blog/2026/medgemma1-5/">https://ai4medresearch.github.io/blog/2026/medgemma1-5/</a></p>
</blockquote>
<p>In the rapidly evolving landscape of healthcare AI, the transition from massive, cloud-dependent models to specialized, on-device intelligence is not just a trend—it’s a clinical necessity. Medical data is inherently sensitive, and the requirements for privacy (HIPAA compliance), zero-latency reasoning, and offline accessibility in remote or high-security environments are paramount.</p>
<p>Here we are diving into how to bring state-of-the-art medical multimodal intelligence directly to the edge. By converting <strong>MedGemma 1.5 4B</strong> to the specialized <a href="https://github.com/google-ai-edge/LiteRT-LM"><strong>LiteRT-LM</strong></a> (<code>.litertlm</code>) format, we unlock the ability to perform complex clinical analysis—including MRI interpretation and EHR questioning—entirely within a local web browser or mobile device.</p>
<section id="the-rise-of-on-device-medical-intelligence" class="level2">
<h2 class="anchored" data-anchor-id="the-rise-of-on-device-medical-intelligence"><strong>1. The Rise of On-Device Medical Intelligence</strong></h2>
<p>Traditional medical AI often relies on sending high-resolution scans and patient records to powerful GPU clusters in the cloud. While effective, this approach introduces significant bottlenecks:</p>
<ul>
<li><strong>Privacy Risks</strong>: Every byte of data leaving the hospital network is a potential point of failure.</li>
<li><strong>Latency</strong>: In critical care, waiting for a round-trip to a data center can be the difference between a prompt diagnosis and a delayed one.</li>
<li><strong>Connectivity:</strong> Many clinical environments (ORs, remote clinics, or mobile health units) suffer from inconsistent internet access.</li>
</ul>
<p>On-device intelligence solves these by performing inference where the data is born. With the release of Google’s <a href="https://deepmind.google/models/gemma/gemma-3/"><strong>Gemma 3</strong></a> architecture and its medical sibling <a href="https://deepmind.google/models/gemma/medgemma/"><strong>MedGemma 1.5</strong></a>, the “edge” is now powerful enough to handle 4-billion parameter multimodal models.</p>
</section>
<section id="medgemma-1.5-4b-a-multimodal-leap" class="level2">
<h2 class="anchored" data-anchor-id="medgemma-1.5-4b-a-multimodal-leap"><strong>2. MedGemma 1.5 4B: A Multimodal Leap</strong></h2>
<p>MedGemma 1.5 4B ****represents a significant architectural shift over its predecessors. While MedGemma 1.0 was a pioneer in clinical text understanding, the 1.5 iteration—built on the Gemma 3 foundation—is a true multimodal powerhouse.</p>
<p>Some key advancements of MedGemma 1.5 are as follows:</p>
<ul>
<li><strong>From 2D to 3D</strong>: Previous models focused on 2D images like X-rays or dermoscopy. MedGemma 1.5 natively interprets 3D medical volumes from CT and MRI scans.</li>
<li><strong>Longitudinal Reasoning:</strong> One of the model’s strongest features is its ability to track disease progression by comparing historical scans against current ones.</li>
<li><strong>Clinical Accuracy</strong>: EHR Question Answering accuracy has jumped from ~68% in version 1 to a staggering <strong>~90%</strong> in version 1.5.</li>
<li><strong>Anatomy Localization</strong>: Precise identification of anatomical structures and abnormalities saw an improvement from ~3% IoU to <strong>~38% IoU</strong>.</li>
</ul>
</section>
<section id="deep-dive-into-the-.litertlm-format" class="level2">
<h2 class="anchored" data-anchor-id="deep-dive-into-the-.litertlm-format"><strong>3. Deep Dive into the .litertlm Format</strong></h2>
<p>To run MedGemma efficiently on the edge, we leverage the <strong><code>.litertlm</code></strong> format. This isn’t just another file extension; it is LiteRT’s (formerly TensorFlow Lite) specialized bundle for Generative AI.</p>
<p>The <code>.litertlm</code> offers several benefits.</p>
<ol type="1">
<li><strong>Stateful Optimization</strong>: Unlike standard <code>.tflite</code> graphs, a <code>.litertlm</code> bundle is designed for the iterative nature of LLMs. It contains separate, optimized graphs for <strong>prefill</strong> (processing the prompt) and <strong>decode</strong> (generating tokens one-by-one), while natively managing the <strong>KV-cache</strong>.</li>
<li><strong>Multimodal Synergy</strong>: MedGemma requires a vision encoder and a language head to work tandem. <code>.litertlm</code> bundles these disparate components into a single, self-describing artifact, ensuring the vision-language projection layers are always synchronized.</li>
<li><strong>Hardware Native</strong>: The format is built to leverage the <strong>LiteRT GenAI API</strong>, which provides highly optimized kernels for mobile GPUs and NPUs, significantly outperforming generic graph execution.</li>
</ol>
</section>
<section id="the-conversion-workflow" class="level2">
<h2 class="anchored" data-anchor-id="the-conversion-workflow">4. The Conversion Workflow</h2>
<p>The transformation from raw PyTorch weights to a production-ready LiteRT bundle is an intricate process handled via the <a href="https://github.com/google-ai-edge/litert-torch"><strong>litert-torch</strong></a> export pipeline. It starts with the original <a href="https://huggingface.co/google/medgemma-1.5-4b-it">MedGemma 1.5 4B IT</a> weights from Google and re-architects them for high-performance edge execution.</p>
<p>Instead of a simple “file format save,” the conversion logic performs several critical architectural bridges and optimizations:</p>
<ol type="1">
<li><strong>Structural Alignment (Architecture Bridging)</strong>: MedGemma 1.5 is built on the Gemma 3 architecture. In some versions of the Hugging Face <code>transformers</code> library, the vision modules (tower and projector) are nested deeply within the model structure. The workflow includes a structural patch that maps these nested components to the top level of the model class. This ensures the export engine can accurately “see” and trace the multimodal connection points during the graph-generation phase.</li>
<li><strong>Multimodal Graph Tracing</strong>: The conversion initiates a <code>image_text_to_text</code> export task. This process traces the mathematical flow of data through both the vision encoder and the language head. It effectively captures how an MRI image is transformed into tokens and how those tokens are processed by the LLM to generate a clinical description.</li>
<li><strong>Prefill Bucketing</strong>: To optimize the “time-to-first-token” on edge devices, the workflow generates specialized graphs for different <strong>prefill lengths</strong> (e.g., 128, 256, 512 tokens). This allows the runtime to use the most efficient computation path based on the size of the user’s initial prompt or image metadata.</li>
<li><strong>Specialized Dual-Quantization</strong>: To compress the 4B parameter model to a browser-friendly ~3GB, we apply distinct quantization strategies to different components:
<ol type="1">
<li><strong>LLM Core</strong>: Uses a <code>dynamic_wi8_afp32</code> recipe (8-bit weights with 32-bit activations), balancing reasoning depth with memory footprint.</li>
<li><strong>Vision Encoder</strong>: Uses a <code>weight_only_wi8_afp32</code> recipe, ensuring that the high-dimensional features required for medical imaging are preserved while still reducing the storage overhead.</li>
</ol></li>
<li><strong>KV-Cache Architecture</strong>: The workflow configures a fixed-length <strong>Key-Value (KV) cache</strong> (typically 4096 tokens). This is embedded into the LiteRT graph, enabling the model to “remember” the context of a long medical conversation without re-processing the entire history for every new word generated.</li>
<li><strong>Unified Bundling</strong>: The final step packages the optimized graphs, the token embedder, the tokenizer configuration, and essential model metadata into a single, self-describing <strong>.litertlm</strong> container. This eliminates the need for external configuration files and ensures the model is “plug-and-play” for the edge runtime.</li>
</ol>
</section>
<section id="model-deployment-on-edge-web-browsers" class="level2">
<h2 class="anchored" data-anchor-id="model-deployment-on-edge-web-browsers">5. <strong>Model Deployment on Edge Web Browsers</strong></h2>
<p>Deploying a 4-billion parameter multimodal model like MedGemma in a browser tab is a feat of modern web engineering. It transforms the browser from a simple document viewer into a secure, hardware-accelerated sandbox for private clinical intelligence. To verify the effectiveness of the converted model, we implement a simple GUI prototype written in JavaScript/TypeScript that runs on a local web browser, in which the model deployment is optimized through a multi-layered architectural approach.</p>
<p><a href="On-Device Medical Intelligence Converging MedGemma/medgemma_edge_demo.mp4">medgemma_edge_demo.mp4</a></p>
<section id="hardware-first-verification" class="level3">
<h3 class="anchored" data-anchor-id="hardware-first-verification">Hardware-First Verification</h3>
<p>The deployment begins with an environmental handshake. Before attempting to load any model, the application verifies the presence of the <strong>WebGPU API</strong> (<code>navigator.gpu</code>). This is the application’s “gatekeeper”, i.e., without native GPU access, the computational overhead of a 4B parameter model would be too high for a standard browser thread.</p>
</section>
<section id="dynamic-runtime-resolution-wasm" class="level3">
<h3 class="anchored" data-anchor-id="dynamic-runtime-resolution-wasm"><strong>Dynamic Runtime Resolution (WASM)</strong></h3>
<p>Once hardware is confirmed, the application initializes the&nbsp;<strong>LiteRT GenAI runtime</strong>. Instead of shipping massive binary loaders with the app, we utilize the&nbsp;<code>FilesetResolver</code>&nbsp;to pull specialized WebAssembly (WASM) runtimes (like&nbsp;<code>genai_wasm_internal.js</code>) from a high-performance CDN. This ensures the edge engine is always running the latest version compatible with the MedGemma 1.5 bundle format.</p>
</section>
<section id="local-asset-setup" class="level3">
<h3 class="anchored" data-anchor-id="local-asset-setup">Local Asset Setup</h3>
<p>For the GUI prototype to function correctly, the <code>.litertlm</code> model files must be hosted locally within the application’s structure. Specifically, the model bundle should be located at, for example, the <code>public/models</code> directory. This allows the LiteRT GenAI runtime to fetch the multi-gigabyte binary artifacts directly from the same origin, bypassing complex cross-origin resource sharing (CORS) issues while maintaining high-speed local data transfer.</p>
</section>
<section id="two-stage-fallback-strategy" class="level3">
<h3 class="anchored" data-anchor-id="two-stage-fallback-strategy"><strong>Two-Stage Fallback Strategy</strong></h3>
<p>To maximize clinical accessibility, we implemented a robust&nbsp;<strong>Primary-to-Backup loading loop</strong>:</p>
<ul>
<li><strong>Primary Attempt (MedGemma 1.5 4B)</strong>: The system first tries to allocate resources for the high-fidelity medical model. This model provides the deep clinical reasoning required for complex MRI/CT analysis.</li>
<li><strong>Automatic Fallback (Gemma 3 2B)</strong>: If the primary load fails—common on devices with less than 16GB of RAM or limited VRAM—the logic catches the error and immediately attempts to initialize the lighter Gemma 3 engine. This ensures the clinician is never left without an active intelligence layer.</li>
</ul>
<p>In the future, it would be more handful to have a robust MedGemma with less than 2B.</p>
</section>
<section id="integrity-and-cache-management" class="level3">
<h3 class="anchored" data-anchor-id="integrity-and-cache-management">Integrity and Cache Management</h3>
<p>Handling 3GB model files at the edge introduces “stale cache” risks. If a browser attempts to load a partially-downloaded or outdated model file, initialization will fail. Our deployment employs a&nbsp;<strong>Cache-Busting Strategy</strong>, appending a dynamic timestamp query parameter (<code>?v=${Date.now()}</code>) to the model asset path. This forces the browser to verify the file’s integrity and ensures that the clinical engine is always synchronized with the correct&nbsp;<code>.litertlm</code>&nbsp;artifact.</p>
</section>
<section id="multimodal-inference-pipeline" class="level3">
<h3 class="anchored" data-anchor-id="multimodal-inference-pipeline"><strong>Multimodal Inference Pipeline</strong></h3>
<p>The inference process is not just a text loop; it is a coordinated orchestration of vision and language:</p>
<ul>
<li><strong>Image Ingestion</strong>: Medical scans are loaded into browser memory as&nbsp;<code>HTMLImageElement</code>&nbsp;objects.</li>
<li><strong>Part-Based Payload</strong>: We construct a multimodal payload consisting of the image source followed by a structured text prompt.</li>
<li><strong>Smart Streaming</strong>: To provide a fluid “typing” experience, the&nbsp;<code>generateResponse</code>&nbsp;method uses a callback that implements a&nbsp;<strong>Smart Accumulator</strong>. This logic detects whether the backend is sending cumulative strings or incremental tokens, ensuring that the response flows smoothly without flickering, repeating, or vanishing.</li>
</ul>
</section>
<section id="secure-execution-environment" class="level3">
<h3 class="anchored" data-anchor-id="secure-execution-environment">Secure Execution Environment</h3>
<p>To enable the advanced memory features (like&nbsp;<code>SharedArrayBuffer</code>) required for GPU-accelerated inference, the deployment requires a “Secure Context.” This is enforced via mandatory HTTP security headers:</p>
<ul>
<li><strong>COOP (Cross-Origin Opener Policy)</strong>:&nbsp;<code>same-origin</code></li>
<li><strong>COEP (Cross-Origin Embedder Policy)</strong>:&nbsp;<code>require-corp</code></li>
</ul>
<p>This architecture allows expert-level medical AI to run with 100% privacy, utilizing the power already sitting on the clinician’s desk.</p>
</section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">5. Conclusion</h2>
<p>The integration of MedGemma 1.5 4B with LiteRT represents a significant milestone in medical AI accessibility, delivering expert-level multimodal intelligence directly to the browser. By enabling private, always-available assistants on-device, we are bridging the gap between cloud-scale performance and edge efficiency, ensuring clinicians can access critical decision support regardless of their connectivity.</p>
<p>To transition this from a research prototype to a global production tool for resource-constrained environments, our future roadmap focuses on three key optimizations:</p>
<ul>
<li><strong>Int4 QAT Quantization</strong>: Implementing Quantization-Aware Training to drastically reduce memory requirements for entry-level mobile and laptop hardware.</li>
<li><strong>Speculative Decoding</strong>: Integrating draft models to achieve a 2–3× increase in inference speed, essential for high-volume clinical workflows.</li>
<li><strong>Localized Fine-Tuning</strong>: Adapting the model to regional medical terminology and local languages to ensure real-world clinical utility.</li>
</ul>
<p>A primary example of the need for these advancements is found in Indonesia’s 3T regions (underdeveloped, frontier, and outermost). In these settings, healthcare workers require high-accuracy tools that operate fully offline, speak Bahasa Indonesia, and run on the affordable, low-spec hardware already available in community health centers (Puskesmas).</p>
<p>Resources:</p>
<ul>
<li><strong>Model (<code>.litertlm</code>)</strong>:&nbsp;<a href="https://huggingface.co/ai4med-id/medgemma-1.5-4b-it-litertlm">huggingface.co/ai4med-id/medgemma-1.5-4b-it-litertlm</a></li>
<li><strong>Implementation &amp; GUI Prototype</strong>:&nbsp;<a href="https://github.com/AI4MedResearch/edge-ai">github.com/AI4MedResearch/edge-ai</a></li>
</ul>


</section>

 ]]></description>
  <category>machine-learning</category>
  <category>generative-ai</category>
  <category>computer-vision</category>
  <category>implementation</category>
  <category>research-notes</category>
  <guid>https://ghif.github.io/posts/2026-03-28-on-device-medical-intelligence-medgemma-litert/</guid>
  <pubDate>Sat, 28 Mar 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Production-Grade 3D Gaussian Splatting on Apple Silicon with Native MPS Kernels</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2026-02-19-production-grade-3d-gaussian-splatting-apple-silicon/</link>
  <description><![CDATA[ 




<p><strong>3D Gaussian Splatting (3DGS)</strong>&nbsp;has emerged as a transformative breakthrough in the field of 3D computer vision and radiance field reconstruction. By representing scenes as a collection of millions of differentiable 3D Gaussians, this technique enables unprecedented visual fidelity and real-time rendering speeds. However, the path to production-ready performance—the kind required for rapid training and high-frame-rate interaction—has historically been paved with NVIDIA GPUs and highly specialized CUDA kernels. This perceived hardware barrier has often relegated the Mac to a “viewer-only” role or a platform for slow prototyping.</p>
<p>This article demonstrates how to achieve&nbsp;<strong>production-grade performance</strong>&nbsp;directly on Apple Silicon by leveraging the power of native C++ and Metal implementations. By utilizing the&nbsp;<strong>Metal Performance Shaders (MPS)</strong>&nbsp;backend and deep learning framework integration with&nbsp;<strong>MLX</strong>, the heavy lifting of the Gaussian Splatting pipeline can be ported to run natively on the Mac.</p>
<p>It turns out that with the right optimization strategy—moving from high-level Python references to fully GPU-resident C++ Metal kernels—the Apple M4 hardware is not just a capable device, but a definitive powerhouse for 3D Gaussian Splatting. These optimizations deliver up to a&nbsp;<strong>48x speedup</strong>, bringing the training loop from several seconds per iteration to over&nbsp;<strong>38 iterations per second</strong>.</p>
<p>All the results discussed in this article can be produced through the following code repository:&nbsp;<a href="https://github.com/ghif/splat-apple">https://github.com/ghif/splat-apple</a>.</p>
<section id="why-c-native-kernels-over-pure-python" class="level2">
<h2 class="anchored" data-anchor-id="why-c-native-kernels-over-pure-python">Why C++ Native Kernels over Pure Python?</h2>
<p>While high-level frameworks like MLX and PyTorch offer excellent vectorized operations, custom algorithms like 3D Gaussian Splatting hit several architectural limitations when implemented in pure Python. Moving to native C++ and Metal kernels is not just about raw speed; it is about achieving the architectural freedom required for production-quality results.</p>
<section id="dynamic-bounding-and-tile-expansion" class="level3">
<h3 class="anchored" data-anchor-id="dynamic-bounding-and-tile-expansion">1. Dynamic Bounding and Tile Expansion</h3>
<p>Python JIT compilers perform best when array shapes and loop bounds are predictable. To maintain performance and avoid excessive re-compilation, many pure Python implementations resort to a fixed-grid expansion, such as assuming a Gaussian touches at most an 8x8 tile area. This approach creates significant visual artifacts, as large or extremely close Gaussians are artificially “clipped” into squares at tile boundaries.</p>
<p>In contrast, native C++ kernels calculate exact bounding boxes for every Gaussian dynamically. They can expand a single splat across the entire screen if necessary, ensuring perfect visual continuity with no performance penalty.</p>
</section>
<section id="elimination-of-synchronization-gaps" class="level3">
<h3 class="anchored" data-anchor-id="elimination-of-synchronization-gaps"><strong>2. Elimination of Synchronization Gaps</strong></h3>
<p>High-level Python execution frequently requires “sync-points” where the CPU must wait for the GPU to complete a task—such as sorting Gaussians by depth—before it can schedule the next operation. This constant hand-off creates a “ping-pong” effect that leaves the GPU idle for significant portions of the training loop.</p>
<p>By moving the entire rendering pipeline into a single, continuous sequence of Metal kernels, we eliminate these synchronization gaps. The GPU remains fully saturated throughout the process, which is the primary driver behind the leap from ~1 it/s to over 38 it/s.</p>
</section>
<section id="native-handling-of-depth-complexity" class="level3">
<h3 class="anchored" data-anchor-id="native-handling-of-depth-complexity"><strong>3. Native Handling of Depth Complexity</strong></h3>
<p>Vectorized Python implementations typically process a fixed number of Gaussians per tile to keep memory usage constant for the compiler. However, dense real-world scenes often require blending hundreds or even thousands of Gaussians to reach full opacity. When this count is capped, it results in “black holes” where the background leaks through under-rendered regions.</p>
<p>Metal kernels overcome this by using dynamic while-loops to iterate through the entire sorted interaction list. They only stop when the pixel transmittance is truly exhausted, providing flawless visual fidelity regardless of the scene’s complexity.</p>
</section>
<section id="hardware-native-gradient-accumulation" class="level3">
<h3 class="anchored" data-anchor-id="hardware-native-gradient-accumulation"><strong>4. Hardware-Native Gradient Accumulation</strong></h3>
<p>During the backward pass of training, gradients must be accumulated from millions of pixels back into the original Gaussian parameters. While Python’s vectorized&nbsp;<code>scatter_add</code>&nbsp;is functional, it can be extremely memory-intensive at high resolutions.</p>
<p>The native implementation utilizes thread-safe atomic operations (<code>atomic_add</code>) directly on the GPU hardware. This allows thousands of concurrent threads to update the same parameter buffers simultaneously with minimal overhead, providing a massively more efficient path for high-throughput backpropagation.</p>
</section>
</section>
<section id="implementation-a-fully-gpu-resident-mlx-metal-pipeline" class="level2">
<h2 class="anchored" data-anchor-id="implementation-a-fully-gpu-resident-mlx-metal-pipeline"><strong>Implementation: A Fully GPU-Resident MLX Metal Pipeline</strong></h2>
<p>Achieving 40+ iterations per second on a single M4 chip required a fundamental shift in how the rendering pipeline is structured. Instead of treating the GPU as a math accelerator for specific loops, we implemented a&nbsp;<strong>Fully GPU-Resident Pipeline</strong>. This includes the projection of 3D Gaussians into 2D space, where the 2D covariance matrix&nbsp;<img src="https://latex.codecogs.com/png.latex?%5CSigma'">&nbsp;is computed from the 3D covariance&nbsp;<img src="https://latex.codecogs.com/png.latex?%5CSigma">, the world-to-view transformation&nbsp;<img src="https://latex.codecogs.com/png.latex?W">, and the Jacobian of the affine approximation of the projective transformation&nbsp;<img src="https://latex.codecogs.com/png.latex?J">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5CSigma'%20=%20J%20W%20%5CSigma%20W%5ET%20J%5ET%0A"></p>
<p>Here is the step-by-step breakdown of how the MLX Metal renderer is implemented:</p>
<section id="step-1-zero-sync-gpu-interaction" class="level3">
<h3 class="anchored" data-anchor-id="step-1-zero-sync-gpu-interaction"><strong>Step 1: Zero-Sync GPU Interaction</strong></h3>
<p>The interaction stage determines which Gaussians affect which 16x16 screen tiles. In traditional implementations, this often requires copying data back to the CPU for sorting. We eliminated this bottleneck using pure MLX GPU primitives to perform expansion and bitwise sorting on-device:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># mlx_gs/renderer/rasterizer_metal.py</span></span>
<span id="cb1-2">...</span>
<span id="cb1-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Vectorized expansion using cumsum and marking masks</span></span>
<span id="cb1-4">offsets <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mx.cumsum(counts)</span>
<span id="cb1-5">mark <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mx.zeros((total,), dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>mx.int32)</span>
<span id="cb1-6">mark[active_starts] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-7">map_idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mx.cumsum(mark) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-8">gaussian_ids <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> active_indices[map_idx]</span>
<span id="cb1-9"></span>
<span id="cb1-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># High-speed sorting using bitwise-packed 64-bit keys</span></span>
<span id="cb1-11">keys <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (tile_ids.astype(mx.uint64) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> depth_quant</span>
<span id="cb1-12">sort_indices <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mx.argsort(keys)</span></code></pre></div></div>
</section>
<section id="step-2-the-host-side-bridge-nanobind" class="level3">
<h3 class="anchored" data-anchor-id="step-2-the-host-side-bridge-nanobind"><strong>Step 2: The Host-Side Bridge (Nanobind)</strong></h3>
<p>To bridge MLX with native Metal, I developed an Objective-C++ extension using&nbsp;<strong>Nanobind</strong>. The bridge retrieves raw memory pointers and wraps them into Metal handles, leveraging Apple’s Unified Memory for zero-copy access:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode cpp code-with-copy"><code class="sourceCode cpp"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// mlx_gs/csrc/rasterizer_metal.mm</span></span>
<span id="cb2-2"></span>
<span id="cb2-3">id<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>MTLBuffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> wrap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> ptr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">size_t</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">){</span></span>
<span id="cb2-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Zero-copy wrapping of Python/MLX pointers into Metal buffers</span></span>
<span id="cb2-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>device newBufferWithBytes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>ptr length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>size options<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>MTLResourceStorageModeShared<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb2-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb2-7"></span>
<span id="cb2-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Dispatching the kernel with 16x16 tile threadgroups</span></span>
<span id="cb2-9"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>enc setComputePipelineState<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>forwardPSO<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb2-10"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>enc setBuffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>b_m offset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> atIndex<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Means</span></span>
<span id="cb2-11"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>enc dispatchThreads<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>MTLSizeMake<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>ntx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> nty<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb2-12">    threadsPerThreadgroup<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>MTLSizeMake<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)];</span></span>
<span id="cb2-13"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">...</span></span>
<span id="cb2-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/**</span></span>
<span id="cb2-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> * Nanobind Module Definition.</span></span>
<span id="cb2-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> */</span></span>
<span id="cb2-17">NB_MODULE<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>_rasterizer_metal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> m<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb2-18">    m<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>def<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"init_metal"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>init_metal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Initialize Metal device and compile shaders"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb2-19">    m<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>def<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"render_forward"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>render_forward<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Perform forward rasterization on Metal"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb2-20">    m<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>def<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"render_backward"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>render_backward<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Compute gradients for rasterization on Metal"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb2-21"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
</section>
<section id="step-3-tiled-forward-rasterization" class="level3">
<h3 class="anchored" data-anchor-id="step-3-tiled-forward-rasterization"><strong>Step 3: Tiled Forward Rasterization</strong></h3>
<p>The core rendering happens in a Metal compute shader. It implements the standard alpha-compositing formula, where the final color&nbsp;<img src="https://latex.codecogs.com/png.latex?C">&nbsp;for a pixel is the sum of colors&nbsp;ci<em>ci</em>&nbsp;weighted by their alpha&nbsp;αi<em>αi</em>&nbsp;and the accumulated transmittance&nbsp;<img src="https://latex.codecogs.com/png.latex?T_i">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AC%20=%20%5Csum_%7Bi%20%5Cin%20N%7D%20c_i%20%5Calpha_i%20T_i,%20%5Cquad%20T_i%20=%20%5Cprod_%7Bj=1%7D%5E%7Bi-1%7D%20(1%20-%20%5Calpha_j)%0A"></p>
<p>The Metal implementation uses a transmittance-aware blending loop with a dynamic early exit to maximize performance:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode cpp code-with-copy"><code class="sourceCode cpp"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// mlx_gs/csrc/rasterizer.metal</span></span>
<span id="cb3-2"></span>
<span id="cb3-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">s_idx</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> e_idx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">++</span>i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb3-4">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">g_idx</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sorted_gaussian_ids<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb3-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ... compute Gaussian alpha ...</span></span>
<span id="cb3-6">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span> alpha <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">f</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> exp<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(-</span>exp<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>power<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> opacities<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">g_idx</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]);</span></span>
<span id="cb3-7">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span> test_T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">f</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb3-8"></span>
<span id="cb3-9">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Early exit: Stop if the pixel is already opaque</span></span>
<span id="cb3-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>test_T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0001</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">f</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb3-11"></span>
<span id="cb3-12">    C <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> color <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> alpha <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> T<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb3-13">    T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> test_T<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb3-14"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
</section>
<section id="step-4-differentiable-backward-pass" class="level3">
<h3 class="anchored" data-anchor-id="step-4-differentiable-backward-pass"><strong>Step 4: Differentiable Backward Pass</strong></h3>
<p>To enable training, the renderer must be differentiable. The backward pass computes gradients with respect to Gaussian parameters. For instance, the gradient for the color&nbsp;ci<em>ci</em>&nbsp;is derived using the chain rule:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20c_i%7D%20=%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20C%7D%20%5Ccdot%20%5Calpha_i%20T_i%0A"></p>
<p>The thread-safe atomic operations are used to accumulate these gradients from millions of pixels back into Gaussian parameters simultaneously:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode cpp code-with-copy"><code class="sourceCode cpp"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// mlx_gs/csrc/rasterizer.metal</span></span>
<span id="cb4-2"></span>
<span id="cb4-3"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span> atomic_add_float<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>device atomic_uint<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> addr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span> val<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">){</span></span>
<span id="cb4-4">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uint</span> old_val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> atomic_load_explicit<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>addr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> memory_order_relaxed<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb4-5">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uint</span> new_val<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb4-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb4-7">        new_val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">as_type</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uint</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">as_type</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;(</span>old_val<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> val<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb4-8">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(!</span>atomic_compare_exchange_weak_explicit<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>addr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>old_val<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> new_val<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">...));</span></span>
<span id="cb4-9"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb4-10"></span>
<span id="cb4-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Accumulating gradients for means and colors</span></span>
<span id="cb4-12">atomic_add_float<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(&amp;</span>grad_means2D<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">g_idx</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">],</span> dL_dmean<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb4-13">atomic_add_float<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(&amp;</span>grad_colors<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">g_idx</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">],</span> dL_dPixel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> w<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span></code></pre></div></div>
</section>
<section id="step-5-autograd-integration-via-mx.custom_function" class="level3">
<h3 class="anchored" data-anchor-id="step-5-autograd-integration-via-mx.custom_function"><strong>Step 5: Autograd Integration via&nbsp;<code>mx.custom_function</code></strong></h3>
<p>Finally, the native kernels are wrapped into MLX’s autograd system using custom VJP (Vector-Jacobian Product) registrations:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># mlx_gs/renderer/rasterizer_metal.py</span></span>
<span id="cb5-2"></span>
<span id="cb5-3"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@mx.custom_function</span></span>
<span id="cb5-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(m, ic, s_o, c, sti, sgi, bg):</span>
<span id="cb5-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Call the C++ extension</span></span>
<span id="cb5-6">    out_np <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rasterizer_metal.render_forward(...)</span>
<span id="cb5-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> mx.array(out_np)</span>
<span id="cb5-8"></span>
<span id="cb5-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@forward.vjp</span></span>
<span id="cb5-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> backward(primals, cotangents, outputs):</span>
<span id="cb5-11">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pass gradients back through the Metal backward kernel</span></span>
<span id="cb5-12">    gm_np, gic_np, go_np, gc_np <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rasterizer_metal.render_backward(...)</span>
<span id="cb5-13">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> mx.array(gm_np), mx.array(gic_np), mx.array(go_np), ...</span></code></pre></div></div>
</section>
</section>
<section id="the-pytorch-challenge-why-cpu-gcd-was-the-choice-for-stability" class="level2">
<h2 class="anchored" data-anchor-id="the-pytorch-challenge-why-cpu-gcd-was-the-choice-for-stability"><strong>The PyTorch Challenge: Why CPU-GCD was the Choice for Stability</strong></h2>
<p>Besides MLX, we could also use PyTorch with Python-C++ bindings to speed-up 3D Gaussian Splatting. Therefore, I also attempted to implement a similar “Zero-Sync” Metal extension for PyTorch on MPS.</p>
<p>However, I encountered several architectural hurdles. These challenges highlight the fundamental differences between frameworks designed for unified memory (MLX) and those with more complex, cross-platform backends (PyTorch).</p>
<section id="stream-synchronization-and-mutex-deadlocks" class="level3">
<h3 class="anchored" data-anchor-id="stream-synchronization-and-mutex-deadlocks"><strong>1. Stream Synchronization and Mutex Deadlocks</strong></h3>
<p>PyTorch’s MPS backend manages a global command buffer and internal hardware state using a strict&nbsp;<code>recursive_mutex</code>. Attempting to dispatch manual Metal kernels on the same device often triggered internal deadlocks, resulting in&nbsp;<code>recursive_mutex lock failed</code>&nbsp;errors. Because PyTorch does not officially export its command buffer management APIs for third-party extensions, there is currently no stable way to safely interleave custom Metal kernels with PyTorch’s native operations without risking a “race to the hardware.”</p>
</section>
<section id="symbol-visibility-and-portability" class="level3">
<h3 class="anchored" data-anchor-id="symbol-visibility-and-portability"><strong>2. Symbol Visibility and Portability</strong></h3>
<p>Achieving zero-copy performance requires accessing the underlying&nbsp;<code>id&lt;MTLBuffer&gt;</code>&nbsp;of an&nbsp;<code>at::Tensor</code>. While APIs like&nbsp;<code>getMTLBuffer_untracked</code>&nbsp;exist, they are not consistently exported across different PyTorch versions or installation environments. My experiments with runtime symbol lookup (<code>dlsym</code>) proved fragile, leading to “Symbol not found” errors on standard user installs. This makes a production-grade GPU-resident extension for PyTorch difficult to distribute reliably.</p>
</section>
<section id="memory-locality-and-race-conditions" class="level3">
<h3 class="anchored" data-anchor-id="memory-locality-and-race-conditions"><strong>3. Memory Locality and Race Conditions</strong></h3>
<p>In my experimental Metal version for PyTorch, I observed significant rendering artifacts, such as massive black regions. These were traced to race conditions between PyTorch’s asynchronous memory allocator and my manual command queue. Without deep integration into the&nbsp;<code>MPSAllocator</code>, Gaussian data was often outdated or “missing” by the time the Metal kernel executed.</p>
<p><strong>The Result</strong>: For the PyTorch implementation, I opted for a high-performance&nbsp;<strong>multi-threaded C++ implementation using Apple’s Grand Central Dispatch (GCD)</strong>. By distributing tile-based rendering across all available CPU cores, a reliable&nbsp;<strong>10.6 it/s</strong>—a 13x boost over the Python reference can be achieved—while maintaining 100% visual accuracy and backend stability.</p>
</section>
</section>
<section id="benchmark-analysis-the-performance-landscape" class="level2">
<h2 class="anchored" data-anchor-id="benchmark-analysis-the-performance-landscape"><strong>Benchmark Analysis: The Performance Landscape</strong></h2>
<p>To evaluate the impact of these native optimizations, I benchmarked the training loop using the standard&nbsp;<strong>Fern</strong>&nbsp;dataset (10,091 Gaussians) at a resolution of 504x378 on an&nbsp;<strong>Apple M4 (10-core GPU, 16GB Unified Memory)</strong>.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th><strong>Framework</strong></th>
<th><strong>Implementation</strong></th>
<th><strong>Backend</strong></th>
<th><strong>Iterations/Sec</strong></th>
<th><strong>Speedup</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>PyTorch</strong></td>
<td>Python (Reference)</td>
<td>MPS (Vectorized)</td>
<td>~0.84 it/s</td>
<td>1.0x</td>
</tr>
<tr class="even">
<td><strong>MLX</strong></td>
<td>Python</td>
<td>Native JIT</td>
<td>~1.15 it/s</td>
<td>1.3x</td>
</tr>
<tr class="odd">
<td><strong>PyTorch</strong></td>
<td><strong>Native C++</strong></td>
<td><strong>CPU (GCD)</strong></td>
<td><strong>~10.60 it/s</strong></td>
<td><strong>12.6x</strong></td>
</tr>
<tr class="even">
<td><strong>MLX</strong></td>
<td><strong>Native C++ Metal</strong></td>
<td><strong>GPU-Resident</strong></td>
<td><strong>~38.50 it/s</strong></td>
<td><strong>45.8x</strong></td>
</tr>
</tbody>
</table>
<p>Here are some training visualization over a few datasets produced by the Native Metal MLX:</p>
<p><a href="Production-Grade 3D Gaussian Splatting on Apple Si/fern_training.mp4">fern_training.mp4</a></p>
<p><a href="Production-Grade 3D Gaussian Splatting on Apple Si/pinecone_training.mp4">pinecone_training.mp4</a></p>
<p><a href="Production-Grade 3D Gaussian Splatting on Apple Si/room_training.mp4">room_training.mp4</a></p>
<p><a href="Production-Grade 3D Gaussian Splatting on Apple Si/trex_training.mp4">trex_training.mp4</a></p>
<section id="insights-the-strategy-behind-the-numbers" class="level3">
<h3 class="anchored" data-anchor-id="insights-the-strategy-behind-the-numbers"><strong>Insights: The Strategy Behind the Numbers</strong></h3>
<p>The benchmarking results reveal three critical insights into the current state of 3D computer vision on Apple Silicon:</p>
</section>
<section id="a.-mlx-and-the-power-of-gpu-residency" class="level3">
<h3 class="anchored" data-anchor-id="a.-mlx-and-the-power-of-gpu-residency"><strong>A. MLX and the Power of GPU-Residency</strong></h3>
<p>The leap from ~1 it/s in pure Python to&nbsp;<strong>38.5 it/s</strong>&nbsp;in the MLX Metal mode represents a complete paradigm shift. This was made possible by moving the&nbsp;<em>entire</em>&nbsp;pipeline—including coordinate projection, tile interaction, and rasterization—into GPU-resident kernels. By utilizing MLX’s native support for Apple’s Unified Memory Architecture (UMA), the Metal kernels access Gaussian parameters at the hardware’s full bandwidth, effectively treating the M4 GPU as a monolithic high-speed processor rather than an external accelerator.</p>
</section>
<section id="b.-pytorch-balancing-performance-and-stability" class="level3">
<h3 class="anchored" data-anchor-id="b.-pytorch-balancing-performance-and-stability"><strong>B. PyTorch: Balancing Performance and Stability</strong></h3>
<p>For the PyTorch implementation, the results highlight a different but equally important optimization path. While I explored an experimental GPU-resident version for PyTorch, the multi-threaded&nbsp;<strong>C++ CPU rasterizer</strong>&nbsp;(leveraging Apple’s Grand Central Dispatch) proved to be the superior choice for production stability. It delivers a respectable&nbsp;<strong>10.6 it/s</strong>—a 12x boost over the Python reference—while ensuring 100% visual accuracy and avoiding the stream synchronization deadlocks often encountered in manual Metal-PyTorch interop.</p>
</section>
<section id="c.-visual-parity-trade-off" class="level3">
<h3 class="anchored" data-anchor-id="c.-visual-parity-trade-off"><strong>C. Visual Parity Trade-off</strong></h3>
<p>Another interesting insight from this experiment was bridging the quality gap between the high-level Python references and the low-level native kernels. Initially, the Python versions suffered from “square clipping” and depth-related artifacts. While I successfully achieved&nbsp;<strong>full visual parity</strong>&nbsp;by implementing dynamic tile expansion and increasing depth complexity (1024 Gaussians per tile) in pure Python, this came with a considerable performance cost.</p>
<p>To maintain this high-fidelity output without custom kernels, the Python implementation must materialize and process massive vectorized tensors, which explains why it remains capped at ~1 it/s. This results in a clear hierarchy: the Python implementation serves as a high-quality reference for research and debugging, while the native C++ and Metal kernels provide the same production-grade quality at 10x to 40x the speed.</p>
</section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion"><strong>Conclusion</strong></h2>
<p>The transition from high-level Python references to native Metal extensions has fundamentally redefined what is possible for 3D computer vision on Apple Silicon. For years, production-grade 3D Gaussian Splatting was considered synonymous with NVIDIA hardware and CUDA kernels. This simple project demonstrates the possibility of leveraging Apple Silicon as part of the “first-class citizens” in the production-grade 3D AI landscape.</p>
<p>By combining the&nbsp;<strong>MLX</strong>&nbsp;framework with a fully&nbsp;<strong>GPU-resident Metal pipeline</strong>, we can deliver a solution that bridges the gap between prototyping and production. With a steady-state performance of over&nbsp;<strong>38 iterations per second</strong>, the Mac is no longer a platform restricted to slow experimentation or viewing-only roles.</p>


</section>

 ]]></description>
  <category>machine-learning</category>
  <category>computer-vision</category>
  <category>implementation</category>
  <category>research-notes</category>
  <guid>https://ghif.github.io/posts/2026-02-19-production-grade-3d-gaussian-splatting-apple-silicon/</guid>
  <pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Deep Dive into DECA: Detailed Expression Capture and Animation</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2026-02-08-deca-detailed-expression-capture-animation/</link>
  <description><![CDATA[ 




<p>In the world of 3D computer vision, reconstructing a detailed, animatable 3D face from a single 2D image is one of the “holy grail” problems. DECA (Detailed Expression Capture and Animation) is a reliable method that tackles this challenge with impressive results (<a href="https://arxiv.org/pdf/2012.04012">Feng et al., 2021</a>). It doesn’t just capture the coarse shape of the face, it captures fine details like wrinkles that appear during specific expressions, and it does so in a way that allows the face to be re-animated.</p>
<p>Reconstructing detailed 3D faces from single images isn’t just a research curiosity. It has profound implications across several domains:</p>
<ul>
<li><strong>Gaming</strong>: Imagine RPGs where we can upload a selfie to generate a perfectly aligned 3D avatar of ourselves, complete with our unique skin texture and facial structure.</li>
<li><strong>Animation &amp; Visual Effects (VFX)</strong>: DECA allows for the creation of high-fidelity digital doubles at a fraction of the cost of traditional 3D scanning. By capturing expression-dependent wrinkles, it enables more realistic performance transfer for animated characters.</li>
<li><strong>Augmented Reality (AR) &amp; Social Media</strong>: Modern face filters often struggle with extreme poses or lighting. DECA’s robustness ensures that virtual makeup, glasses, or prosthetic effects stay perfectly pinned to the face even as the user moves dynamically.</li>
<li><strong>Teleconferencing &amp; Bandwidth Optimization</strong>: Instead of streaming high-resolution video, a DECA-enabled system could transmit only the facial parameters (shape, expression, pose). The receiver than reconstructs the 3D face locally, potentially reducing bandwidth usage by orders of magnitude while maintaining visual quality.</li>
<li><strong>Plastic Surgery &amp; Healthcare</strong>: Surgeons can use DECA to provide pre-operative visualizations of facial procedures, allowing patients to see potential outcomes based on their own unique facial geometry.</li>
</ul>
<p>In this article, I’ll dissect how DECA works, exploring it architecture, the mathematical principles behind it, and the code that powers it.</p>
<section id="the-core-idea-coarse-to-fine" class="level2">
<h2 class="anchored" data-anchor-id="the-core-idea-coarse-to-fine">The Core Idea: Coarse-to-Fine</h2>
<p>DECA operates on a coarse-to-fine principle. It doesn’t try to solve everything at once. Instead, it breaks the problem down into two stages:</p>
<ol type="1">
<li><strong>Coarse Reconstruction</strong>: Estimate the underlying head shape, head pose, and facial expression using a statistical 3D face model (FLAME).</li>
<li><strong>Detail Reconstruction:</strong> Predict a person-specific detail map (displacement map) that adds high-frequency details (like forehead wrinkles or crow’s feet) to the coarse mesh.</li>
</ol>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-02-08-deca-detailed-expression-capture-animation/media/Screenshot_2026-02-08_at_19.18.31.png" class="img-fluid figure-img"></p>
<figcaption>Screenshot 2026-02-08 at 19.18.31.png</figcaption>
</figure>
</div>
</section>
<section id="the-flame-model" class="level2">
<h2 class="anchored" data-anchor-id="the-flame-model">The FLAME Model</h2>
<p>At the heart of DECA is the FLAME (Faces Learned with Articulated Model and Expressions) model (<a href="https://download.is.tue.mpg.de/flame/flame_paper.pdf">Li et al.&nbsp;2017</a>). FLAME is a parametric model, meaning it generates a 3D mesh based on a set of low-dimensional parameters.</p>
<p>Mathematically, a 3D mesh <img src="https://latex.codecogs.com/png.latex?M"> with <img src="https://latex.codecogs.com/png.latex?N"> vertices is generated as:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AM(%5Cbeta,%20%5Ctheta,%20%5Cpsi)%20=%20W%20%5Cleft(T_r(%5Cbeta,%20%5Ctheta,%20%5Cpsi),%20J(%5Cbeta),%20%5Ctheta,%20%5Cmathcal%7BW%7D%20%5Cright)%0A"></p>
<p>where:</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?%5Cbeta"> (Shape parameters): Controls identity-specific shape (e.g., big nose, wide jaw).</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cpsi"> (Expression parameters): Controls facial expressions (e.g., smile, frown).</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Ctheta"> (Pose parameters): Controls the rotation of the neck and jaw.</li>
</ul>
<p>I discussed about FLAME in more details in my <a href="https://app.notion.com/p/Unmasking-FLAME-The-Articulated-3D-Face-Model-Powered-by-Apple-MLX-2f8a59d373608031b513fb39205f7021?pvs=21">previous article</a>.</p>
<p>In DECA, the goal of the neural network is to predict these parameters (<img src="https://latex.codecogs.com/png.latex?%5Cbeta">, <img src="https://latex.codecogs.com/png.latex?%5Cpsi">, <img src="https://latex.codecogs.com/png.latex?%5Ctheta">) along with camera parameters <img src="https://latex.codecogs.com/png.latex?c"> and lighting <img src="https://latex.codecogs.com/png.latex?l"> from a single input image.</p>
</section>
<section id="coarse-reconstruction-stream" class="level2">
<h2 class="anchored" data-anchor-id="coarse-reconstruction-stream">Coarse Reconstruction Stream</h2>
<p>The coarse stream is a straightforward encoder-decoder architecture.</p>
<section id="the-encoder" class="level3">
<h3 class="anchored" data-anchor-id="the-encoder">The Encoder</h3>
<p>DECA uses a ResNet-50 backbone to extract a 2048-dimensional feature vector from the input image. This vector is then projected down to the parameter space of the FLAME model.</p>
<p>Here is the implementation of the encoder with PyTorch:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> ResnetEncoder(nn.Module):</span>
<span id="cb1-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, outsize, last_op<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb1-3">      <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>(ResnetEncoder, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>).<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb1-4">    feature_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2048</span></span>
<span id="cb1-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.encoder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> resnet.load_ResNet50Model() <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Backbone</span></span>
<span id="cb1-6">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">### regressor</span></span>
<span id="cb1-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Sequential(</span>
<span id="cb1-8">        nn.Linear(feature_size, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1024</span>),</span>
<span id="cb1-9">            nn.ReLU(),</span>
<span id="cb1-10">            nn.Linear(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1024</span>, outsize) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Projects to n_shape + n_exp + n_pose + ...</span></span>
<span id="cb1-11">    )</span>
<span id="cb1-12">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.last_op <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> last_op</span>
<span id="cb1-13"></span>
<span id="cb1-14">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, inputs):</span>
<span id="cb1-15">            features <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.encoder(inputs)</span>
<span id="cb1-16">            parameters <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layers(features)</span>
<span id="cb1-17">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.last_op:</span>
<span id="cb1-18">                parameters <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.last_op(parameters)</span>
<span id="cb1-19">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> parameters</span></code></pre></div></div>
</section>
<section id="the-decoder" class="level3">
<h3 class="anchored" data-anchor-id="the-decoder">The Decoder</h3>
<p>Once the parameters are predicted, DECA uses a differentiable renderer <img src="https://latex.codecogs.com/png.latex?(%5Cmathcal%7BR%7D)">. This allows the model to “draw” the 3D face back into 2D (<img src="https://latex.codecogs.com/png.latex?I_r">). By comparing this rendered image to the original input image, the network learns to align the 3D model to the 2D photo. This analysis-by-synthesis loop allows the model to learn without 3D ground truth by minimizing the photometric difference between the input image and the rendering.</p>
<p>The rendering process involves 3 primary components:</p>
<ol type="1">
<li><strong>Geometry Rendering</strong>: The FLAME model generates vertices <img src="https://latex.codecogs.com/png.latex?M%20%5Cin%20%5Cmathbb%7BR%7D%5E%7B3N%7D"> which are projected into 2D image space using an orthographic camera model <img src="https://latex.codecogs.com/png.latex?c=%5Bs,%20t_x,%20t_y%5D">:</li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?%0Av=s%5Ccdot%20%5CPi%20(M_i)%20+%20t%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5CPi"> is the 3D-to-2D projection matrix.</p>
<ol type="1">
<li><strong>Albedo Mapping</strong>: A UV albedo map <img src="https://latex.codecogs.com/png.latex?A(%5Calpha)"> is generated from the predicted albedo parameters <img src="https://latex.codecogs.com/png.latex?%5Calpha">. This map represents the intrinsic surface color of the face.</li>
<li><strong>Shading &amp; Lighting</strong>: DECA uses Spherical Harmonics (SH) to model global illumination. The shaded face image <img src="https://latex.codecogs.com/png.latex?B"> in UV coordinates is computed as:</li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bequation%7DB(%5Calpha,%20%5Cmathbf%7B1%7D,%20N_%7B%5Cmathrm%7Buv%7D%7D)_%7Bi,%20j%7D%20=%20A(%5Calpha)_%7Bi,j%7D%20%5Codot%20%5Csum_%7Bk=1%7D%5E%7B9%7D%20%5Cmathbf%7B1%7D_k%20H_k%20(N_%7Bi,j%7D)%20%5Cend%7Bequation%7D%0A"></p>
<p>where:</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?N_%7Bi,j%7D"> is the surface normal at pixel <img src="https://latex.codecogs.com/png.latex?(i,j)"> in UV space.</li>
<li><img src="https://latex.codecogs.com/png.latex?H_k:%5Cmathbb%7BR%7D%5E3%20%5Crightarrow%20%5Cmathbb%7BR%7D"> are the SH basis functions.</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7B1%7D_k%20%5Cin%20%5Cmathbb%7BR%7D%5E3"> are the predicted lighting coefficients.</li>
</ul>
<p>The final rendered image is then produced by the rendering function:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AI_r%20=%20%5Cmathcal%7BR%7D(M,%20B,%20c)%0A"></p>
</section>
</section>
<section id="detail-reconstruction-stream" class="level2">
<h2 class="anchored" data-anchor-id="detail-reconstruction-stream">Detail Reconstruction Stream</h2>
<p>This is where DECA shines. A standard parametric model is too smooth. To capture wrinkles, DECA also predicts a UV displacement map.</p>
<p>The detail reconstruction uses another encoder to extract a latent detail code <img src="https://latex.codecogs.com/png.latex?%5Cdelta">. This code, combined with the expression <img src="https://latex.codecogs.com/png.latex?%5Cpsi"> and pose <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> parameters, is fed into a generator.</p>
<p>Why include expression and pose? Because wrinkles are dynamic. A forehead wrinkle appears when we raise our eyebrows. By conditioning the generator on expression, DECA learns expression-dependent details.</p>
<p>Here is the implementation of the detail reconstruction through <code>Generator</code> class below.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Generator(nn.Module):</span>
<span id="cb2-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, latent_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, out_channels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, out_scale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span>, sample_mode <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'bilinear'</span>):</span>
<span id="cb2-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>(Generator, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>).<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb2-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.out_scale <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> out_scale</span>
<span id="cb2-5">        </span>
<span id="cb2-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.init_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span> </span>
<span id="cb2-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.l1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Sequential(nn.Linear(latent_dim, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">128</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.init_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb2-8">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.conv_blocks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Sequential(</span>
<span id="cb2-9">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Upsampling layers to generate a high-res displacement map</span></span>
<span id="cb2-10">            nn.BatchNorm2d(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">128</span>),</span>
<span id="cb2-11">            nn.Upsample(scale_factor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>sample_mode), </span>
<span id="cb2-12">            nn.Conv2d(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">128</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">128</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, stride<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, padding<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>),</span>
<span id="cb2-13">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... (more layers) ...</span></span>
<span id="cb2-14">            nn.Conv2d(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>, out_channels, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, stride<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, padding<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>),</span>
<span id="cb2-15">            nn.Tanh(),</span>
<span id="cb2-16">        )</span>
<span id="cb2-17"></span>
<span id="cb2-18">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, noise):</span>
<span id="cb2-19">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.l1(noise)</span>
<span id="cb2-20">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> out.view(out.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">128</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.init_size, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.init_size)</span>
<span id="cb2-21">        img <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.conv_blocks(out)</span>
<span id="cb2-22">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> img <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.out_scale</span></code></pre></div></div>
<p>The output is a displacement map <img src="https://latex.codecogs.com/png.latex?D">. The final detailed mesh vertices <img src="https://latex.codecogs.com/png.latex?V_%7B%5Cmathrm%7Bdetail%7D%7D"> are calculated by displacing the coarse vertices <img src="https://latex.codecogs.com/png.latex?V_%7B%5Cmathrm%7Bcoarse%7D%7D"> along their normals <img src="https://latex.codecogs.com/png.latex?N_%7B%5Cmathrm%7Buv%7D%7D">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bequation%7DV_%7B%5Cmathrm%7Bdetail%7D%7D%20=%20V_%7B%5Cmathrm%7Bcoarse%7D%7D%20+%20D%20%5Codot%20N_%7B%5Cmathrm%7Buv%7D%7D%5Cend%7Bequation%7D%0A"></p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-02-08-deca-detailed-expression-capture-animation/media/ghif_face1_vis.jpg" class="img-fluid figure-img"></p>
<figcaption>ghif_face1_vis.jpg</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-02-08-deca-detailed-expression-capture-animation/media/ghif_face1_animation.gif" class="img-fluid figure-img"></p>
<figcaption>ghif_face1_animation.gif</figcaption>
</figure>
</div>
</section>
<section id="training-the-loss-functions" class="level2">
<h2 class="anchored" data-anchor-id="training-the-loss-functions">Training: The Loss Functions</h2>
<p>DECA uses a complex combination of loss functions to train these networks without requiring 3D ground truth (unsupervised learning).</p>
<p><strong>Landmark Loss (<img src="https://latex.codecogs.com/png.latex?L_%7B%5Cmathrm%7Blmk%7D%7D">)</strong>: ****Ensures the projected 3D keypoints match the 2D facial landmarks detected in the image.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> landmark_loss(predicted_landmarks, landmarks_gt, weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.</span>):</span>
<span id="cb3-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> torch.is_tensor(landmarks_gt) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>:</span>
<span id="cb3-3">        real_2d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.cat(landmarks_gt).cuda()</span>
<span id="cb3-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb3-5">        real_2d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.cat([landmarks_gt, torch.ones((landmarks_gt.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">68</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)).cuda()], dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb3-6"></span>
<span id="cb3-7">    loss_lmk_2d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> batch_kp_2d_l1_loss(real_2d, predicted_landmarks)</span>
<span id="cb3-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> loss_lmk_2d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> weight</span></code></pre></div></div>
<p><strong>Photometric Loss (<img src="https://latex.codecogs.com/png.latex?L_%7B%5Cmathrm%7Bpho%7D%7D">)</strong>: ****Computes the pixel-wise difference between the input image and the rendered 3D face. This forces the texture and shape to match the input.</p>
<p><strong>Identity Loss (<img src="https://latex.codecogs.com/png.latex?L_%7B%5Cmathrm%7Bid%7D%7D">)</strong>: ****Uses a pretrained Face Recognition network (like ResNet50 trained on VGGFace2) to extract feature vectors. The loss minimizes the cosine distance between the features of the input image and the rendered image. This ensures the reconstructed 3D face “looks like” the person.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> VGGFace2Loss(nn.Module):</span>
<span id="cb4-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ...</span></span>
<span id="cb4-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, gen, tar, is_crop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>):</span>
<span id="cb4-4">        gen <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transform(gen)</span>
<span id="cb4-5">        tar <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transform(tar)</span>
<span id="cb4-6"></span>
<span id="cb4-7">        gen_out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.reg_features(gen)</span>
<span id="cb4-8">        tar_out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.reg_features(tar)</span>
<span id="cb4-9">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Cosine similarity loss</span></span>
<span id="cb4-10">        loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._cos_metric(gen_out, tar_out).mean()</span>
<span id="cb4-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> loss</span></code></pre></div></div>
<p><strong>Detail Loss (<img src="https://latex.codecogs.com/png.latex?L_%7B%5Cmathrm%7Bmrf%7D%7D">)</strong>: ****Since pixel-wise difference doesn’t work well for high-frequency details (due to slight misalignments), DECA uses an&nbsp;<strong>ID-MRF loss</strong>. This matches local patches of features from the input image to the rendered detail image, effectively transferring the “style” (details) of the photo to the 3D model.</p>
</section>
<section id="putting-it-all-together-the-inference-process" class="level2">
<h2 class="anchored" data-anchor-id="putting-it-all-together-the-inference-process"><strong>Putting It All Together: The Inference Process</strong></h2>
<p>A critical distinction of DECA’s architecture is that it is a&nbsp;<strong>learning-based approach</strong>, as opposed to traditional&nbsp;<strong>optimization-based</strong>&nbsp;methods. While optimization-based methods solve an energy minimization problem for&nbsp;<em>every</em>&nbsp;image (seconds or minutes), DECA’s pipeline predicts all parameters in a single&nbsp;<strong>forward pass</strong>&nbsp;(milliseconds). This enables the real-time interaction and animation.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-02-08-deca-detailed-expression-capture-animation/media/teaser.gif" class="img-fluid figure-img"></p>
<figcaption>teaser.gif</figcaption>
</figure>
</div>
<p>When DECA is run on a face image, the following sequence of operations occurs:</p>
<section id="step-1-preprocessing-encoding" class="level3">
<h3 class="anchored" data-anchor-id="step-1-preprocessing-encoding">Step 1: Preprocessing &amp; Encoding</h3>
<p>The input image <img src="https://latex.codecogs.com/png.latex?I"> is cropped and resized to <img src="https://latex.codecogs.com/png.latex?224%20%5Ctimes%20224">. The ResNet-50 encoder predicts a vector of parameters <img src="https://latex.codecogs.com/png.latex?%5CTheta">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5CTheta%20=%20%5C%7B%20%5Cbeta,%20%5Cpsi,%20%5Ctheta,%20c,%20l,%20%5Cdelta%20%5C%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%5Cin%20%5Cmathbb%7BR%7D%5E%7B100%7D"> (shape), <img src="https://latex.codecogs.com/png.latex?%5Cpsi%20%5Cin%20%5Cmathbb%7BR%7D%5E%7B50%7D"> (expression), <img src="https://latex.codecogs.com/png.latex?%5Ctheta%20%5Cin%20%5Cmathbb%7BR%7D%5E%7B6%7D"> (pose), <img src="https://latex.codecogs.com/png.latex?c%20%5Cin%20%5Cmathbb%7BR%7D%5E%7B3%7D"> (camera), <img src="https://latex.codecogs.com/png.latex?l%20%5Cin%20%5Cmathbb%7BR%7D%5E%7B27%7D"> (lighting), and <img src="https://latex.codecogs.com/png.latex?%5Cdelta%20%5Cin%20%5Cmathbb%7BR%7D%5E%7B128%7D"> (detail).</p>
</section>
<section id="step-2-flame-decoding-coarse-geometry" class="level3">
<h3 class="anchored" data-anchor-id="step-2-flame-decoding-coarse-geometry">Step 2: FLAME Decoding (Coarse Geometry)</h3>
<p>The FLAME model uses <img src="https://latex.codecogs.com/png.latex?%5Cbeta,%20%5Cpsi,%20%5Ctheta"> to generate the coarse vertices <img src="https://latex.codecogs.com/png.latex?V_%7B%5Cmathrm%7Bcoarse%7D%7D"> in world coordinates.</p>
</section>
<section id="step-3-orthographic-projection" class="level3">
<h3 class="anchored" data-anchor-id="step-3-orthographic-projection">Step 3: Orthographic Projection</h3>
<p>The camera parameters <img src="https://latex.codecogs.com/png.latex?c%20=%20%5Bs,%20t_x,%20t_y%5D"> are used to project the 3D vertices into 2D Normalized Device Coordinates (NDC):</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV_%7B%5Cmathrm%7Bndc%7D%7D%20=%20s%20%5Ccdot%20(V_%7Bcoarse%7D%20+%20%5Bt_x,%20t_y,%200%5D%5E%5Ctop)%0A"></p>
</section>
<section id="step-4-detail-generation-displacement" class="level3">
<h3 class="anchored" data-anchor-id="step-4-detail-generation-displacement">Step 4: Detail Generation &amp; Displacement</h3>
<p>The detail encoder predicts <img src="https://latex.codecogs.com/png.latex?%5Cdelta">, which is combined with expression <img src="https://latex.codecogs.com/png.latex?%5Cpsi"> and pose <img src="https://latex.codecogs.com/png.latex?%5Ctheta">. The Generator outputs a UV displacement map <img src="https://latex.codecogs.com/png.latex?D">. The vertices are then updated through Equation (2).</p>
</section>
<section id="step-5-differentiable-rendering-lighting" class="level3">
<h3 class="anchored" data-anchor-id="step-5-differentiable-rendering-lighting">Step 5: Differentiable Rendering &amp; Lighting</h3>
<p>The final image is rendered using Spherical Harmonics (SH) lighting according to Equation (1).</p>
</section>
</section>
<section id="custom-c-cpu-rasterizer" class="level2">
<h2 class="anchored" data-anchor-id="custom-c-cpu-rasterizer">Custom C++ CPU Rasterizer</h2>
<p>In standard DECA (and many modern 3D project), the rasterization step is typically handled by CUDA-based engines or heavy libraries like PyTorch3D. To achieve native, fast performance on a local machine (e.g., Apple Silicon) without these dependencies, I implemented a custom C++ CPU rasterizer that also leverages <a href="https://docs.pytorch.org/cppdocs/">PyTorch C++ API</a>.</p>
<section id="the-core-algorithm" class="level3">
<h3 class="anchored" data-anchor-id="the-core-algorithm">The Core Algorithm</h3>
<p>The rasterizer uses a traditional Barycentric-based rasterization technique with Z-buffering. For every triangle in the mesh, we perform the following steps:</p>
<ol type="1">
<li>NDC to Raster Mapping: Vertices are transformed from Normalized Device Coordinates (NDC) to pixel coordinates:</li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?%0Ax_%7B%5Cmathrm%7Bpixel%7D%7D%20=%20x_%7B%5Cmathrm%7Bndc%7D%7D%20%5Ccdot%20%5Cfrac%7BW%7D%7B2%7D%20+%20%5Cfrac%7BW%7D%7B2%7D%20%5C%5C%0Ay_%7B%5Cmathrm%7Bpixel%7D%7D%20=%20y_%7B%5Cmathrm%7Bndc%7D%7D%20%5Ccdot%20%5Cfrac%7BW%7D%7B2%7D%20+%20%5Cfrac%7BW%7D%7B2%7D%0A"></p>
<ol type="1">
<li>Bounding Box Optimization: Instead of checking every pixel in the image, we only iterate over the pixels within the triangle’s actual screen-space bounding box.</li>
<li>Barycentric Weight Calculation: For each pixel <img src="https://latex.codecogs.com/png.latex?P"> in the bounding box, we calculate weights (<img src="https://latex.codecogs.com/png.latex?w_0,%20w_1,%20w_2">) such that:</li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?%0AP%20=%20w_0%20P_0%20+%20w_1%20P_1%20+%20w_2%20P_2%0A"></p>
<p>The weights are computed using the dot products of vectors</p>
<p>$$</p>
<p>v_0 = P_2 - P_0, v_1 = P_1 - P_0, v2 = P - P_0 \ denom=(v_0 v_0)(v_1v_1)−(v_0 v_1)(v_0 v_1) \ u = \ v = \ w_0 = 1 - u - v, w_1 = v, w_2 = u $$</p>
<ol type="1">
<li>Point-in-Triangle Test: A pixel is “inside” if <img src="https://latex.codecogs.com/png.latex?w_i%20%5Cge%200"> for all <img src="https://latex.codecogs.com/png.latex?i">.</li>
<li>Perspective Correct Depth: To handle the 3D depth correctly during 2D projection, the inverse-depth interpolation is used:</li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?%0AZ_p%20=%20%5Cfrac%7B1%7D%7B%5Cfrac%7Bw_0%7D%7Bz_0%7D%20+%20%5Cfrac%7Bw_1%7D%7Bz_1%7D%20+%20%5Cfrac%7Bw_2%7D%7Bz_2%7D%7D%0A"></p>
<ol type="1">
<li>Z-Buffering: If the calculated <img src="https://latex.codecogs.com/png.latex?Z_p"> is smaller (closer to the camera) than the value currently in the depth buffer, the pixel is updated with new depth, triangle index, and barycentric coordinates.</li>
</ol>
<p>The following is the C++ kernel implementation.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode cpp code-with-copy"><code class="sourceCode cpp"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//standard_rasterize_cpu.cpp</span></span>
<span id="cb5-2"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">#include </span><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">&lt;torch/extension.h&gt;</span></span>
<span id="cb5-3"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">#include </span><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">&lt;vector&gt;</span></span>
<span id="cb5-4"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">#include </span><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">&lt;cmath&gt;</span></span>
<span id="cb5-5"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">#include </span><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">&lt;algorithm&gt;</span></span>
<span id="cb5-6"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">#include </span><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">&lt;iostream&gt;</span></span>
<span id="cb5-7"></span>
<span id="cb5-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Helper struct for 2D points</span></span>
<span id="cb5-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">template</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">typename</span> T<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb5-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">struct</span> Point <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-11">    T x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-12"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span>
<span id="cb5-13"></span>
<span id="cb5-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Barycentric weight calculation</span></span>
<span id="cb5-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">template</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">typename</span> T<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb5-16"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span> barycentric_weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>T<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> w<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> Point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>T<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> Point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>T<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> p0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> Point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>T<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> p1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> Point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>T<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> p2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-17">    Point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>T<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> v0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>p2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> p0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> p0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span>
<span id="cb5-18">    Point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>T<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> v1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>p1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> p0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> p0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span>
<span id="cb5-19">    Point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>T<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> v2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> p0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> p0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span>
<span id="cb5-20"></span>
<span id="cb5-21">    T dot00 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> v0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> v0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-22">    T dot01 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> v0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> v0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-23">    T dot02 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> v0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> v0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-24">    T dot11 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> v1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> v1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-25">    T dot12 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> v1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> v1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-26"></span>
<span id="cb5-27">    T denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dot00 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dot11 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> dot01 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dot01<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-28">    T invDenom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">?</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> denom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-29"></span>
<span id="cb5-30">    T u <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>dot11 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dot02 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> dot01 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dot12<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> invDenom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-31">    T v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>dot00 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dot12 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> dot01 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dot02<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> invDenom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-32"></span>
<span id="cb5-33">    w<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> u <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> v<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-34">    w<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> v<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-35">    w<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> u<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-36"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb5-37"></span>
<span id="cb5-38"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span> standard_rasterize_cpu_kernel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb5-39">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">const</span> torch<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>Tensor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> face_vertices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-40">    torch<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>Tensor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> depth_buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-41">    torch<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>Tensor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> triangle_buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-42">    torch<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>Tensor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> baryw_buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-43">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> h<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> w</span>
<span id="cb5-44"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-45">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// face_vertices: [B, F, 3, 3]</span></span>
<span id="cb5-46">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// depth_buffer: [B, H, W]</span></span>
<span id="cb5-47">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// triangle_buffer: [B, H, W]</span></span>
<span id="cb5-48">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// baryw_buffer: [B, H, W, 3]</span></span>
<span id="cb5-49"></span>
<span id="cb5-50">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">auto</span> B <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> face_vertices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb5-51">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">auto</span> F <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> face_vertices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb5-52"></span>
<span id="cb5-53">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Get accessors for efficient element access</span></span>
<span id="cb5-54">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Assuming float input as per standard renderer usage</span></span>
<span id="cb5-55">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">auto</span> face_vertices_a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> face_vertices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>accessor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;();</span></span>
<span id="cb5-56">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">auto</span> depth_buffer_a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> depth_buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>accessor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;();</span></span>
<span id="cb5-57">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">auto</span> triangle_buffer_a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> triangle_buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>accessor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;();</span></span>
<span id="cb5-58">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">auto</span> baryw_buffer_a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> baryw_buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>accessor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;();</span></span>
<span id="cb5-59"></span>
<span id="cb5-60">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> b <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> b <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> B<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">++</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-61">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> f <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> f <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> F<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">++</span>f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-62">            <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span> p0x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> face_vertices_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb5-63">            <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span> p0y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> face_vertices_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb5-64">            <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span> p0z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> face_vertices_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb5-65"></span>
<span id="cb5-66">            <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span> p1x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> face_vertices_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb5-67">            <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span> p1y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> face_vertices_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb5-68">            <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span> p1z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> face_vertices_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb5-69"></span>
<span id="cb5-70">            <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span> p2x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> face_vertices_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb5-71">            <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span> p2y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> face_vertices_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb5-72">            <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span> p2z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> face_vertices_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb5-73"></span>
<span id="cb5-74">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Calculate bounding box for the triangle</span></span>
<span id="cb5-75">            <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> x_min <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>max<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>ceil<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>min<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">({</span>p0x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p1x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p2x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">})));</span></span>
<span id="cb5-76">            <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> x_max <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>min<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>w <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>floor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>max<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">({</span>p0x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p1x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p2x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">})));</span></span>
<span id="cb5-77">            <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> y_min <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>max<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>ceil<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>min<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">({</span>p0y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p1y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p2y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">})));</span></span>
<span id="cb5-78">            <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> y_max <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>min<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>floor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>max<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">({</span>p0y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p1y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p2y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">})));</span></span>
<span id="cb5-79"></span>
<span id="cb5-80">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x_min <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> x_max <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">||</span> y_min <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> y_max<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-81"></span>
<span id="cb5-82">            Point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> p0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>p0x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p0y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span>
<span id="cb5-83">            Point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> p1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>p1x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p1y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span>
<span id="cb5-84">            Point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> p2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>p2x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p2y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span>
<span id="cb5-85"></span>
<span id="cb5-86">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> y_min<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> y_max<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">++</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-87">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x_min<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> x_max<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">++</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-88">                    Point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span>
<span id="cb5-89">                    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span> bw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb5-90">                    barycentric_weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>bw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb5-91"></span>
<span id="cb5-92">                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Check if pixel is inside the triangle</span></span>
<span id="cb5-93">                    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>bw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;&amp;</span> bw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;&amp;</span> bw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-94">                        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Perspective correct depth interpolation</span></span>
<span id="cb5-95">                        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">float</span> zp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">f</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>bw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> p0z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> bw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> p1z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> bw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> p2z<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb5-96">                        </span>
<span id="cb5-97">                        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Z-buffer test</span></span>
<span id="cb5-98">                        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>zp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> depth_buffer_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">])</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-99">                            depth_buffer_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> zp<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-100">                            triangle_buffer_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-101">                            baryw_buffer_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb5-102">                            baryw_buffer_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb5-103">                            baryw_buffer_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">][</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span></span>
<span id="cb5-104">                        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb5-105">                    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb5-106">                <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb5-107">            <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb5-108">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb5-109">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb5-110"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb5-111"></span>
<span id="cb5-112"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span> standard_rasterize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb5-113">    torch<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>Tensor face_vertices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-114">    torch<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>Tensor depth_buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-115">    torch<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>Tensor triangle_buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-116">    torch<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>Tensor baryw_buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-117">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> h<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> w</span>
<span id="cb5-118"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-119">    standard_rasterize_cpu_kernel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>face_vertices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> depth_buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> triangle_buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> baryw_buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> h<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> w<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb5-120"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb5-121"></span>
<span id="cb5-122">PYBIND11_MODULE<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>TORCH_EXTENSION_NAME<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> m<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-123">    m<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>def<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"standard_rasterize"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>standard_rasterize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Standard Rasterize (CPU)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb5-124"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> torch.utils.cpp_extension <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load</span>
<span id="cb6-2">...</span>
<span id="cb6-3"></span>
<span id="cb6-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb6-5">    standard_rasterize_cpu_module <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> load(</span>
<span id="cb6-6">    name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'standard_rasterize_cpu'</span>, </span>
<span id="cb6-7">    sources<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[source_path],</span>
<span id="cb6-8">    verbose<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb6-9">    )</span>
<span id="cb6-10">  _standard_rasterize_cpu_impl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> standard_rasterize_cpu_module.standard_rasterize</span>
<span id="cb6-11">  <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"[set_rasterizer] Successfully loaded standard rasterizer extension."</span>)</span>
<span id="cb6-12"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> e:</span>
<span id="cb6-13">  <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"[set_rasterizer] Error loading C++ extension: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>e<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb6-14">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span></span>
<span id="cb6-15">...</span></code></pre></div></div>
<p>By wrapping this C++ implementation as a PyTorch extension, a rendering speed that makes DECA feel incredibly fluid on Macbooks.</p>
<video controls="" preload="metadata" width="100%">
<source src="media/demo_viser.mp4" type="video/mp4">
<p>Your browser does not support embedded video.</p>
</video></section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>DECA represents a considerable milestone in 3D facial mesh reconstruction. By combining robust coarse shape estimation with detailed, expression-driven displacement mapping, it bridges the gap between static mesh reconstruction and realistic animation.</p>
<p>With the native Apple Silicon port leveraging PyTorch capabilities to run on the CPU/MPS devices, we can run DECA without requiring CUDA GPU.</p>
<p><em>For the full source code and implementation details, check out my forked <a href="https://github.com/ghif/DECA_pt-apple">DECA repository</a>.</em></p>


</section>

 ]]></description>
  <category>machine-learning</category>
  <category>computer-vision</category>
  <category>deep-learning</category>
  <category>implementation</category>
  <category>research-notes</category>
  <guid>https://ghif.github.io/posts/2026-02-08-deca-detailed-expression-capture-animation/</guid>
  <pubDate>Sun, 08 Feb 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Unmasking FLAME: The Articulated 3D Face Model Powered by Apple MLX</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2026-01-30-flame-articulated-3d-face-model-mlx/</link>
  <description><![CDATA[ 




<p>Facial modeling has come a long way since the early days of simple 3D scans. Today, the&nbsp;<strong>FLAME</strong>&nbsp;(Faces Learned with an Articulated Model and Expressions) model stands as a cornerstone in computer vision, providing a powerful, differentiable, and highly expressive framework for human head modeling (<a href="https://download.is.tue.mpg.de/flame/flame_paper.pdf">Li et al.&nbsp;2017</a>).</p>
<p>In this article, we’ll dive deep into the mechanics of FLAME, explore recent breakthroughs, and look at how we can leverage Apple’s&nbsp;<strong>MLX</strong>&nbsp;framework to run these models at lightning speeds on Apple Silicon.</p>
<section id="what-is-flame" class="level2">
<h2 class="anchored" data-anchor-id="what-is-flame">What is FLAME?</h2>
<p>FLAME is a Linear Blend Skinning (LBS) model that captures the vast variety of human head shapes and expressions. Unlike older models that focused solely on the face, FLAME models the entire head — including the neck, jaw, and eyballs.</p>
<p>At its heart, FLAME represents a 3D mesh <img src="https://latex.codecogs.com/png.latex?M(%5Cvec%7B%5Cbeta%7D,%20%5Cvec%7B%5Ctheta%7D,%20%5Cvec%7B%5Cpsi%7D)">, which is a function of:</p>
<ul>
<li>Shape parameters <img src="https://latex.codecogs.com/png.latex?%5Cvec%7B%5Cbeta%7D">: Identity-specific features (height, face width, etc.).</li>
<li>Pose parameters <img src="https://latex.codecogs.com/png.latex?%5Cvec%7B%5Ctheta%7D">: Rotations for the neck, jaw, and eyeballs.</li>
<li>Expression parameters <img src="https://latex.codecogs.com/png.latex?%5Cvec%7B%5Cpsi%7D">: Dynamic movements like smiles or frowns.</li>
</ul>
<p>The final position of a vertex <img src="https://latex.codecogs.com/png.latex?v"> is calculated using the LBS formula:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Av_%7B%5Cmathrm%7Bfinal%7D%7D%20=%20%5Csum_%7Bj=1%7D%5E%7BJ%7D%20w_%7Bj%7D%20G_j(%5Cvec%7B%5Ctheta%7D,%20J)%20(v_%7B%5Cmathrm%7Btemplate%7D%7D%20+%20B_s(%5Cvec%7B%5Cbeta%7D)%20+%20B_p(%5Cvec%7B%5Ctheta%7D)%20+%20B_e(%5Cvec%7B%5Cpsi%7D))%0A"></p>
<p>where</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?v_%7B%5Cmathrm%7Btemplate%7D%7D"> is the average head shape.</li>
<li><img src="https://latex.codecogs.com/png.latex?B_s">, <img src="https://latex.codecogs.com/png.latex?B_p">, <img src="https://latex.codecogs.com/png.latex?B_e"> are the Shape, Pose, and Expression blendshapes.</li>
<li><img src="https://latex.codecogs.com/png.latex?G_j"> is the global transformation matrix for point <img src="https://latex.codecogs.com/png.latex?j"> - <img src="https://latex.codecogs.com/png.latex?w_j"> are the skinning weights.</li>
</ul>
</section>
<section id="recent-advancements" class="level2">
<h2 class="anchored" data-anchor-id="recent-advancements">Recent Advancements</h2>
<p>FLAME is not just a static model; it has become the core for modern facial research.</p>
<section id="flame-2023-open-mouth-and-beyond" class="level3">
<h3 class="anchored" data-anchor-id="flame-2023-open-mouth-and-beyond">FLAME 2023: Open Mouth and Beyond</h3>
<p>Recent updates to <a href="https://github.com/Suraj-Bhor/flame_to_flame_open_converter">FLAME</a> have expanded its topology to better handle open-mouth scenarios and interior details, making it more robust for realistic speech animation.</p>
</section>
<section id="monocular-reconstruction-deca-emoca" class="level3">
<h3 class="anchored" data-anchor-id="monocular-reconstruction-deca-emoca">Monocular Reconstruction: DECA &amp; EMOCA</h3>
<p>Models like <strong>DECA</strong> (Detailed Expression Capture and Animation) leverage FLAME to reconstruct a detailed 3D face from a single 2D image (<a href="https://arxiv.org/pdf/2012.04012">Feng et al.&nbsp;2021</a>). <strong>EMOCA</strong> (Emotion-driven Monocular Face Capture) takes this a step further by prioritizing the emotional content of the expression, ensuring that the 3D model doesn’t just look like the person, but feels like them (<a href="https://openaccess.thecvf.com/content/CVPR2022/papers/Danecek_EMOCA_Emotion_Driven_Monocular_Face_Capture_and_Animation_CVPR_2022_paper.pdf">Daněček et al.&nbsp;2022</a>).</p>
</section>
<section id="integration-with-neural-rendering-nerf-and-gaussian-splatting" class="level3">
<h3 class="anchored" data-anchor-id="integration-with-neural-rendering-nerf-and-gaussian-splatting">Integration with Neural Rendering (NeRF and Gaussian Splatting)</h3>
<p>One of the most exciting trends is the marriage of FLAME with implicit neural representations and point-based rendering. Some examples of research work are:</p>
<ul>
<li><strong>IMAvatar (<a href="https://arxiv.org/pdf/2112.07471">Zheng et al., 2022</a>)</strong>: Implements “Implicit Morphable Avatars,” where FLAME is used to provide the underlying structure for a neural signed distance function (SDF), allowing for high-fidelity rendering from monocular video.</li>
<li><strong>NeRFlame (<a href="https://arxiv.org/pdf/2303.06226">Zając et al., 2023</a>)</strong>: This work uses the FLAME mesh to explicitly define the density volume within a NeRF, ensuring that the neural radiance field remains geometrically consistent with the underlying head model.</li>
<li><strong>HeadGaS (<a href="https://www.ecva.net/papers/eccv_2024/papers_ECCV/papers/00280.pdf">Dhamo et al., 2023</a>)</strong>: Represents the state-of-the-art in&nbsp;<strong>3D Gaussian Splatting (3DGS)</strong>&nbsp;integration. It binds 3D Gaussians to the triangles of a FLAME mesh; as the mesh deforms with expression coefficients, the Gaussians move and scale accordingly, enabling real-time, photorealistic animation at hundreds of frames per second.</li>
<li><strong>GaussianAvatars (<a href="https://arxiv.org/pdf/2312.02069">Qian et al., 2024</a>)</strong>: This paper that takes rigging to the next level. It introduces a&nbsp;<strong>“binding inheritance strategy”</strong>&nbsp;to rig 3D Gaussians directly to the FLAME topology. By initializing Gaussians on each triangle and learning their relative offsets, it achieves extremely high-fidelity head avatars that inherit the full articulability of the FLAME model.</li>
</ul>
</section>
</section>
<section id="how-is-flame-trained" class="level2">
<h2 class="anchored" data-anchor-id="how-is-flame-trained">How is FLAME Trained?</h2>
<p>Understanding the usage of FLAME is one thing, but how is such a model actually created? The training of FLAME is an iterative process of statistical learning from raw 3D data.</p>
<section id="data-collection-and-registration" class="level3">
<h3 class="anchored" data-anchor-id="data-collection-and-registration">1. Data Collection and Registration</h3>
<p>The foundation of FLAME is a massive dataset of over&nbsp;<strong>33,000 high-fidelity 3D scans</strong>.</p>
<ul>
<li><strong>Shape Space</strong>: Derived from ~3,800 head scans of different individuals.</li>
<li><strong>Expression Space</strong>: Learned from 4D sequences (videos of 3D scans) from the D3DFACS dataset.</li>
</ul>
<p>The “magic” happens during&nbsp;<strong>Registration</strong>. Raw 3D scans are just “bags of points” (unstructured point clouds). To train a model, researchers must map a fixed-topology template mesh (the 5023 vertex structure) onto every single scan. This ensures that vertex #100 is always the tip of the nose, regardless of the person’s identity or expression.</p>
</section>
<section id="learning-the-components-pca" class="level3">
<h3 class="anchored" data-anchor-id="learning-the-components-pca">2. Learning the Components (PCA)</h3>
<p>Once the scans are co-registered,&nbsp;<strong>Principal Component Analysis (PCA)</strong>&nbsp;is performed on the vertex displacements.</p>
<ul>
<li>The&nbsp;<strong>Mean Mesh</strong>&nbsp;<img src="https://latex.codecogs.com/png.latex?%5Cbar%7BT%7D">&nbsp;is calculated.</li>
<li>The&nbsp;<strong>Shape Basis</strong>&nbsp;<img src="https://latex.codecogs.com/png.latex?S">&nbsp;and&nbsp;<strong>Expression Basis</strong>&nbsp;<img src="https://latex.codecogs.com/png.latex?%5Cvarepsilon">&nbsp;are learned by capturing the directions of maximum variance in the data.</li>
</ul>
</section>
<section id="optimization-of-joints-lbs" class="level3">
<h3 class="anchored" data-anchor-id="optimization-of-joints-lbs">3. Optimization of Joints &amp; LBS</h3>
<p>To make the model articulated, the researchers must solve for the <strong>Joint Locations <img src="https://latex.codecogs.com/png.latex?J">, Skinning Weights <img src="https://latex.codecogs.com/png.latex?W">, and Pose <img src="https://latex.codecogs.com/png.latex?B">.</strong> This is done by minimizing the reconstruction error across thousands of poses:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Carg%5Cmin_%7BW,%20J,%20B%7D%20%5Csum_%7Bi%7D%20%7C%7C%20%5Ctext%7BLBS%7D(W,%20J,%20B_i)%20-%20%5Ctext%7BScan%7D_i%20%7C%7C%5E2%0A"></p>
<p>Recent advancements like FLAME 2023 have further refined this by updating the joints to better represent the “open mouth” anatomy.</p>
</section>
</section>
<section id="implementing-flame-in-mlx" class="level2">
<h2 class="anchored" data-anchor-id="implementing-flame-in-mlx">Implementing FLAME in MLX</h2>
<p>Why MLX? As a specialized framework for Apple Silicon, MLX allows us to run these complex matrix operations directly on the GPU/NPU with unified memory. This means faster training and real-time inference on edge device with MacBook machines — the real reason is actually, I just want to maximize the utilization of my best local machine: Apple Silicon laptop 🙂</p>
<p>For those who are fluent with NumPy / JAX / PyTorch, MLX feels remarkably familiar. If you can write NumPy, you can write MLX. The API between NumPy and MLX is almost 1:1, with added power of composable function transformations like <code>mx.grad</code> and <code>mx.vmap</code>.</p>
<p>In MLX, memory is unified. One of the hurdles in PyTorch is managing <code>.to(device)</code> or <code>.cuda()</code> calls. There is no cost in MLX to move data between the CPU and GPU because they share the same physical memory, simplifying code and eliminates “device mismatch” errors.</p>
<p>Like JAX, MLS uses lazy evaluation. It builds on a computation graph and only executes it when needed (e.g., when we print a value or save a file). This allows for automatic graph optimizations without the complexity of manual JIT decorators in many cases.</p>
<p>Here is how we implement the Linear Blend Skinning forward pass with MLX:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> lbs(betas, pose, v_template, shapedirs, posedirs, J_regressor, parents, lbs_weights):</span>
<span id="cb1-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. Add shape, pose, and expression contributions</span></span>
<span id="cb1-3">    v_shaped <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> v_template <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> blend_shapes(betas, shapedirs)</span>
<span id="cb1-4">    </span>
<span id="cb1-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. Add pose-dependent blend shapes</span></span>
<span id="cb1-6">    ident <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mx.eye(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb1-7">    rot_mats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> batch_rodrigues(pose.reshape(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)).reshape(batch_size, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb1-8">    pose_feature <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (rot_mats[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> ident).reshape(batch_size, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb1-9">    pose_offsets <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mx.matmul(pose_feature, posedirs).reshape(batch_size, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb1-10">    </span>
<span id="cb1-11">    v_posed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> v_shaped <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> pose_offsets</span>
<span id="cb1-12">    </span>
<span id="cb1-13">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. Apply the global joint transformations</span></span>
<span id="cb1-14">    J_transformed, A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> batch_rigid_transform(rot_mats, J, parents)</span>
<span id="cb1-15">    </span>
<span id="cb1-16">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4. Final skinning</span></span>
<span id="cb1-17">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... matrix transforms ...</span></span>
<span id="cb1-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> verts, J_transformed</span></code></pre></div></div>
<p>Once we have the posed vertices from the <code>lbs</code> function, MLX makes it easy to implement downstream tasks. For instance, calculating 3D landmarks via barycentric interpolation, 3D joint locations from mesh vertices, and per-vertex displacements from blend shapes can be done using <code>mx.einsum</code> operation.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> vertices2landmarks(vertices, faces, lmk_faces_idx, lmk_bary_coords):</span>
<span id="cb2-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb2-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Calculates 3D landmarks by barycentric interpolation over specific mesh faces.</span></span>
<span id="cb2-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    </span></span>
<span id="cb2-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb2-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        vertices (mx.array): Batch of mesh vertices of shape (B, V, 3).</span></span>
<span id="cb2-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        faces (mx.array): Mesh face indices of shape (F, 3).</span></span>
<span id="cb2-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        lmk_faces_idx (mx.array): Indices of faces where landmarks are located (B, L).</span></span>
<span id="cb2-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        lmk_bary_coords (mx.array): Barycentric coordinates of landmarks on those faces (B, L, 3).</span></span>
<span id="cb2-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        </span></span>
<span id="cb2-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns:</span></span>
<span id="cb2-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        mx.array: Calculated 3D landmarks of shape (B, L, 3).</span></span>
<span id="cb2-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb2-14">    batch_size, num_verts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> vertices.shape[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb2-15">    </span>
<span id="cb2-16">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Extract the indices of the vertices for each face</span></span>
<span id="cb2-17">    lmk_faces <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> faces[lmk_faces_idx]</span>
<span id="cb2-18">    </span>
<span id="cb2-19">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Add batch offset to indices for flat indexing</span></span>
<span id="cb2-20">    batch_offset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mx.arange(batch_size)[:, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> num_verts</span>
<span id="cb2-21">    lmk_faces_absolute <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lmk_faces <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> batch_offset</span>
<span id="cb2-22">    </span>
<span id="cb2-23">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Gather vertices</span></span>
<span id="cb2-24">    vertices_flat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> vertices.reshape(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb2-25">    lmk_vertices <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> vertices_flat[lmk_faces_absolute] <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (B, L, 3, 3)</span></span>
<span id="cb2-26">    </span>
<span id="cb2-27">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Compute landmarks using barycentric interpolation</span></span>
<span id="cb2-28">    landmarks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mx.einsum(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'blfi,blf-&gt;bli'</span>, lmk_vertices, lmk_bary_coords)</span>
<span id="cb2-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> landmarks</span>
<span id="cb2-30"></span>
<span id="cb2-31"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> vertices2joints(J_regressor, vertices):</span>
<span id="cb2-32">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb2-33"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Calculates 3D joint locations from mesh vertices using a regressor matrix.</span></span>
<span id="cb2-34"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    </span></span>
<span id="cb2-35"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb2-36"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        J_regressor (mx.array): Joint regressor matrix of shape (J, V).</span></span>
<span id="cb2-37"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        vertices (mx.array): Batch of mesh vertices of shape (B, V, 3).</span></span>
<span id="cb2-38"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        </span></span>
<span id="cb2-39"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns:</span></span>
<span id="cb2-40"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        mx.array: Calculated 3D joint locations of shape (B, J, 3).</span></span>
<span id="cb2-41"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb2-42">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> mx.einsum(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'bik,ji-&gt;bjk'</span>, vertices, J_regressor)</span>
<span id="cb2-43"></span>
<span id="cb2-44"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> blend_shapes(betas, shape_disps):</span>
<span id="cb2-45">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb2-46"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Calculates per-vertex displacements from blend shapes (identities or expressions).</span></span>
<span id="cb2-47"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    </span></span>
<span id="cb2-48"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb2-49"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        betas (mx.array): Coefficients for the blend shapes of shape (B, K).</span></span>
<span id="cb2-50"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        shape_disps (mx.array): Blend shape bases of shape (V, 3, K).</span></span>
<span id="cb2-51"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        </span></span>
<span id="cb2-52"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns:</span></span>
<span id="cb2-53"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        mx.array: Vertex displacements of shape (B, V, 3).</span></span>
<span id="cb2-54"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb2-55">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> mx.einsum(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'bl,mkl-&gt;bmk'</span>, betas, shape_disps)</span></code></pre></div></div>
<p>Since MLX is fully differentiable, we can also easily compute gradient for fitting the model to 2D landmarks or 3D scans.</p>
</section>
<section id="high-performance-demos" class="level2">
<h2 class="anchored" data-anchor-id="high-performance-demos"><strong>High-Performance Demos</strong></h2>
<p>With this implementation, we can achieve real-time interactivity. Below is a demo of our&nbsp;<strong>Viser-based 3D Viewer</strong>&nbsp;running with FLAME parameters:</p>
<video controls="" preload="metadata" width="100%">
<source src="media/flame_demo.mp4" type="video/mp4">
<p>Your browser does not support embedded video.</p>
</video></section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>FLAME remains the gold standard for controllable 3D face modeling. Its disentangled representation of identity and expression makes it perfect bridge between classic computer graphic and modern generative AI. By porting these models to high-performance frameworks on edge device like MLX, it’s bridging the digital human faces to every edge desktop computer.</p>
<p>Check out the full implementation on GitHub: <a href="https://github.com/ghif/FLAME_mlx">https://github.com/ghif/FLAME_mlx</a>.</p>
<p>Also, thanks to <a href="https://antigravity.google/">Google’s Antigravity</a> for providing developer experience to the next level in assisting me to port the code.</p>


</section>

 ]]></description>
  <category>machine-learning</category>
  <category>computer-vision</category>
  <category>deep-learning</category>
  <category>implementation</category>
  <category>research-notes</category>
  <guid>https://ghif.github.io/posts/2026-01-30-flame-articulated-3d-face-model-mlx/</guid>
  <pubDate>Fri, 30 Jan 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Beyond the Triangle: Building a 3D Gaussian Splatting Engine from Scratch in JAX</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2026-01-13-3d-gaussian-splatting-engine-jax/</link>
  <description><![CDATA[ 




<p>For decades, the fundamental atom of computer graphics has been the <a href="https://en.wikipedia.org/wiki/Triangle_mesh">Triangle representation</a>, the simplest 2D shape that can define a flat surface requiring only 3 points (vertices) in 3D space. From the original Doom game to the latest Unreal Engine 5 demo, we build 3D digital worlds by stitching together rigid polygons. But reality isn’t made of hard edges and vertices; it is fuzzy, complex, and volumetric.</p>
<p>In 2020, Neural Radiance Fields (NeRFs) promised a solution, using AI to ‘dream’ photorealistic scenes (<a href="https://arxiv.org/pdf/2003.08934">Mildenhall et al., 2020</a>). But they came with a heavy cost: agonizingly slow rendering speeds — previously I wrote a <a href="https://app.notion.com/p/Implementing-Neural-Radiance-Fields-NeRF-with-Keras-3-213a59d373608018884dee4a00a79273?pvs=21">technical article about NeRFs as well</a>.</p>
<p>Enter 3D Gaussian Splatting (3DGS). Released in 2023, this technique threw out the neural networks and brought back an old 90s concept — point-based rasterization — supercharged with modern optimization, the same engine as in deep learning (<a href="https://arxiv.org/pdf/2308.04079">Kerbl et al.&nbsp;2023</a>). The result? Photorealism that rivals NeRFs, but renders at a blistering 100+ FPS on consumer hardware.</p>
<p>In this article, we’ll look under the hood to see how millions of fuzzy 3D ellipsoids / Gaussian ‘blobs’ instead of Triangle are rewriting the rules of 3D rendering. We’ll also explore how a full pipeline of 3DGS can be implemented with <a href="https://docs.jax.dev/en/latest/">JAX</a>, an open source framework developed by Google that is designed for high-performance numerical computing and large-scale machine learning.</p>
<section id="from-rigid-triangles-to-fuzzy-blobs" class="level3">
<h3 class="anchored" data-anchor-id="from-rigid-triangles-to-fuzzy-blobs">From Rigid Triangles to Fuzzy Blobs</h3>
<p>To understand why 3DGS is revolutionary, we must look at how it defines space.</p>
<p>In a traditional Triangle Mesh, an object is a hollow shell. To render a cat, you stretch a “skin” of texture over a wireframe skeleton. It works great for solid surfaces like walls or cars, but it fails at complex, thin, or semi-transparent structures. Have you ever noticed how video game hair often looks like stiff strips of paper? That is the limitation of the Triangle.</p>
<p>3DGS abandons the shell. Instead, it treats the world as a <strong>volumetric cloud</strong>.</p>
<p>Imagine a point cloud, but instead of tiny, single-pixel dots, every point is a 3D ellipsoid (a stretched sphere). We call these “Gaussians.” Each Gaussian is defined not just by a position, but by a set of learnable parameters that describe its existence in space.</p>
<p>If the Triangle is a piece of origami paper, the Gaussian is a soft, colored snowball.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-01-13-3d-gaussian-splatting-engine-jax/media/Screenshot_2026-01-13_at_11.39.23.png" class="img-fluid figure-img"></p>
<figcaption>Figure 1: Illustration of Multi-View Scene Reconstruction with 3D Gaussian Splatting taken from (<a href="https://arxiv.org/pdf/2506.12727">Choi et al.&nbsp;2025</a>)</figcaption>
</figure>
</div>
<p>Figure 1: Illustration of Multi-View Scene Reconstruction with 3D Gaussian Splatting taken from (<a href="https://arxiv.org/pdf/2506.12727">Choi et al.&nbsp;2025</a>)</p>
<p>3DGS can enable a new class of product features that were previously impossible due to the “quality vs speed” trade-off. Previously, if a product needed photorealism, it used pre-rendered video (non-interactive). If it needed interactivity, it used polygon meshes (often lacking photorealism for complex materials). 3DGS bridges this gap.</p>
<p>Here are the specific functionalities in real-world products that can be enabled by 3DGS:</p>
<p><strong>1. Next-Gen E-Commerce: “The Unscannable Product” Viewer</strong></p>
<p>Traditional photogrammetry (Meshes) fails drastically when scanning objects with <strong>transparency, refraction, thin structures, or high reflectivity</strong>. This limits 3D product views to matte objects like shoes or furniture.</p>
<ul>
<li><strong>The Enabled Functionality:</strong> An interactive web viewer for <strong>jewelry, perfume bottles, fluffy apparel, and complex electronics</strong>.</li>
<li><strong>Why 3DGS?</strong>
<ul>
<li><strong>Refraction/Reflection:</strong> 3DGS captures view-dependent effects (shiny diamonds sparkle as you rotate the view) because it uses Spherical Harmonics.</li>
<li><strong>Fuzzy Details:</strong> It captures the individual strands of a mohair sweater or the bristles of a toothbrush, which would otherwise be a flat texture on a blobby mesh.</li>
</ul></li>
</ul>
<p><strong>2. Real Estate &amp; Tourism: The “Cinematic” Virtual Tour</strong></p>
<p>Current virtual tours (like Matterport) often rely on 360° panoramas. You can jump from point A to point B, but you cannot “walk” smoothly between them. Mesh-based navigations often look like low-poly video games.</p>
<ul>
<li><strong>The Enabled Functionality:</strong> <strong>6-Degrees-of-Freedom (6DoF) Walkthroughs</strong> on mobile devices. Users can fly a drone path through a hotel suite or walk through a house museum with photorealistic lighting preservation.</li>
<li><strong>Why 3DGS?</strong>
<ul>
<li><strong>Lighting Baking:</strong> 3DGS bakes the complex bounce lighting of a room into the scene. Mirrors work correctly (reflecting the room), and windows show the actual view outside, rather than a flat white texture.</li>
<li><strong>Mobile Performance:</strong> Unlike NeRFs, which kill mobile batteries, optimized 3DGS (like Luma AI or Spline) can render at 60fps on iPhones.</li>
</ul></li>
</ul>
<p><strong>3. Telepresence: The Photorealistic Avatar</strong></p>
<p>Video conferencing has looked the same for 15 years (2D grid). VR avatars (like Meta’s Horizon) look cartoonish because rendering realistic skin and hair on a mesh is computationally heavy.</p>
<ul>
<li><strong>The Enabled Functionality:</strong> <strong>Holographic Telepresence</strong>. A system where you see the other person in full 3D volume, with realistic hair movement and clothing wrinkles, viewable from different angles in a VR/AR headset.</li>
<li><strong>Why 3DGS?</strong>
<ul>
<li><strong>Hair &amp; Eyes:</strong> These are the hardest parts of a human to render. 3DGS handles the thin geometry of hair and the wetness of eyes naturally.</li>
<li><strong>GaussianAvatar:</strong> Technologies like <em>GaussianAvatar</em> bind splats to a skeleton, allowing for real-time animation of a photorealistic human scan.</li>
</ul></li>
</ul>
<p><strong>4. VFX &amp; Virtual Production: Instant Background Plates</strong></p>
<p>In film production, creating a digital twin of a movie set for “Virtual Production” (LED walls) usually takes weeks of manual modeling and texturing by artists.</p>
<ul>
<li><strong>The Enabled Functionality:</strong> <strong>Rapid Set Digitization</strong>. A drone flies over a location (e.g., a forest or city street), and within 20 minutes, a Director can scout that location in VR or use it as a background plate for a car chase scene.</li>
<li><strong>Why 3DGS?</strong>
<ul>
<li><strong>Training Speed:</strong> A 3DGS scene trains in minutes, not days.</li>
<li><strong>Unstructured Environments:</strong> It excels at organic nature (trees, bushes, dirt) which are notoriously difficult and expensive to model by hand with polygons.</li>
</ul></li>
</ul>
<p><strong>5. Automotive: The Interactive Configurator</strong></p>
<p>Car configurators usually rely on heavy ray-tracing (cloud streaming) or simplified WebGL meshes (loss of quality).</p>
<ul>
<li><strong>The Enabled Functionality:</strong> <strong>Real-time Ray-Tracing Quality on Web</strong>. A user can inspect a car’s metallic paint flakes, the leather stitching, and the glass headlights in a browser without lag.</li>
<li><strong>Why 3DGS?</strong>
<ul>
<li><strong>Anisotropic Splats:</strong> The “squashed” shape of Gaussians is perfect for representing the metallic sheen of car paint and the sharp specular highlights on the bodywork.</li>
</ul></li>
</ul>
<p><strong>6. Robotics &amp; Sim: Simulation Environments</strong></p>
<p>Robots need to train in simulators. Mesh simulators are “too clean”—they don’t have the visual noise, dust, and lighting artifacts of the real world, leading to the “Sim-to-Real gap.”</p>
<ul>
<li><strong>The Enabled Functionality:</strong> <strong>Digital Twin Simulation</strong>. A robot can train navigation inside a 3DGS scan of a warehouse that is visually indistinguishable from the real warehouse.</li>
<li><strong>Why 3DGS?</strong>
<ul>
<li>It captures the “messiness” of the real world (wires hanging down, reflective puddles on the floor) that confuses robot sensors, providing better training data than hand-made CAD models.</li>
</ul></li>
</ul>
</section>
<section id="anatomy-of-a-gaussian" class="level3">
<h3 class="anchored" data-anchor-id="anatomy-of-a-gaussian">Anatomy of a Gaussian</h3>
<p>The fundamental building block of the 3DGS representation is the 3D Gaussians / Ellipsoids. Unlike isotropic points or voxels used in previous explicit methods, 3D Gaussians are anisotropic, meaning they can be stretched and scaled along independent axes. This anisotropy is crucial for modeling surface geometry efficiently: a single flat Gaussian can represent a large patch of a wall, while thin, elongated Gaussians can represent wires or hair strands.</p>
<p>Mathematically, a 3D Gaussian is defined by a mean position <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7B%5Cmu%7D"> and a covariance matrix <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7B%5CSigma%7D">. The influence of the Gaussian at any point <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bp%7D"> in 3D space is given by the standard multivariate Gaussian distribution:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bequation%7DG(%5Cmathbf%7Bp%7D)%20=%20%5Cexp(-%5Cfrac%7B1%7D%7B2%7D%20(%5Cmathbf%7Bp%7D%20-%20%5Cboldsymbol%7B%5Cmu%7D)%5E%5Ctop%20%5Cmathbf%7B%5CSigma%7D%5E%7B-1%7D%20(%5Cmathbf%7Bp%7D%20-%20%5Cboldsymbol%7B%5Cmu%7D))%5Cend%7Bequation%7D%0A"></p>
<p>However, directly optimizing the covariance matrix <img src="https://latex.codecogs.com/png.latex?%5Cboldsymbol%7B%5CSigma%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E%7B3%20%5Ctimes%203%7D"> is problematic because it must remain <strong>positive semi-definite</strong> to represent a valid physical ellipsoid. To enforce this constraint during gradient descent optimization, (Kerbl et al.&nbsp;2023) proposes decomposing <img src="https://latex.codecogs.com/png.latex?%5Cboldsymbol%7B%5CSigma%7D"> into a scaling matrix <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BS%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E%7B3%20%5Ctimes%203%7D"> and a rotation matrix <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BR%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E%7B3%20%5Ctimes%203%7D">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bequation%7D%20%5Cboldsymbol%7B%5CSigma%7D%20=%20%5Cmathbf%7BR%7D%20%5Cmathbf%7BS%7D%20%5Cmathbf%7BS%7D%5E%5Ctop%20%5Cmathbf%7BR%7D%5E%5Ctop%20%5Cend%7Bequation%7D%0A"></p>
<p>Here, <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BS%7D"> is a diagonal matrix representing the scaling factors along the three axes, and <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BR%7D"> is a rotation matrix constructed from a unit quaternion <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bq%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E4">. This decomposition allows the optimizer to adjust the size and orientation of the splats independently, enabling the representation to “flatten” along surfaces and align with geometric structures effectively.</p>
<p>Quaternions are critical for efficient optimization. Unlike <img src="https://latex.codecogs.com/png.latex?3%20%5Ctimes%203"> rotation matrices, which break and shear when modified by gradient descent, quaternions maintain valid rotations without requiring expensive correction algorithms like Gram-Schmidt.</p>
<p>So, the optimization utilizes quaternions representing the blob pose. To convert it into its corresponding <img src="https://latex.codecogs.com/png.latex?3%20%5Ctimes%203"> rotation matrix, the following rule can be applied: for a unit quaternion <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bq%7D%20=%20w%20+%20xi%20+%20yj%20+%20zk">, where <img src="https://latex.codecogs.com/png.latex?w"> is the scalar (real) part and <img src="https://latex.codecogs.com/png.latex?x,%20y,%20z"> are the vector (imaginary) parts, the corresponding <img src="https://latex.codecogs.com/png.latex?3%20%5Ctimes%203"> rotation matrix is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7BR%7D%20=%20%5Cbegin%7Bbmatrix%7D%0A1%20-%202(y%5E2%20+%20z%5E2)%20&amp;%202(xy%20-%20wz)%20&amp;%202(xz%20+%20wy)%20%5C%5C%0A2(xy%20+%20wz)%20&amp;%201%20-%202(x%5E2%20+%20z%5E2)%20&amp;%202(yz%20-%20wx)%20%5C%5C%0A2(xz%20-%20wy)%20&amp;%202(yz%20+%20wx)%20&amp;%201%20-%202(x%5E2%20+%20y%5E2)%0A%5Cend%7Bbmatrix%7D%0A"></p>
<p>Technically, 3D Gaussian blobs can be generated randomly by initializing the mean (a single point coordinate) and covariances. In standard practice, however, 3D Gaussian blobs are almost never generated randomly for complex real-world scenes. Instead, the pipeline relies heavily on <strong>Structure-from-Motion (SfM)</strong> algorithms, most notably COLMAP (<a href="https://openaccess.thecvf.com/content_cvpr_2016/papers/Schonberger_Structure-From-Motion_Revisited_CVPR_2016_paper.pdf">Schönberger et al.&nbsp;2016</a>). Before training begins, COLMAP analyzes the input images to calculate camera poses and generates a sparse point cloud of the scene. Each valid point in this cloud serves as the initial “seed” for a Gaussian blob — the point’s location becomes the Gaussian’s mean <img src="https://latex.codecogs.com/png.latex?%5Cmu">, while its scale and opacity are initialized to generic low values. This provides the optimizer with a rough geometric scaffold, preventing it from getting stuck in local minima which frequently happens if training starts from random noise.</p>
<p>The following is the JAX code snippet implementing the 3D Gaussian blob data structure.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> jax.numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> jnp</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> chex</span>
<span id="cb1-3"></span>
<span id="cb1-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@chex.dataclass</span></span>
<span id="cb1-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Gaussians:</span>
<span id="cb1-6">    means: jnp.ndarray  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (N, 3)</span></span>
<span id="cb1-7">    scales: jnp.ndarray  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (N, 3)</span></span>
<span id="cb1-8">    quaternions: jnp.ndarray  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (N, 4)</span></span>
<span id="cb1-9">    opacities: jnp.ndarray  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (N, 1)</span></span>
<span id="cb1-10">    sh_coeffs: jnp.ndarray  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (N, K, 3) where K is num SH coefficients</span></span>
<span id="cb1-11"></span>
<span id="cb1-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> init_gaussians_from_pcd(points: jnp.ndarray, colors: jnp.ndarray):</span>
<span id="cb1-13">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Initialize Gaussians from a point cloud.</span></span>
<span id="cb1-15"></span>
<span id="cb1-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb1-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        points: (N, 3)</span></span>
<span id="cb1-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        colors: (N, 3) in [0, 1]</span></span>
<span id="cb1-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns:</span></span>
<span id="cb1-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        gaussians: Gaussians dataclass</span></span>
<span id="cb1-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb1-22">    num_points <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> points.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb1-23">    </span>
<span id="cb1-24">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Position: mean of the point cloud</span></span>
<span id="cb1-25">    means <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> points</span>
<span id="cb1-26">    </span>
<span id="cb1-27">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Scales: log of the distance to the nearest neighbors</span></span>
<span id="cb1-28">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Initialized to a small value (approx 0.05m)</span></span>
<span id="cb1-29">    scales <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.full((num_points, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>), <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>) </span>
<span id="cb1-30">    </span>
<span id="cb1-31">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Rotations: identity quaternions [1, 0, 0, 0]</span></span>
<span id="cb1-32">    quaternions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.tile(jnp.array([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>]), (num_points, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb1-33">    </span>
<span id="cb1-34">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Opacities: inverse sigmoid of 0.5 = 0.0</span></span>
<span id="cb1-35">    opacities <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.full((num_points, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>) </span>
<span id="cb1-36">    </span>
<span id="cb1-37">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># SH Coefficients (DC term only)</span></span>
<span id="cb1-38">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># SH_DC = (R - 0.5) / 0.28209</span></span>
<span id="cb1-39">    sh_dc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (colors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.28209479177387814</span></span>
<span id="cb1-40">    sh_coeffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.zeros((num_points, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Degree 3 SH -&gt; 16 coefficients</span></span>
<span id="cb1-41">    sh_coeffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sh_coeffs.at[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, :].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(sh_dc)</span>
<span id="cb1-42">    </span>
<span id="cb1-43">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Gaussians(</span>
<span id="cb1-44">        means<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>means,</span>
<span id="cb1-45">        scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>scales,</span>
<span id="cb1-46">        quaternions<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>quaternions,</span>
<span id="cb1-47">        opacities<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>opacities,</span>
<span id="cb1-48">        sh_coeffs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>sh_coeffs</span>
<span id="cb1-49">    )</span></code></pre></div></div>
<p>Here we use the <code>@chex.dataclass</code> decorator for the structure due to several reasons:</p>
<ul>
<li><strong>JIT compatibility (Pytree registration)</strong>: In JAX, functions are often transformed using <code>jax.jit</code>, <code>jax.vmap</code>, or <code>jax.grad</code>. These transformations require that any data passed into them be a “Pytree” (a container of JAX arrays). A standard Python <code>dataclass</code> is not a Pytree by default. If we try to pass it to a JIT-compiled function, JAX won’t know hot to “look inside” it to find the arrays. <code>@check.dataclass</code> automatically registers the class as a JAX Pytree. This allows us to pass the Gaussians object directly into JIT-compiled functions as if it were a simple tuple or dictionary.</li>
<li><strong>Immutability</strong>: <code>@check.dataclass</code> are immutable by default (similar to <code>frozen=True</code> in standard dataclasses). JAX’s functional programming model relies on pure functions and immutable data.</li>
<li><strong>Ease of optimization</strong>: Because it’s a Pytree, we can use JAX’s gradient transformations directly on the object. For example:</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> loss_fn(gaussians):</span>
<span id="cb2-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># compute loss ...</span></span>
<span id="cb2-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> loss</span>
<span id="cb2-4"></span>
<span id="cb2-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This works because Gaussians is a chex.dataclass (Pytree). The resulting grads grads will have the same structure as the Gaussians class, containing gradients for means, scales. etc.</span></span>
<span id="cb2-6">grads <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.grad(loss_fn)(gaussians)</span></code></pre></div></div>
<p>Note that there are two attributes that have not been introduced before: <code>opacities</code> and <code>sh_coeefs</code>. Those are needed from the rendering through rasterization that will be explained in the next section.</p>
<p>Below is the function to compute the covariance matrix <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7B%5CSigma%7D"> from the quaternion <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bq%7D"> of <img src="https://latex.codecogs.com/png.latex?N"> Gaussian blobs. Here we employ JAX vectorizing map (<code>jax.vmap</code>) to allow more efficient batched computation.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_covariance_3d(scales: jnp.ndarray, quaternions: jnp.ndarray):</span>
<span id="cb3-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Computes 3D covariance matrix from scales and quaternions.</span></span>
<span id="cb3-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Σ = R S S^T R^T</span></span>
<span id="cb3-5"></span>
<span id="cb3-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb3-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        scales: (N, 3)</span></span>
<span id="cb3-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        quaternions: (N, 4)</span></span>
<span id="cb3-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns:</span></span>
<span id="cb3-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        covariance: (N, 3, 3)</span></span>
<span id="cb3-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb3-12">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Normalize quaternions</span></span>
<span id="cb3-13">    q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> quaternions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> jnp.linalg.norm(quaternions, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, keepdims<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb3-14">    </span>
<span id="cb3-15">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Rotation matrix from quaternion</span></span>
<span id="cb3-16">    r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb3-17">    x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb3-18">    y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb3-19">    z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>]</span>
<span id="cb3-20">    </span>
<span id="cb3-21">    R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.stack([</span>
<span id="cb3-22">        jnp.stack([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>z<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>z, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>y], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>),</span>
<span id="cb3-23">        jnp.stack([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>z, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>z<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>),</span>
<span id="cb3-24">        jnp.stack([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>y, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb3-25">    ], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb3-26">    </span>
<span id="cb3-27">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Scaling matrix</span></span>
<span id="cb3-28">    s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.exp(scales)</span>
<span id="cb3-29">    S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.vmap(<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> x: jnp.diag(x))(s) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># "Vectorize" operation. Since s has shape (N, 3), vmap applies the diag function to each of the N rows independently so that the operation becomes much more efficien</span></span>
<span id="cb3-30">    </span>
<span id="cb3-31">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># M = R S</span></span>
<span id="cb3-32">    M <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> S</span>
<span id="cb3-33">    </span>
<span id="cb3-34">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Σ = M M^T</span></span>
<span id="cb3-35">    Sigma <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> M <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> M.transpose(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb3-36">    </span>
<span id="cb3-37">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Sigma</span></code></pre></div></div>
<p>The figures below illustrate the initialization of Gaussian blobs: random generation vs extraction from SfM.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-01-13-3d-gaussian-splatting-engine-jax/media/random_splats.gif" class="img-fluid figure-img"></p>
<figcaption>Figure 2: 3D Gaussian splats/blobs generated randomly</figcaption>
</figure>
</div>
<p>Figure 2: 3D Gaussian splats/blobs generated randomly</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-01-13-3d-gaussian-splatting-engine-jax/media/progress_0000.png" class="img-fluid figure-img"></p>
<figcaption>Figure 3: 3D Gaussian splats induced by point clouds extracted from Structure-from-Motion (COLMAP)</figcaption>
</figure>
</div>
<p>Figure 3: 3D Gaussian splats induced by point clouds extracted from Structure-from-Motion (COLMAP)</p>
</section>
<section id="rendering-through-rasterization-from-3d-cloud-to-2d-image" class="level3">
<h3 class="anchored" data-anchor-id="rendering-through-rasterization-from-3d-cloud-to-2d-image">Rendering through Rasterization: From 3D Cloud to 2D Image</h3>
<p>A defining characteristic of photorealistic rendering is the ability to capture non-Lambertian effects—how the appearance of a surface changes based on the viewing angle (e.g., specular highlights on metal, the sheen of silk). Now that we have a scene populated with millions of initialized 3D Gaussians, we need to render them.</p>
<p>This is where 3DGS diverges sharply from NeRFs. Instead of shooting rays backwards from the camera into the scene (ray marching), 3DGS utilizes <strong>Forward Rasterization</strong>. It takes the existing 3D data and “pushes” it onto the 2D screen, similar to how standard game engines render triangles. 3DGS moves away from the neural decoding of color used in NeRF. Instead, each Gaussian stores a set of coefficients for Spherical Harmonics.</p>
<p>SHs serve as a basis function for representing functions on the surface of a sphere. By storing higher-order SH coefficients (typically up to degree 3, resulting in 16 coefficients per color channel), 3DGS can approximate complex view-dependent lighting effects directly. During rendering, the viewing direction vector is used to evaluate the SH functions, producing a specific RGB color for that angle. This explicit storage contributes to the high memory footprint of 3DGS but eliminates the need for expensive MLP inference during the render pass.</p>
<p>Here is the step-by-step mathematical flow.</p>
<ol type="1">
<li><strong>The 2D Projection / Viewing Transformation (World —&gt; Camera)</strong></li>
</ol>
<p>First, we transform the geometry relative to the camera. If the camera moves forward, the Gaussians effectively move backward.</p>
<p>For the position mean <img src="https://latex.codecogs.com/png.latex?%5Cmu">, this is a standard matrix multiplication using the camera view matrix <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BW%7D"> (pose):</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cboldsymbol%7B%5Cmu%7D_%7B%5Cmathrm%7Bcam%7D%7D%20=%20%5Cmathbf%7BW%7D%5E%5Ctop%20%5Cboldsymbol%7B%5Cmu%7D%0A"></p>
<p>However, transforming the covariance matrix <img src="https://latex.codecogs.com/png.latex?%5Cboldsymbol%7B%5CSigma%7D"> — the shape of the blob — is trickier. Because perspective projection is non-linear (objects shrink as the get further away), we cannot simply rotate the covariance.</p>
<p>To solve this, we use a linear approximation: <strong>Jacobian of the affine projection</strong> (<img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BJ%7D">). This matrix encodes how 3D space compresses into 2D space based on the camera’s focal length <img src="https://latex.codecogs.com/png.latex?f_x,%20f_y"> and the point’s depth <img src="https://latex.codecogs.com/png.latex?z">.</p>
<p><strong>The projection equation:</strong> we compute the new 2D covariance <img src="https://latex.codecogs.com/png.latex?%5Cboldsymbol%7B%5CSigma%7D_%7B%5Cmathrm%7B2D%7D%7D"> by “sandwiching” the 3D covariance between the transformation matrices:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7B%5CSigma%7D_%7B%5Cmathrm%7B2D%7D%7D%20=%20%5Cmathbf%7BJ%7D%20%5Cmathbf%7BW%7D%20%5Cmathbf%7B%5CSigma%7D%20%5Cmathbf%7BW%7D%5E%5Ctop%20%5Cmathbf%7BJ%7D%5E%5Ctop%0A"></p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7B%5CSigma%7D">: The original <img src="https://latex.codecogs.com/png.latex?3%20%5Ctimes%203"> shape of the blob</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BW%7D">: The <img src="https://latex.codecogs.com/png.latex?3%20%5Ctimes%203"> rotation matrix of the camera</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BJ%7D">: The <img src="https://latex.codecogs.com/png.latex?2%20%5Ctimes%203"> Jacobian approximation</li>
</ul>
<p>The result, <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7B%5CSigma%7D_%7B%5Cmathrm%7B2D%7D%7D">, is a <img src="https://latex.codecogs.com/png.latex?2%20%5Ctimes%202"> covariance matrix that describes a flat 2D Ellipse on your screen. If the calculated size of this 2D splat is smaller than a single pixel, we usually discard it to save performance.</p>
<p>The code snippet below implements the 2D projection of 3D Gaussian blobs given camera parameters using JAX:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> project_gaussians(gaussians: Gaussians, camera: Camera):</span>
<span id="cb4-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb4-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Project 3D Gaussians to 2D splats.</span></span>
<span id="cb4-4"></span>
<span id="cb4-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb4-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        gaussians: Gaussians dataclass</span></span>
<span id="cb4-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        camera: Camera dataclass </span></span>
<span id="cb4-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns:</span></span>
<span id="cb4-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        means2D: 2D means of the projected splats</span></span>
<span id="cb4-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        cov2D: 2D covariance of the projected splats</span></span>
<span id="cb4-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        radii: Radii of the projected splats</span></span>
<span id="cb4-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        valid_mask: Valid mask for the projected splats</span></span>
<span id="cb4-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        z: Depth of the projected splats</span></span>
<span id="cb4-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb4-15">    means3D <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gaussians.means</span>
<span id="cb4-16">    scales <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gaussians.scales</span>
<span id="cb4-17">    quats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gaussians.quaternions</span>
<span id="cb4-18">    </span>
<span id="cb4-19">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. Transform means</span></span>
<span id="cb4-20">    means3D_homo <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.concatenate([means3D, jnp.ones((means3D.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb4-21">    means_cam <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (means3D_homo <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> camera.W2C.T)[:, :<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>]</span>
<span id="cb4-22">    </span>
<span id="cb4-23">    x, y, z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> means_cam[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], means_cam[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], means_cam[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb4-24">    </span>
<span id="cb4-25">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. Filter </span></span>
<span id="cb4-26">    valid_mask <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span></span>
<span id="cb4-27">    </span>
<span id="cb4-28">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. Covariance</span></span>
<span id="cb4-29">    cov3D <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_covariance_3d(scales, quats)</span>
<span id="cb4-30">    </span>
<span id="cb4-31">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4. Project to 2D</span></span>
<span id="cb4-32">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Jacobian of the perspective transformation</span></span>
<span id="cb4-33">    J <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.zeros((means3D.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>))</span>
<span id="cb4-34">    J <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> J.at[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(camera.fx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> z)</span>
<span id="cb4-35">    J <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> J.at[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>camera.fx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (z<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb4-36">    J <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> J.at[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(camera.fy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> z)</span>
<span id="cb4-37">    J <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> J.at[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>camera.fy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (z<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb4-38">    </span>
<span id="cb4-39">    W_rot <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> camera.W2C[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, :<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>]</span>
<span id="cb4-40">    </span>
<span id="cb4-41">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> project_single_cov(c3d, j_mat):</span>
<span id="cb4-42">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> j_mat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W_rot <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> c3d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> W_rot.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> j_mat.T</span>
<span id="cb4-43">    </span>
<span id="cb4-44">    cov2D <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.vmap(project_single_cov)(cov3D, J)</span>
<span id="cb4-45">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Add a small bias for numerical stability (low pass filter)</span></span>
<span id="cb4-46">    cov2D <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cov2D.at[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].add(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span>)</span>
<span id="cb4-47">    cov2D <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cov2D.at[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].add(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span>)</span>
<span id="cb4-48">    </span>
<span id="cb4-49">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 5. Means 2D</span></span>
<span id="cb4-50">    means2D <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.stack([</span>
<span id="cb4-51">        camera.fx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> camera.cx,</span>
<span id="cb4-52">        camera.fy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> camera.cy</span>
<span id="cb4-53">    ], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb4-54">    </span>
<span id="cb4-55">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 6. Radii for tile interaction</span></span>
<span id="cb4-56">    det <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cov2D[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> cov2D[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> cov2D[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb4-57">    trace <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cov2D[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> cov2D[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb4-58">    mid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> trace <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span></span>
<span id="cb4-59">    term <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.sqrt(jnp.maximum(mid<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> det, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>))</span>
<span id="cb4-60">    lambda1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> term</span>
<span id="cb4-61">    max_eigen <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lambda1 </span>
<span id="cb4-62">    radii <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.ceil(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> jnp.sqrt(max_eigen))</span>
<span id="cb4-63">    </span>
<span id="cb4-64">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> means2D, cov2D, radii, valid_mask, z</span></code></pre></div></div>
<ol type="1">
<li><strong>Sorting (The Depth Problem)</strong></li>
</ol>
<p>Since we are blending transparent blobs, order matters, i.e., Red behind Blue <img src="https://latex.codecogs.com/png.latex?%5Cneq"> Blue behind Red. Once all Gaussians are projected to 2D, the engine performs a Global Sort based on their depth (<img src="https://latex.codecogs.com/png.latex?z"> -value) relative to the camera. In the official CUDA implementation, this uses a highly optimized GPU Radix Sort.</p>
<ul>
<li><strong>Input</strong>: Unsorted list of 2D splats</li>
<li><strong>Output</strong>: List of indices sorted from closest to furthest.</li>
</ul>
<ol type="1">
<li><strong>Tile-based Rasterization</strong></li>
</ol>
<p>A 4K screen has 8 million pixels. A scene might have 4 million Gaussians. A naive loop (for every pixel —&gt; check every Gaussian) would be 8M <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> 4M operations. That is impossible.</p>
<p>3DGS uses a tiled approach, similar to modern GPU rasterizers:</p>
<ul>
<li>The screen is divided into <img src="https://latex.codecogs.com/png.latex?16%20%5Ctimes%2016"> pixel tiles.</li>
<li>Identify which tiles a Gaussian overlaps with.</li>
<li>Each tile creates a generic “list” of Gaussians that touch it.</li>
</ul>
<p>Now, a pixel only needs to check the Gaussians in its specific tile list, reducing complexity drastically.</p>
<ol type="1">
<li><strong>Alpha Blending (The Compositing Equation)</strong></li>
</ol>
<p>This is the final step of the rendering: color calculation. For a single pixel, we iterate through the sorted Gaussians in its tile.</p>
<p>The color <img src="https://latex.codecogs.com/png.latex?C"> of a pixel is computed using standard <strong>front-to-back composition</strong>:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bequation%7D%20C=%5Csum_%7Bi%20%5Cin%20N%7D%20%5Cmathbf%7Bc%7D_i%20%5Calpha_i%20%5Cprod_%7Bj=1%7D%5E%7Bi-1%7D%20(1%20-%20%5Calpha_j)%20%5Cend%7Bequation%7D%0A"></p>
<p>Let’s break down the terms:</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?c_i">: The <strong>color</strong> of the <img src="https://latex.codecogs.com/png.latex?i"> -th Gaussian (from Spherical Harmonics)</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Calpha_i">: The <strong>effective opacity</strong>. This combines the learned opacity <img src="https://latex.codecogs.com/png.latex?%5Csigma"> with the Gaussian falloff distance <img src="https://latex.codecogs.com/png.latex?G"> from the center of the blob:</li>
</ul>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Calpha_i%20=%20%5Csigma_i%20%5Ccdot%20G(%5Cmathbf%7Bp%7D)%0A"></p>
<p>(<em>Pixels at the center of the blob are opaque; pixels at the edge are transparent)</em></p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?%5Cprod(1-%5Calpha_i)">: The <strong>transmittance</strong><img src="https://latex.codecogs.com/png.latex?T">. This represents “how much light has not been blocked yet.”</li>
</ul>
<p>The <strong>optimization (early stopping)</strong>: Because it renders front-to-back, we can tract the transmittance <img src="https://latex.codecogs.com/png.latex?T">:</p>
<ul>
<li>Start with <img src="https://latex.codecogs.com/png.latex?T=1.0"> (100% light passes).</li>
<li>After Gaussian A (<img src="https://latex.codecogs.com/png.latex?%5Calpha=0.5">), <img src="https://latex.codecogs.com/png.latex?T"> drops to <img src="https://latex.codecogs.com/png.latex?0.5">.</li>
<li>After Gaussian B (<img src="https://latex.codecogs.com/png.latex?%5Calpha=0.5">), <img src="https://latex.codecogs.com/png.latex?T"> drops to <img src="https://latex.codecogs.com/png.latex?0.25"> - <strong>Stop condition</strong>: if <img src="https://latex.codecogs.com/png.latex?T%3C%200.0001">, the pixel is fully saturated (opaque). We stop processing the list. This ignores objects hidden behind a wall, saving massive computation.</li>
</ul>
</section>
<section id="training-loop-how-to-optimize-the-blob-cloud" class="level3">
<h3 class="anchored" data-anchor-id="training-loop-how-to-optimize-the-blob-cloud">Training Loop: How to Optimize the Blob Cloud</h3>
<p>Unlike a neural network, which optimizes “weights” inside a black box, 3DGS optimizes the scene geometry directly. The training process is a continuous cycle of rendering the scene, comparing it to a real photo, and nudging the blobs to match.</p>
<p><strong>Learnable Parameters</strong></p>
<p>Recall equation (1) where a Gaussian is expressed with <img src="https://latex.codecogs.com/png.latex?G."> In a 3DGS scene, we have a set of <img src="https://latex.codecogs.com/png.latex?N"> Gaussians, where <img src="https://latex.codecogs.com/png.latex?N"> starts around 100k and grows to millions. Each Gaussian <img src="https://latex.codecogs.com/png.latex?G_i"> is parameterized by a tuple of learnable variables <img src="https://latex.codecogs.com/png.latex?%5CTheta_i">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5CTheta_i%20=%20%5C%7B%20%5Cmathbf%7B%5Cmu%7D_i,%20%5Cmathbf%7Bs%7D_i,%20%5Cmathbf%7Bq%7D_i,%20%5Csigma_i,%20%5Cmathbf%7Bc%7D_i%20%5C%7D%0A"></p>
<p>where each represents:</p>
<ul>
<li>Position <img src="https://latex.codecogs.com/png.latex?(%5Cmathbf%7B%5Cmu%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E3)">: World coordinates <img src="https://latex.codecogs.com/png.latex?(x,%20y%20,%20z)"> with learning rate scaled by scene extent.</li>
<li>Scale <img src="https://latex.codecogs.com/png.latex?(%5Cmathbf%7Bs%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E3)">: Log-scale vector stored as <img src="https://latex.codecogs.com/png.latex?%5Cln(s)"> to enforce <img src="https://latex.codecogs.com/png.latex?s%20%3E%200">.</li>
<li>Rotation <img src="https://latex.codecogs.com/png.latex?(%5Cmathbf%7Bq%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E4)">: Unit quaternion normalized every step to ensure valid rotation.</li>
<li>Opacity <img src="https://latex.codecogs.com/png.latex?(%5Csigma%20%5Cin%20%5Cmathbb%7BR%7D)">: Scalar probability stored as logit passed through Sigmoid.</li>
<li>Color <img src="https://latex.codecogs.com/png.latex?(%5Cmathbf%7Bc%7D%20%5Cin%20%5Cmathbb%7BR%7D%5Ek)">: Spherical harmonics, usually degree 3 (<img src="https://latex.codecogs.com/png.latex?16%20%5Ctimes%203"> coeffs).</li>
</ul>
<p>As discussed earlier, we do not optimize the covariance matrix <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7B%5CSigma%7D"> directly, because it must remain <strong>positive semi-definite</strong>. Instead, we optimize the scale and rotation, and construct the covariance matrix on the fly using equation (2).</p>
<p><strong>Forward Pass (Differentiable Rasterization)</strong></p>
<p>The forward pass process is basically the differentiable pipeline sequence of rendering through rasterization described in the previous section, from 2D projection to alpha blending. The final output of this pipeline produces a rendered RGB image <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BI%7D_%7B%5Cmathrm%7Brender%7D%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E%7BH%20%5Ctimes%20W%20%5Ctimes%203%7D">.</p>
<p>For each pixel location <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bp%7D"> in <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BI%7D_%5Cmathrm%7Brender%7D">, the value is rendered by the compositing equation (3). We can rewrite the equation by introducing the pixel location and learnable parameters:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bequation%7D%20%5Cmathbf%7BI%7D_%7B%5Cmathrm%7Brender%7D%7D(%5Cmathbf%7Bp%7D;%20%5CTheta)%20=%20%20%5Csum_%7Bi%20%5Cin%20N%7D%20%5Cmathbf%7Bc%7D_i%20%5Calpha_i%20%5Cprod_%7Bj=1%7D%5E%7Bi-1%7D%20(1%20-%20%5Calpha_i)%20%5Cend%7Bequation%7D%0A"></p>
<p>where</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Calpha_i%20=%20%5Csigma_i%20%5Ccdot%20G(%5Cmathbf%7Bp%7D;%20%5C%7B%20%5Cmathbf%7Bu%7D_i,%20%5Cmathbf%7Bs%7D_i,%20%5Cmathbf%7Bq%7D_i%20%5C%7D)%0A"></p>
<p>Denote by <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BI%7D_%5Cmathrm%7Brender%7D(%5CTheta)"> the entire <img src="https://latex.codecogs.com/png.latex?H%20%5Ctimes%20W%20%5Ctimes%203"> image induced the Gaussian’s learnable parameters <img src="https://latex.codecogs.com/png.latex?%5CTheta%20=%20%5C%7B%20%5CTheta_i%20%5C%7D_%7Bi=1%7D%5EN">.</p>
<p><strong>Gradient-based Optimization</strong></p>
<p>The rendered image <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BI%7D_%5Cmathrm%7Brender%7D(%5CTheta)"> is then compared against the ground truth training image <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BI%7D_%5Cmathrm%7Bgt%7D">. 3DGS utilizes a loss function in the form of a combination of L1 distance (for raw pixel accuracy) and D-SSIM (Structural Similarity Index, for perceptual structure).</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bequation%7D%20%5Cmathcal%7BL%7D(%5CTheta)%20=%20(1%20-%20%5Clambda)%20L_1(%5Cmathbf%7BI%7D_%5Cmathrm%7Brender%7D(%5CTheta),%20%5Cmathbf%7BI%7D_%5Cmathrm%7Bgt%7D)%20+%20%5Clambda%20L_%7B%5Cmathrm%7BD-SSIM%7D%7D%20(%5Cmathbf%7BI%7D_%5Cmathrm%7Brender%7D(%5CTheta),%20%5Cmathbf%7BI%7D_%5Cmathrm%7Bgt%7D)%20%5Cend%7Bequation%7D%0A"></p>
<p>Standard implementation uses <img src="https://latex.codecogs.com/png.latex?%5Clambda%20=%200.2">.</p>
<p>The objective of 3DGS is therefore minimizing the loss function in equation (5) with respect to the gradient <img src="https://latex.codecogs.com/png.latex?%5Cnabla_%5CTheta%20%5Cmathcal%7BL%7D(%5CTheta)">, which guides the rendering quality closer to the ground truth. This tells every Gaussian blob exactly how to move, stretch, or change color to minimize the error. The gradient computation can be easily implemented through JAX autograd.</p>
<p>Here is the JAX code snippet in implementing the gradient-based training step, using <a href="https://github.com/google-deepmind/optax">Optax</a> library for the optimization helper.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> jax</span>
<span id="cb5-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> optax</span>
<span id="cb5-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> functools <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> partial</span>
<span id="cb5-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> jax_gs.renderer.renderer <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> render</span>
<span id="cb5-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> jax_gs.training.losses <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> l1_loss</span>
<span id="cb5-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> jax_gs.core.camera <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Camera</span>
<span id="cb5-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> jax.numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> jnp</span>
<span id="cb5-8"></span>
<span id="cb5-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@partial</span>(jax.jit, static_argnums<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))</span>
<span id="cb5-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> train_step(state, target_image, w2c, camera_static, optimizer):</span>
<span id="cb5-11">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb5-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Standard training step.</span></span>
<span id="cb5-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb5-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        state: (params, opt_state)</span></span>
<span id="cb5-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        target_image: (H, W, 3)</span></span>
<span id="cb5-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        w2c: (4, 4)</span></span>
<span id="cb5-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        camera_static: (W, H, fx, fy, cx, cy)</span></span>
<span id="cb5-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        optimizer: optax optimizer</span></span>
<span id="cb5-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns:</span></span>
<span id="cb5-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        (next_params, next_opt_state), loss </span></span>
<span id="cb5-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb5-22">    params, opt_state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state</span>
<span id="cb5-23">    W, H, fx, fy, cx, cy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> camera_static</span>
<span id="cb5-24">    </span>
<span id="cb5-25">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Reconstruct Camera object inside JIT    </span></span>
<span id="cb5-26">    camera <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Camera(W<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>W, H<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>H, fx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>fx, fy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>fy, cx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>cx, cy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>cy, W2C<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>w2c, full_proj<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>jnp.eye(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))</span>
<span id="cb5-27">    </span>
<span id="cb5-28">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> loss_fn(p):</span>
<span id="cb5-29">        image <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> render(p, camera)</span>
<span id="cb5-30">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> l1_loss(image, target_image)</span>
<span id="cb5-31">    </span>
<span id="cb5-32">    loss, grads <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.value_and_grad(loss_fn)(params)</span>
<span id="cb5-33">    updates, next_opt_state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> optimizer.update(grads, opt_state, params)</span>
<span id="cb5-34">    next_params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> optax.apply_updates(params, updates)</span>
<span id="cb5-35">    </span>
<span id="cb5-36">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (next_params, next_opt_state), loss</span></code></pre></div></div>
<p><strong>Adaptive Density Control</strong></p>
<p>Standard Gradient Descent changes the <em>values</em> of parameters, but it cannot change the <em>number</em> of parameters. If a scene needs more geometry (e.g., a complex hair strand), moving existing blobs isn’t enough.</p>
<p>Every 100 iterations, 3DGS performs <strong>Density Control</strong> based on the gradients of the positions.</p>
<p><strong>A. Densification (Growth)</strong>: Look for Gaussians with high positional gradients (blobs that are “struggling” to fit the data).</p>
<ol type="1">
<li>Clone: if the Gaussian is small, we duplicate it — fills empty holes.</li>
<li>Split: if the Gaussian is large, we split it into 2 smaller Gaussians and reduce their scale by factor 1.6 — refines blurry details.</li>
</ol>
<p><strong>B. Pruning (Cleanup)</strong>: Remove Gaussians that are redundant or artifacts:</p>
<ol type="1">
<li>Low Opacity: if <img src="https://latex.codecogs.com/png.latex?%5Csigma%20%3C%20%5Cepsilon"> (transparent), delete it.</li>
<li>Screen-Space Size: if a Gaussian is huge (covering the whole screen), delete it.</li>
</ol>
<p>This dynamic growing and pruning allows the optimization to start with a sparse point cloud and organically evolve into a dense, detailed representation.</p>
</section>
<section id="minimal-implementation-of-3dgs-with-jax" class="level3">
<h3 class="anchored" data-anchor-id="minimal-implementation-of-3dgs-with-jax">Minimal Implementation of 3DGS with JAX</h3>
<p>We have covered the theory: the Gaussian structure, spherical harmonics, and differentiable rasterization pipeline. Now, it is time to see how these equations translate into actual, executable code.</p>
<p>I have prepared a minimal, educational implementation of this engine using JAX, available on Github: <a href="https://github.com/ghif/jax-gs">https://github.com/ghif/jax-gs</a>. I use agentic AI workflow through Google’s <a href="https://antigravity.google/">Antigravity IDE</a> for helping me writing and structuring the codebase.</p>
<p>This repository is designed to be a “mathematical playground.” It strips away the complex C++/CUDA kernels found in the original implementation, replacing them with pure Python and JAX. This makes the codebase significantly shorter and easier to debug.</p>
<p>The beauty of building with JAX is the hardware agnosticims provided by the XLA (Accelerated Linear Algebra) compiler. We don’t need to rewrite the rasterizer to switch between hardware backends.</p>
<p>Here’s an example of visualizing the training progress using the codebase on a <a href="https://www.kaggle.com/datasets/arenagrenade/llff-dataset-full">Fern scene from LLFF dataset</a>.</p>
<video controls="" preload="metadata" width="100%">
<source src="media/progress_video.mp4" type="video/mp4">
<p>Your browser does not support embedded video.</p>
<p>The trained 3D point clouds can be stored in a standard 3D file format (e.g., PLY) and visualized through a viewer. Here’s the visualization through the <a href="https://viser.studio/main/">Viser</a> library.</p>
<video controls="" preload="metadata" width="100%">
<source src="media/viser_animation.mp4" type="video/mp4">
<p>Your browser does not support embedded video.</p>
<p>To quantify the time complexity, I also created a simple training benchmark implemented in <code>tests/test_benchmark_training.py</code>. It ran the training loop of 10 iterations on the Fern dataset (~10k Gaussians, 504x378 resolution). Here are the benchmark results comparing comparing the training computation in Apple M4 CPU vs a single <a href="https://www.nvidia.com/en-us/data-center/l4/">NVIDIA L4 GPU</a> accessed through GCP <a href="https://docs.cloud.google.com/vertex-ai/docs/workbench/introduction">Vertex AI Workbench</a>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">**#</span> CPU <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Apple</span> M4<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">**</span></span>
<span id="cb6-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">tests/test_benchmark_training.py</span></span>
<span id="cb6-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loading</span> Fern dataset for training benchmark from data/nerf_example_data/nerf_llff_data/fern...</span>
<span id="cb6-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Benchmarking</span> training step with 10091 Gaussians at 504x378 resolution...</span>
<span id="cb6-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Warming</span> up <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">JIT</span> compilation of train_step<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">...</span></span>
<span id="cb6-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Warm-up/Compilation</span> took 0.8526s</span>
<span id="cb6-7"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Running</span> benchmark <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">10</span> training iterations<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">...</span></span>
<span id="cb6-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 1: 0.8092s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4832<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-9"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 2: 0.3521s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4755<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-10"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 3: 0.3724s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4723<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-11"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 4: 0.3697s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4645<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-12"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 5: 0.3752s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4633<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-13"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 6: 0.3748s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4597<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-14"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 7: 0.3683s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4649<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-15"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 8: 0.3685s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4717<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-16"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 9: 0.3726s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4786<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-17"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 10: 0.3738s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4835<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-18"></span>
<span id="cb6-19"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Training</span> Benchmark Result <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Fern</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb6-20"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Average</span> Iteration Time: 0.3697s</span>
<span id="cb6-21"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Average</span> Speed:          2.70 it/s</span>
<span id="cb6-22"></span>
<span id="cb6-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># **GPU (NVIDIA L4 through Vertex AI Workbench)</span></span>
<span id="cb6-24"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">tests/test_benchmark_training.py</span></span>
<span id="cb6-25"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loading</span> Fern dataset for training benchmark from data/nerf_example_data/nerf_llff_data/fern...</span>
<span id="cb6-26"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Benchmarking</span> training step with 10091 Gaussians at 504x378 resolution...</span>
<span id="cb6-27"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Warming</span> up <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">JIT</span> compilation of train_step<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">...</span></span>
<span id="cb6-28"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Warm-up/Compilation</span> took 5.7437s</span>
<span id="cb6-29"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Running</span> benchmark <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">10</span> training iterations<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">...</span></span>
<span id="cb6-30"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 1: 3.6011s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4832<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-31"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 2: 0.0397s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4754<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-32"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 3: 0.0397s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4722<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-33"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 4: 0.0396s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4645<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-34"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 5: 0.0396s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4633<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-35"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 6: 0.0395s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4597<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-36"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 7: 0.0395s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4649<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-37"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 8: 0.0396s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4717<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-38"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 9: 0.0396s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4786<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-39"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Iteration</span> 10: 0.0395s <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Loss:</span> 0.4835<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb6-40"></span>
<span id="cb6-41"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Training</span> Benchmark Result <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Fern</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb6-42"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Average</span> Iteration Time: 0.0396s</span>
<span id="cb6-43"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Average</span> Speed:          25.26 it/s<span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">**</span></span></code></pre></div></div>
<p>In a simpler summary,</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th><strong>Metric</strong></th>
<th><strong>🍎 Apple M4 (CPU)</strong></th>
<th><strong>⚡ NVIDIA L4 (Cloud)</strong></th>
<th><strong>Difference</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Throughput</strong></td>
<td><strong>2.70 it/s</strong></td>
<td><strong>25.26 it/s</strong></td>
<td><strong>GPU is ~9.4x Faster</strong></td>
</tr>
<tr class="even">
<td><strong>Step Time</strong></td>
<td>369.7 ms</td>
<td>39.6 ms</td>
<td>GPU is real-time</td>
</tr>
<tr class="odd">
<td><strong>JIT Warmup</strong></td>
<td><strong>0.85 s</strong></td>
<td>5.74 s</td>
<td><strong>M4 is ~7x Quicker</strong></td>
</tr>
</tbody>
</table>
<p>It is obvious and clear that using NVIDIA L4 GPU saves us a lot of training time to get a photorealistic scene in comparison to Apple M4 CPU (9.4x faster).</p>
<p>The Apple M4 secures a victory in the JIT warmup latency, completing it roughly 7x quicker. This discrepancy is not surprising, stemming from the heavy optimization tax required for GPU execution: XLA must performs aggressive kernel fusion and complex thread scheduling to coordinate thousands of CUDA cores, a process akin to architecting a massive factory floor. Conversely, the CPU follows a shallower compilation path to straightforward LLVM instructions, allowing it to start running almost immediately. Ultimately, the GPU takes much longer to “plan” its work, but once optimized, its parallel throughput completely eclipses the CPU.</p>
<hr>
<p>For thirty years, the “Triangle” has been the dictator of digital reality. It defined how we built games, how we modeled physics, and how we visualized data. But the triangle is rigid, hollow, and fundamentally disconnected from the messy, volumetric nature of the real world.</p>
<p>3D Gaussian Splatting represents more than just a faster way to render pictures; it is a shift toward a more organic representation of 3D space. We are moving from manually constructing hollow shells to optimizing volumetric clouds that “evolve” to match reality.</p>


</video></video></section>

 ]]></description>
  <category>machine-learning</category>
  <category>deep-learning</category>
  <category>computer-vision</category>
  <category>implementation</category>
  <category>research-notes</category>
  <guid>https://ghif.github.io/posts/2026-01-13-3d-gaussian-splatting-engine-jax/</guid>
  <pubDate>Tue, 13 Jan 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Agentic Engineering Insights from The Creator of Claude Code</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2026-01-07-agentic-engineering-insights-claude-code/</link>
  <description><![CDATA[ 




<p>I recently came across an insightful <a href="https://x.com/bcherny/status/2007179832300581177?s=20">thread on X</a> by Boris Cherny, one of the creators of Claude Code, detailing his workflow for AI-driven software engineering. I have included his full post below to serve as a study guide, as many of these patterns are platform-agnostic and equally applicable to other agentic coding tools.</p>
<p>Gone are the days when software engineering meant writing every line of code manually or staring blankly at a blinking cursor. We are entering the exciting era of <a href="https://arxiv.org/pdf/2509.06216"><strong>Agentic Software Engineering (SE 3.0)</strong></a>, where developers evolve from mere code writers into system architects and orchestrators.</p>
<p>In this new paradigm, the bottleneck is no longer how fast you can type syntax, but how clearly you can articulate intent. SE 3.0 isn’t just about auto-completion; it is about delegating the entire OODA loop (Observe, Orient, Decide, Act) of development to AI. We are moving toward a workflow where the human engineer focuses on high-level system design, edge-case reasoning, and strategic decision-making, while AI agents handle the implementation, testing, and iterative refactoring.</p>
<p>With that context in mind, Boris Cherny’s thread offers a practical, real-world glimpse into what this workflow looks like today.</p>
<hr>
<hr>
<hr>
<p>I’m Boris and I created Claude Code. Lots of people have asked how I use Claude Code, so I wanted to show off my setup a bit.</p>
<p>My setup might be surprisingly vanilla! Claude Code works great out of the box, so I personally don’t customize it much. There is no one correct way to use Claude Code: we intentionally build it in a way that you can use it, customize it, and hack it however you like. Each person on the Claude Code team uses it very differently.</p>
<p>So, here goes.</p>
<ol type="1">
<li>I run 5 Claudes in parallel in my terminal. I number my tabs 1-5, and use system notifications to know when a Claude needs input <a href="https://t.co/nmRJ5km3oZ">https://code.claude.com/docs/en/terminal-config#iterm-2-system-notifications</a></li>
</ol>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-01-07-agentic-engineering-insights-claude-code/media/G9rtc4EasAELEzh.jpeg" class="img-fluid figure-img"></p>
<figcaption>G9rtc4EasAELEzh.jpeg</figcaption>
</figure>
</div>
<ol type="1">
<li><p>I also run 5-10 Claudes on <a href="https://t.co/pEWPQoSq5t">http://claude.ai/code</a>, in parallel with my local Claudes. As I code in my terminal, I will often hand off local sessions to web (using &amp;), or manually kick off sessions in Chrome, and sometimes I will –teleport back and forth. I also start a few sessions from my phone (from the Claude iOS app) every morning and throughout the day, and check in on them later.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-01-07-agentic-engineering-insights-claude-code/media/G9reZjqaYAA9btU.jpeg" class="img-fluid figure-img"></p>
<figcaption>G9reZjqaYAA9btU.jpeg</figcaption>
</figure>
</div></li>
<li><p>I use Opus 4.5 with thinking for everything. It’s the best coding model I’ve ever used, and even though it’s bigger &amp; slower than Sonnet, since you have to steer it less and it’s better at tool use, it is almost always faster than using a smaller model in the end.</p></li>
<li><p>Our team shares a single <a href="http://CLAUDE.md">CLAUDE.md</a> for the Claude Code repo. We check it into git, and the whole team contributes multiple times a week. Anytime we see Claude do something incorrectly we add it to the CLAUDE.md, so Claude knows not to do it next time. Other teams maintain their own CLAUDE.md’s. It is each team’s job to keep theirs up to date.</p></li>
</ol>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-01-07-agentic-engineering-insights-claude-code/media/G9rfKYRbkAA6Q3w.jpeg" class="img-fluid figure-img"></p>
<figcaption>G9rfKYRbkAA6Q3w.jpeg</figcaption>
</figure>
</div>
<ol start="5" type="1">
<li>During code review, I will often tag @.claude on my coworkers’ PRs to add something to the <a href="http://CLAUDE.md">CLAUDE.md</a> as part of the PR. We use the Claude Code Github action (/install-github-action) for this. It’s our version of <a href="https://x.com/danshipper"><span class="citation" data-cites="danshipper">@danshipper</span></a>’s <a href="https://every.to/chain-of-thought/compound-engineering-how-every-codes-with-agents">Compounding Engineering</a>.</li>
</ol>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-01-07-agentic-engineering-insights-claude-code/media/G9rhsVFasAIUCYj.jpg" class="img-fluid figure-img"></p>
<figcaption>G9rhsVFasAIUCYj.jpg</figcaption>
</figure>
</div>
<ol start="6" type="1">
<li>Most sessions start in Plan mode (shift+tab twice). If my goal is to write a Pull Request, I will use Plan mode, and go back and forth with Claude until I like its plan. From there, I switch into auto-accept edits mode and Claude can usually 1-shot it. A good plan is really important!</li>
</ol>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-01-07-agentic-engineering-insights-claude-code/media/G9rjZcwasAQpPN6.png" class="img-fluid figure-img"></p>
<figcaption>G9rjZcwasAQpPN6.png</figcaption>
</figure>
</div>
<ol start="7" type="1">
<li>I use slash commands for every “inner loop” workflow that I end up doing many times a day. This saves me from repeated prompting, and makes it so Claude can use these workflows, too. Commands are checked into git and live in .claude/commands/.</li>
</ol>
<p>For example, Claude and I use a /commit-push-pr slash command dozens of times every day. The command uses inline bash to pre-compute git status and a few other pieces of info to make the command run quickly and avoid back-and-forth with the model (<a href="https://code.claude.com/docs/en/slash-commands#bash-command-execution">https://code.claude.com/docs/en/slash-commands#bash-command-execution</a>).</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-01-07-agentic-engineering-insights-claude-code/media/G9rj3eFasAEK_8J.jpg" class="img-fluid figure-img"></p>
<figcaption>G9rj3eFasAEK_8J.jpg</figcaption>
</figure>
</div>
<ol start="8" type="1">
<li>I use a few subagents regularly: code-simplifier simplifies the code after Claude is done working, verify-app has detailed instructions for testing Claude Code end to end, and so on. Similar to slash commands, I think of subagents as automating the most common workflows that I do for most PRs.</li>
</ol>
<p><a href="https://code.claude.com/docs/en/sub-agents">https://code.claude.com/docs/en/sub-agents</a></p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-01-07-agentic-engineering-insights-claude-code/media/G9rnUzEasAElFcN.png" class="img-fluid figure-img"></p>
<figcaption>G9rnUzEasAElFcN.png</figcaption>
</figure>
</div>
<ol start="9" type="1">
<li>We use a PostToolUse hook to format Claude’s code. Claude usually generates well-formatted code out of the box, and the hook handles the last 10% to avoid formatting errors in CI later.</li>
</ol>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-01-07-agentic-engineering-insights-claude-code/media/G9rrnTxasAAMoZ_.jpg" class="img-fluid figure-img"></p>
<figcaption>G9rrnTxasAAMoZ_.jpg</figcaption>
</figure>
</div>
<ol start="10" type="1">
<li>I don’t use –dangerously-skip-permissions. Instead, I use /permissions to pre-allow common bash commands that I know are safe in my environment, to avoid unnecessary permission prompts. Most of these are checked into .claude/settings.json and shared with the team.</li>
</ol>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-01-07-agentic-engineering-insights-claude-code/media/G9rlDa-asAAXlHx.jpg" class="img-fluid figure-img"></p>
<figcaption>G9rlDa-asAAXlHx.jpg</figcaption>
</figure>
</div>
<ol start="11" type="1">
<li>Claude Code uses all my tools for me. It often searches and posts to Slack (via the MCP server), runs BigQuery queries to answer analytics questions (using bq CLI), grabs error logs from Sentry, etc. The Slack MCP configuration is checked into our .mcp.json and shared with the team.</li>
</ol>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-01-07-agentic-engineering-insights-claude-code/media/G9rl_pQb0AAILz8.jpg" class="img-fluid figure-img"></p>
<figcaption>G9rl_pQb0AAILz8.jpg</figcaption>
</figure>
</div>
<ol start="12" type="1">
<li>For very long-running tasks, I will either (a) prompt Claude to verify its work with a background agent when it’s done, (b) use an agent Stop hook to do that more deterministically, or (c) use the ralph-wiggum plugin (originally dreamt up by <a href="https://x.com/GeoffreyHuntley"><span class="citation" data-cites="geoff">@geoff</span></a>). I will also use either –permission-mode=dontAsk or –dangerously-skip-permissions in a sandbox to avoid permission prompts for the session, so Claude can cook without being blocked on me.</li>
</ol>
<p>https://github.com/anthropics/claude-plugins-official/tree/main/plugins/ralph-wiggum</p>
<p>https://code.claude.com/docs/en/hooks-guide</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2026-01-07-agentic-engineering-insights-claude-code/media/G9ro4W5bEAAQ3ug.jpg" class="img-fluid figure-img"></p>
<figcaption>G9ro4W5bEAAQ3ug.jpg</figcaption>
</figure>
</div>
<ol start="13" type="1">
<li>A final tip: probably the most important thing to get great results out of Claude Code – give Claude a way to verify its work. If Claude has that feedback loop, it will 2-3x the quality of the final result.</li>
</ol>
<p>Claude tests every single change I land to <a href="http://claude.ai/code">http://claude.ai/code</a> using the Claude Chrome extension. It opens a browser, tests the UI, and iterates until the code works and the UX feels good.</p>
<p>Verification looks different for each domain. It might be as simple as running a bash command, or running a test suite, or testing the app in a browser or phone simulator. Make sure to invest in making this rock-solid.</p>



 ]]></description>
  <category>generative-ai</category>
  <category>implementation</category>
  <category>opinions</category>
  <category>reading-notes</category>
  <category>research-notes</category>
  <guid>https://ghif.github.io/posts/2026-01-07-agentic-engineering-insights-claude-code/</guid>
  <pubDate>Wed, 07 Jan 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Generating Audio-Driven Talking Face Animation from Single Image</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2025-12-17-audio-driven-talking-face-animation/</link>
  <description><![CDATA[ 




<p>In my previous article, we explored how to <a href="https://app.notion.com/p/Demystifying-Text-to-Speech-and-Voice-Cloning-for-Content-Creation-28ea59d3736080e3a53eec790a2b2797?pvs=21">synthesize and clone our own voices from text</a>. That tool was a crucial first step, allowing us to create multimedia content with a personalized voice without ever stepping in front of a microphone.</p>
<p>But, of course, a voice in the void is not a character. To generate a complete Digital Human / Character, we need the missing piece: Audio-Driven Talking Face Generation. This is actually a large piece of cutting-edge AI research work on its own. The goal is to take static images and a purely audio file, and merge them into a video where the face not only moves naturally but lip-syncs perfectly to the speech.</p>
<p>Talking practicality to the next level, we can now start with nothing more than a single face portrait and let the machine handle the heavy lifting — a capability known as “One-shot Audio-Driven Talking Face Generation.”</p>
<p>In this article, I will discuss some research advancements of audio-driven talking face, then walk you through the practical steps of building an educational video starring a digital human, using the latest advancements in generative AI.</p>
<section id="state-of-audio-driven-talking-faces" class="level2">
<h2 class="anchored" data-anchor-id="state-of-audio-driven-talking-faces">State of Audio-Driven Talking Faces</h2>
<p>Crossing the “Uncanny Valley”—generating a digital face indistinguishable from a human—has been the “boss level” of computer graphics for decades. However, the last few years have seen the industry make a massive leap from “significant progress” to “solved problem.”</p>
<p>For a long time, the standard approach was simple <strong>lip synchronization</strong>: essentially gluing a moving mouth onto a static face. If you followed the early explosion of “talking head” AI, you likely remember models like Wav2Lip (<a href="https://arxiv.org/pdf/2008.10010">Prajwal et al.&nbsp;2020</a>). While they were technically impressive at matching phonemes to mouth shapes, the results were often unsettling — the mouth moved perfectly, but the eyes were dead and the head was frozen. It was accurate, but robotic.</p>
<p>Research from 2024 through 2025 has rewritten the playbook. We are no longer just syncing lips: we are generating holistic facial dynamics. Here is the breakdown of how AI avatars woke up.</p>
<section id="beyond-the-lips" class="level3">
<h3 class="anchored" data-anchor-id="beyond-the-lips"><strong>Beyond The Lips</strong></h3>
<p>The biggest shift in the past years has been the move from “lip-sync” to “life-sync”.</p>
<p>Defining this new era are heavyweights like Microsoft’s VASA-1 (<a href="https://arxiv.org/abs/2404.10667">Xu et al., 2024</a>) and Alibaba’s EMO (<a href="https://arxiv.org/abs/2402.17485">Tian et al.&nbsp;2024</a>). Both utilize Diffusion Models — the same tech behind image generators like Midjourney — but apply it to complex, full-video motion.</p>
<p>EMO takes a “brute force” approach. Instead of relying on 3D face models or landmarks, it was trained on over 250 hours of diverse footage — including people singing and shouting — to learn the direct relationship between sound and motion. The result? Avatars that can sing opera or rap with full emotional intensity.</p>
<p>In contrast, MuseTalk (<a href="https://arxiv.org/pdf/2410.10122">Zhang et al.&nbsp;2025</a>) took a similar logic, but used Generative Adversarial Networks (GAN) and borrowed “inpainting” idea on the mouth region alone. This strategy trades between the fidelity of generated video and the speed of inference.</p>
</section>
<section id="need-for-speed-3d-gaussian-splatting" class="level3">
<h3 class="anchored" data-anchor-id="need-for-speed-3d-gaussian-splatting"><strong>Need for Speed: 3D Gaussian Splatting</strong></h3>
<p>While diffusion models make high-quality videos, they are slow. Generating a few seconds of video can take minutes of compute time. That’s not suitable for a live video chat.</p>
<p>Enter 3D Gaussian Splatting (3DGS) (<a href="https://arxiv.org/abs/2308.04079">Kerbl et al., 2023</a>). With this approach, we leverage an explicit 3D representation to represent a face as a cloud of millions of 3D blobs (Gaussians) that can be rendered instantly, instead of traditional 3D meshes.</p>
<p>The first wave of audio-driven 3DGS, such as GaussianTalker (<a href="https://arxiv.org/pdf/2404.16012v2">Cho et al., 2024</a>) and TalkingGaussian (<a href="https://arxiv.org/pdf/2404.15264">Li et al., 2024</a>), used “end-to-end” architectures. They utilized tri-plane representations to map audio signals directly to 3D deformations. While they achieved visual fidelity comparable to diffusion models, they suffered from a flaw: temporal instability. Because these models often generated frames independently or relied on imperfect tracking, the avatars exhibited visible “wobbling” artifacts, flickering, and inconsistent lip synchronization.</p>
<p>To reduce the “wobble”, recent methods turned to “hybrid” architectures. They anchor the unstable, free-floating Gaussians to rigorous 3D geometry — specifically 3D Morphable Models (3DMM) like FLAME (<a href="https://dl.acm.org/doi/pdf/10.1145/3130800.3130813">Li et al.&nbsp;2017</a>). A key innovation here was GaussianAvatars (<a href="https://arxiv.org/pdf/2312.02069">Qian et al., 2024</a>), which explicitly binds 3D Gaussians to the triangles of a FLAME mesh. By initializing Gaussian blobs based on mesh vertices and normals, and back-propagating for each triangle, the rendering becomes robust against tracking inaccuracies. This geometric constraint effectively stabilizes the avatar, preventing the chaotic drifting of facial features often observed in purely end-to-end approaches. However, that method has not covered the animation generation driven by speech audio.</p>
<p>The latest research, GaussianHeadTalk (<a href="https://arxiv.org/pdf/2512.10939">Agarwal et al., 2025</a>), builds on GaussianAvatars by animating these anchored avatars directly from speech. Instead of mapping instantaneous audio cues to immediate pixel deformations, these models use Transformer architectures to capture long-range semantic information and dependencies within the speech signal. This allows the system to predict smooth, consistent parameters for the 3DMM scaffold rather than acting on a disjointed frame-by-frame basis. The result is a generation pipeline capable of producing “wobble-free,” temporally consistent talking heads with precise lip-sync at real-time speeds exceeding 45 FPS.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-12-17-audio-driven-talking-face-animation/media/Screenshot_2025-12-16_at_12.05.18.png" class="img-fluid figure-img"></p>
<figcaption>Architecture of GaussianHeadTalk (Agarwal et al.&nbsp;2025)</figcaption>
</figure>
</div>
<p>Architecture of GaussianHeadTalk (Agarwal et al.&nbsp;2025)</p>
</section>
<section id="micro-details-sighs-laughs-and-blinks" class="level3">
<h3 class="anchored" data-anchor-id="micro-details-sighs-laughs-and-blinks"><strong>Micro-Details: Sighs, Laughs, and Blinks</strong></h3>
<p>A face that never blinks is terrifying. A face that talks without taking a breath feels fake. The latest research, specifically a model called KeyFace (<a href="https://arxiv.org/pdf/2503.01715">Bigata et al., 2025</a>), focuses on Non-Speech Vocalizations (NSVs). Standard models ignore sounds like laughter, sighs, or yawns. KeyFace actually listens for them. If the audio has a sigh, the avatar’s shoulders might drop and the head might lower.</p>
<p>Other models like ManiTalk (<a href="https://link.springer.com/article/10.1007/s00371-024-03490-4">Fang et al., 2024</a>) now treat blinking as a controllable feature, letting creators decide if an avatar should have a “nervous blink” or a “sleepy stare”.</p>
</section>
<section id="commercial-showdown" class="level3">
<h3 class="anchored" data-anchor-id="commercial-showdown"><strong>Commercial Showdown</strong></h3>
<p>Some commercial products have improved their capabilities along the line. <a href="https://developer.nvidia.com/ace-for-games">NVIDIA ACE</a> is building the engine for gamers. Their <a href="https://developer.nvidia.com/blog/nvidia-open-sources-audio2face-animation-model/">Audio2Face</a> tech does not just make a video; it generates 3D geometry that game developers can drop into VFX or game dev tools such as Autodesk Maya and Unreal Engine 5.</p>
<p>Synthesia has launched <a href="https://www.technologyreview.com/2025/09/04/1123054/synthesias-ai-clones-are-more-expressive-than-ever-soon-theyll-be-able-to-talk-back/">Express-1 and Express-2</a>, moving away from static avatars to “digital actors” that can perform scripts with semantic awareness — meaning they are aware of what they are saying, not just the sounds they are making.</p>
<p><a href="https://app.heygen.com/">HeyGen</a> has also provided their “Interactive Avatar” product, allowing for sub-200ms latency response that feel like a real Zoom call.</p>
<hr>
<p>In this deep dive, we are going hands-on with MuseTalk (https://github.com/TMElyralab/MuseTalk). We will explore how to leverage this tool for audio-driven face animation that is fast enough to run on a single commercial GPU and simple enough to set up — no need to involve complex 3D assets.</p>
</section>
</section>
<section id="the-workflow-at-a-glance" class="level2">
<h2 class="anchored" data-anchor-id="the-workflow-at-a-glance">The Workflow at a Glance</h2>
<p>Creating a convincing talking head isn’t just one button press: it is a pipeline. Here is the workflow we will cover:</p>
<ol type="1">
<li><strong>Speech script preparation:</strong> Crafting the narrative</li>
<li><strong>Voice synthesis and cloning:</strong> Generating the audio driver</li>
<li><strong>Initial video generation:</strong> Bringing the static image to life (blinking, head movement)</li>
<li><strong>Voice dubbing/face reenactment:</strong> The final lip-syncing using MuseTalk</li>
</ol>
<hr>
<section id="step-1-speech-script-preparation" class="level3">
<h3 class="anchored" data-anchor-id="step-1-speech-script-preparation">Step 1: Speech Script Preparation</h3>
<p>The first step is to prepare the content. For a digital human to be engaging, the script needs to be compeling. For the demonstration, I have written a short educational narrative about “World Models” — an emerging topic in AI community that moves beyond standard Large Language Models (LLMs).</p>
<p>The following is the script example for 1 - 2 minute speech — and, of course, I ask an LLM (Gemini) for helping me copywriting 🙂 :</p>
<pre><code>Stop calling them chatbots. That era is over. The new trend isn't just about Language Models anymore; it’s about World Models.

Here is the difference. An LLM predicts the next word in a sentence. A World Model predicts the next second of reality. It understands 3D space, gravity, and consequence. Here are the big players building the Matrix.

First, meet the Dreamer: Genie 3 from Google DeepMind. This isn't just a video generator; it is an interactive game engine. You can actually grab a controller and play inside the video it creates. It generates the world and the gameplay in real-time. It is essentially an infinite holodeck.

Second, the Architect: Marble from World Labs. Most AI video is like a hallucination; it warps and shifts. Marble creates solid, persistent 3D worlds. You can take a photo of a room, and Marble turns it into a 3D asset you can actually edit and walk around in. This gives AI true Spatial Intelligence.

Finally, the Engineers: NVIDIA Cosmos and PAN from MBZUAI. These are the brains for robots. They don't just see video; they understand physics. They allow robots to preview the future, simulating what happens if they drop a cup or crash a car before they actually do it.

Genie dreams it, Marble builds it, and NVIDIA simulates the physics. We are moving from AI that writes about the world, to AI that lives in it. Which is scarier to you: An infinite game, or a robot that can predict the future? Let me know in the comments.</code></pre>
</section>
<section id="step-2-voice-synthesis-and-cloning" class="level3">
<h3 class="anchored" data-anchor-id="step-2-voice-synthesis-and-cloning">Step 2: Voice Synthesis and Cloning</h3>
<p>With the script ready, we need a voice. Instead of recording this myself, I used my custom Text-to-Speech (TTS) and Voice Cloning pipeline: <a href="https://github.com/ghif/tts-vc">https://github.com/ghif/tts-vc</a>. This allows for rapid iteration — if want to change a sentence later, I don’t need to re-record audio.</p>
<p>Using Chirp 3 as the TTS backbone, this results in a clean, high-quality audio file that captures the cadence and tone of the script, which is critical because this audio waveform will literally “drive” the animation of the face expressions in the final step.</p>
<p><a href="media/ghif_worldmodels.mp3">ghif_worldmodels.mp3</a></p>
</section>
<section id="step-3-initial-video-generation" class="level3">
<h3 class="anchored" data-anchor-id="step-3-initial-video-generation">Step 3: Initial Video Generation</h3>
<p>I start with a single portrait of my own face. Technically, we could apply lip-sync to a static face image directly. However, it only drives the mouth moves, but the rest of the face feels dead and robotic.</p>
<p>To achieve more lifelike result, we first need to convert our single static image into a “silent video” where the character is blinking, breathing, and slightly moving their head. This provides a natural canvas for the lip-synching model to work on.</p>
<ul>
<li><strong>Tools Used:</strong> Google Veo (for high-fidelity image-to-video generation) and Nano Banana (optional, if you want a first-pass editing of the portrait).</li>
<li><strong>Process:</strong> I fed the static portrait into Veo with aspect ratio 16:9 using a prompt</li>
</ul>
<pre><code>Generate an animated face video of this portrait with natural and gentle eye blinking as well as minimal head movement. Don't make it talk.</code></pre>
<p>This generated a ~8-second video clip of the character looking alive, but silent.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-12-17-audio-driven-talking-face-animation/media/Screenshot_2025-12-10_at_21.12.48.png" class="img-fluid figure-img"></p>
<figcaption>Screenshot 2025-12-10 at 21.12.48.png</figcaption>
</figure>
</div>
</section>
<section id="step-4-voice-dubbing-face-reenactment-musetalk" class="level3">
<h3 class="anchored" data-anchor-id="step-4-voice-dubbing-face-reenactment-musetalk">Step 4: Voice Dubbing / Face Reenactment (MuseTalk)</h3>
<p>Now for the magic. We have the <strong>audio</strong> (Step 2) and the <strong>video</strong> (Step 3). We need to fuse them.</p>
<p>For this, I utilized MuseTalk, a state-of-the-art high quality lip synchronization model from Tencent. Unlike older GAN-based models (like Wav2Lip) which often produce blurry mouth regions, or diffusion model-based approaches that are typically much slower, MuseTalk uses a Latent Space Inpainting technique: <a href="https://github.com/TMElyralab/MuseTalk">https://github.com/TMElyralab/MuseTalk</a>.</p>
<p>How it works:</p>
<p><strong>A. Face detection:</strong> The model detects the face in our silent video.</p>
<p><strong>B. Audio feature extraction:</strong> It analyzes the audio file, breaking it down into phonetic features (Whisper’s features).</p>
<p><strong>C. Latent inpainting:</strong> Instead of generating pixels from scratch, it masks out the mouth region of the video and “inpaints” the correct lip shape for that specific milliseconds of audio directly in the latent space — a compressed representation of the image induced by a pretrained Variational Autoencoder (VAE) encoder.</p>
<p><strong>D. Reconstruction:</strong> It decodes the result back into high-resolution video frames through a pretrained VAE decoder.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-12-17-audio-driven-talking-face-animation/media/Screenshot_2025-12-12_at_12.00.39.png" class="img-fluid figure-img"></p>
<figcaption>Illustration of MuseTalk’s framework (<a href="https://arxiv.org/abs/2410.10122">Zhang et al.&nbsp;2025</a>)</figcaption>
</figure>
</div>
<p>Illustration of MuseTalk’s framework (<a href="https://arxiv.org/abs/2410.10122">Zhang et al.&nbsp;2025</a>)</p>
<p>This technique can runs at 30fps+ on modern commercial GPUs, making it one of the most efficient ways to generate high-fidelity talking faces. I ran the entire MuseTalk inference process using <a href="https://docs.cloud.google.com/vertex-ai/docs/workbench/introduction">Vertex AI Workbench</a> with NVIDIA L4 GPU.</p>
<p>To run it smoothly on a Workbench’s jupyter notebook/terminal, I made a minor adjustment in the installation process described in the original <a href="https://github.com/TMElyralab/MuseTalk">README</a>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Step 1: Create a new conda environment in a persistent directory (under /home/jupyter/)</span></span>
<span id="cb3-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">conda</span> create <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> /home/jupyter/envs/MuseTalk python==3.10 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-y</span></span>
<span id="cb3-3"></span>
<span id="cb3-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Step 2: Activate the new environment</span></span>
<span id="cb3-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">conda</span> activate /home/jupyter/envs/MuseTalk</span>
<span id="cb3-6"></span>
<span id="cb3-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Step 3: Clone the MuseTalk's source code and go to the root directory</span></span>
<span id="cb3-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> clone https://github.com/TMElyralab/MuseTalk.git</span>
<span id="cb3-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> MuseTalk</span>
<span id="cb3-10"></span>
<span id="cb3-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Step 4: Install all required libraries</span></span>
<span id="cb3-12"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-r</span> requirements.txt</span>
<span id="cb3-13"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--no-cache-dir</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-U</span> openmim</span>
<span id="cb3-14"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">mim</span> install <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mmcv==2.0.1"</span></span>
<span id="cb3-15"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">mim</span> install <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mmdet==3.1.0"</span></span>
<span id="cb3-16"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">mim</span> install <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--no-build-isolation</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mmpose==1.1.0"</span></span>
<span id="cb3-17"></span>
<span id="cb3-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Step 5: Remove huggingface-hub and gdown libraries inside download_weights.sh to avoid library's version conflicts</span></span>
<span id="cb3-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Open download_weights.sh file and delete lines associated with huggingface-hub and gdown.</span></span>
<span id="cb3-20"></span>
<span id="cb3-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Step 6: Download all the model's weights</span></span>
<span id="cb3-22"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sh</span> ./download_weights.sh</span>
<span id="cb3-23"></span>
<span id="cb3-24"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Step 7: Configure the test.yaml to specify the inputs and run the inference script</span></span>
<span id="cb3-25"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sh</span> ./inference.sh v1.5 normal</span></code></pre></div></div>
<p><a href="https://www.ffmpeg.org/">FFmpeg</a> is also needed to process the video and audio. Here’s how to install ffmpeg in a persistent directory of the Workbench’s instance.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Go to home directory</span></span>
<span id="cb4-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> ~</span>
<span id="cb4-3"></span>
<span id="cb4-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Create a "bin" directory</span></span>
<span id="cb4-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mkdir</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> ~/bin</span>
<span id="cb4-6"></span>
<span id="cb4-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Download the source</span></span>
<span id="cb4-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wget</span> https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-O</span> ~/ffmpeg-static.tar.xz</span>
<span id="cb4-9"></span>
<span id="cb4-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Extract the source files to "bin"</span></span>
<span id="cb4-11"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tar</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-xf</span> ~/ffmpeg-static.tar.xz <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-C</span> ~/bin/</span>
<span id="cb4-12"></span>
<span id="cb4-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Embed the path to the PATH env variable</span></span>
<span id="cb4-14"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">echo</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'export PATH="$HOME/bin/ffmpeg-7.0.2-amd64-static:$PATH"'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;&gt;</span> ~/.bashrc</span>
<span id="cb4-15"></span>
<span id="cb4-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Activate the new PATH</span></span>
<span id="cb4-17"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">source</span> ~/.bashrc</span>
<span id="cb4-18"></span>
<span id="cb4-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Go back to conda env</span></span>
<span id="cb4-20"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">conda</span> activate /home/jupyter/envs/MuseTalk</span></code></pre></div></div>
</section>
</section>
<section id="final-content" class="level2">
<h2 class="anchored" data-anchor-id="final-content">Final Content</h2>
<p>By combining a thoughtful script, cloned voice, AI-generated video motion, and MuseTalk’s precision lip-syncing, we get a final video that crosses the uncanny valley.</p>
<p>An example of full generated video with minor post-editing can be seen below.</p>
<p><a href="https://www.youtube.com/watch?v=GIsw39CROow">https://www.youtube.com/watch?v=GIsw39CROow</a></p>
<p>Another example with a different speech script:</p>
<p><a href="https://www.youtube.com/watch?v=mrQnMZWh-3Q">https://www.youtube.com/watch?v=mrQnMZWh-3Q</a></p>


</section>

 ]]></description>
  <category>generative-ai</category>
  <category>computer-vision</category>
  <category>implementation</category>
  <guid>https://ghif.github.io/posts/2025-12-17-audio-driven-talking-face-animation/</guid>
  <pubDate>Wed, 17 Dec 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Build Your Own Sovereign AI Personal Healthcare with MedGemma</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2025-11-29-sovereign-ai-personal-healthcare-medgemma/</link>
  <description><![CDATA[ 




<p>In the rapidly evolving landscape of AI, healthcare is frontier where the promise of AI meets real-world impact. Enter <a href="https://deepmind.google/models/gemma/medgemma/">MedGemma</a>, a model family developed by Google DeepMind, designed specifically to understand and interpret both medical text and images. Built on the robust foundation of the Gemma 3 architecture (<a href="https://arxiv.org/abs/2503.19786">Gemma team, 2025</a>), MedGemma represents a significant step toward giving developers and clinicians the tools to build healthcare-oriented AI applications.</p>
<p>Perhaps most importantly, MedGemma opens the door for accessible, locally-deployable health AI systems. With open-model availability and modular design, developers can run it on private infrastructure, maintain control of sensitive health data, and build customized workflows for triage, patient intake, or image-based diagnostics — rather than relying purely on cloud-based black-box models.</p>
<p>In this article, I’ll walk you through how we can build a simple AI healthcare assistant that runs entirely on our own local machine — thanks to MedGemma’s accessibility and domain-specialized power.</p>
<section id="personal-ai-doctor-web-app" class="level2">
<h2 class="anchored" data-anchor-id="personal-ai-doctor-web-app">Personal AI Doctor Web App</h2>
<p>The personal AI doctor I built is just a simple web-app prototype featuring a conversational UI that supports both chat and voice commands. It runs entirely on a local machine without any Internet connection. Users can also upload images alongside particular instructions for the virtual doctor to perform medical image analysis. The system can respond to user prompts with text and, optionally, speech output, as illustrated in Figure 1.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-11-29-sovereign-ai-personal-healthcare-medgemma/media/demo.gif" class="img-fluid figure-img"></p>
<figcaption>Figure 1: Personal AI Doctor Demo</figcaption>
</figure>
</div>
<p>Figure 1: Personal AI Doctor Demo</p>
<p>The prototype is fully written in Python, comprising frontend and backend services. The frontend implements the user interface components, while all relevant AI models will be served in backend services, comprising:</p>
<ul>
<li>Large multimodal model (MedGemma-4B)</li>
<li>Voice-to-text model (Whisper)</li>
<li>Text-to-Speech (VITS through Coqui TTS)</li>
</ul>
</section>
<section id="medgemma-as-the-foundation-model" class="level2">
<h2 class="anchored" data-anchor-id="medgemma-as-the-foundation-model">MedGemma as The Foundation Model</h2>
<p>MedGemma is a collection of open-model variants developed by Google DeepMind specifically optimized for medical-domain tasks involving both text and images. Built on the underlying architecture of the Gemma 3 family, it brings healthcare-focused capabilities into a freely accessible model ecosystem.</p>
<p>MedGemma currently comes in two variants: a 4B-parameter multimodal version (capable of ingesting medical images + text) and a 27B-parameter text-only (and/or multimodal) version. The multimodal variant uses an image encoder, SigLIP (<a href="https://arxiv.org/pdf/2303.15343">Zhai et al., 2023</a>), which was pre-trained on a large corpus of de-identified medical imager (e.g., chest X-rays, dermatology photos, pathology pictures).</p>
<p>Because MedGemma inherits the capabilities of Gemma 3 (multimodality, long context, efficient architecture), developers can build healthcare applications that have strong baseline performance and benefit from the engineering work behind Gemma 3. Therefore, to further understand MedGemma, it’s worth understanding Gemma 3’s architecture in more details.</p>
<section id="gemma-3" class="level3">
<h3 class="anchored" data-anchor-id="gemma-3">Gemma 3</h3>
<p>Gemma 3 is the base models for MedGemma sized from ~1B parameters up to ~27B parameters. Its design goals are as follows:</p>
<ul>
<li><strong>Multimodal:</strong> Support for text + image (and implicitly more modalities) in a unified model.</li>
<li><strong>Long-context:</strong> Large context ****windows (e.g., 128 K tokens for many variants) so it can process long documents, chats, context spans.</li>
<li><strong>Multilingual:</strong> Wide language support (over 140 languages mentioned) so global use is possible.</li>
<li><strong>Efficiency / deployability:</strong> Built so that the models can reasonably run a single GPU/accelerator rather than requiring massive clusters.</li>
</ul>
<p>The following is the illustration of Gemma 3’s architecture:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-11-29-sovereign-ai-personal-healthcare-medgemma/media/image.png" class="img-fluid figure-img"></p>
<figcaption>Figure 2: Gemma 3’s architecture (<a href="https://developers.googleblog.com/en/gemma-explained-whats-new-in-gemma-3/">https://developers.googleblog.com/en/gemma-explained-whats-new-in-gemma-3/</a>)</figcaption>
</figure>
</div>
<p>Figure 2: Gemma 3’s architecture (<a href="https://developers.googleblog.com/en/gemma-explained-whats-new-in-gemma-3/">https://developers.googleblog.com/en/gemma-explained-whats-new-in-gemma-3/</a>)</p>
<p>In shorts,</p>
<ul>
<li>Gemma 3 uses a decoder-only transformer architecture (i.e., the standard LLM style) but augmented for multimodal input (text + image) via a vision encoder.</li>
<li>It integrates a SigLIP vision encoder (or variant) to process images into a fixed sequence of “soft tokens” that then feed into the transformer.</li>
<li>The attention mechanism is optimized for long-context usage achieved via a hybrid local/global attention scheme (5 local sliding-window layers with window size ~1024 tokens, then 1 global attention layer) to reduce KV-cache burden.</li>
<li>Architectural changes to attention, e.g., Grouped-Query Attention (GQA), and replacing previous “soft-capping” mechanisms with a QK-norm (normalisation on <img src="https://latex.codecogs.com/png.latex?Q%20%5Ctimes%20K"> for better scaling / stability.</li>
</ul>
</section>
<section id="a-bit-deeper-on-gemma-3s-attention-mechanism" class="level3">
<h3 class="anchored" data-anchor-id="a-bit-deeper-on-gemma-3s-attention-mechanism">A bit deeper on Gemma 3’s attention mechanism</h3>
<p>The efficiency of LLMs depends heavily on their attention mechanism — the component responsible for how each token in a sequence interacts with every other. As models scale, memory and latency during inference become major bottlenecks. To address this, researches on attention mechanism have evolved from the classic Multi-Head Attention (MHA) to more efficient forms such as Multi-Query Attention (MQA) and Grouped-Query Attention (GQA).</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-11-29-sovereign-ai-personal-healthcare-medgemma/media/Screenshot_2025-11-10_at_11.50.53.png" class="img-fluid figure-img"></p>
<figcaption>Figure 3: Illustration of the difference among popular attention mechanisms — Gemma 3 utilizes Grouped Query Attention.</figcaption>
</figure>
</div>
<p>Figure 3: Illustration of the difference among popular attention mechanisms — Gemma 3 utilizes Grouped Query Attention.</p>
<p>Let’s unpack the differences.</p>
<p><strong>Multi-Head Attention (MHA)</strong></p>
<p>In standard MHA, each attention heads has its own set of queries, keys, and values. This design allows every head to learn different contextual relationships — for example, one head might focus on syntactic roles while another captures semantic associations. However, the drawback is redundancy: since heads maintain separate keys and values, storing and accessing them during inference becomes expensive in both memory and compute.</p>
<p><strong>Multi-Query Attention (MQA)</strong></p>
<p>To make inference faster, Multi-Query Attention simplifies the structure: each head still has its own queries, but all heads share a single set of keys and values. This drastically reduces the amount of data that needs to be cached during generation (important for autoregressive decoding). With fewer KV pairs to store and retrieve, inference becomes much master and memory usage drops.</p>
<p><strong>Grouped-Query Attention (GQA)</strong></p>
<p>GQA is a hybrid approach that balances the efficiency and expressiveness.</p>
<p>Instead of giving each head its own KV pair (as in MHA) or sharing them across all heads (as in MQA), GQA divides attention heads into groups. Each group of query heads shares one set of keys and values. For instance, if there are 16 attention heads and 4 groups, each group of 4 heads will share a KV set.</p>
<p>This grouping reduces the KV cache size (like MQA) but still retains some diversity across groups (like MHA). The result is nearly the same inference speed and memory efficiency as MQA, but with better accuracy and representational capacity.</p>
<p>The following table summarizes the differences among those three approaches.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th><strong>Feature</strong></th>
<th><strong>Multi-Head</strong></th>
<th><strong>Multi-Query</strong></th>
<th><strong>Grouped-Query</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Keys/Values per Head</strong></td>
<td>Separate</td>
<td>Shared (1 for all)</td>
<td>Shared within groups</td>
</tr>
<tr class="even">
<td><strong>Memory Usage</strong></td>
<td>High</td>
<td>Low</td>
<td>Moderate-Low</td>
</tr>
<tr class="odd">
<td><strong>Inference Speed</strong></td>
<td>Slowest</td>
<td>Fastest</td>
<td>Nearly fastest</td>
</tr>
<tr class="even">
<td><strong>Representation Diversity</strong></td>
<td>Highest</td>
<td>Lowest</td>
<td>Balanced</td>
</tr>
</tbody>
</table>
<p>Gemma 3, like other modern lightweight foundation models (e.g., Gemma 2, Mistral, and LLaMA 3), adopts Grouped-Query Attention because it provides the best balance between performance and resource efficiency — crucial for both local deployment and scalable serving.</p>
<p>During training, GQA allows the model to maintain rich multi-head representation learning. During inference, it reduces the KV cache size (which directly impacts memory usage when generating long sequences). This makes it particularly suitable for consumer hardware (like Apple M-series chips) and edge inference frameworks (like Ollama or vLLM).</p>
</section>
</section>
<section id="personal-doctor-system" class="level2">
<h2 class="anchored" data-anchor-id="personal-doctor-system">Personal Doctor System</h2>
<section id="llm-serving" class="level3">
<h3 class="anchored" data-anchor-id="llm-serving">LLM Serving</h3>
<p>To serve MedGemma locally, <strong>Ollama</strong> is utilized as the inference engine — an open source LLM serving tool. I chose Ollama for its simplicity to setup large language models on consumer hardware with limited number of users — in fact, it’s only me at the moment 🙂. For a production-scale system with large number of users, vLLM might be a better choice.</p>
<p>For this prototype, I use a specific quantized version of MedGemma to balance performance and resource usage. The system relies on the <code>amsaravi/medgemma-4b-it:q6</code> model, which is a 4-billion parameter, instruction-tuned variant quantized to 6-bits. This compression allows the model to run smoothly even on machines with limited VRAM without significantly sacrificing reasoning capabilities.</p>
<p>To set up the model, execute the following commands in your terminal:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pull the specific 6-bit quantized MedGemma model</span></span>
<span id="cb1-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">ollama</span> pull amsaravi/medgemma-4b-it:q6</span>
<span id="cb1-3"></span>
<span id="cb1-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Run the model to start the local inference server</span></span>
<span id="cb1-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">ollama</span> run amsaravi/medgemma-4b-it:q6</span></code></pre></div></div>
</section>
<section id="architecture" class="level3">
<h3 class="anchored" data-anchor-id="architecture">Architecture</h3>
<p>To ensure the simplicity, the entire architecture follows a standard frontend-backend pattern implemented in Python. The frontend leverages <strong>Streamlit</strong> to deliver a streamlined conversational UI prototype, while the backend orchestrates a suite of services housing the AI models.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-11-29-sovereign-ai-personal-healthcare-medgemma/media/arch.png" class="img-fluid figure-img"></p>
<figcaption>Figure 4: Components of AI Personal Doctor</figcaption>
</figure>
</div>
<p>Figure 4: Components of AI Personal Doctor</p>
<p>Interaction with MedGemma is managed through the <strong>Agent Development Kit (ADK)</strong> framework, which established a dual-agent pattern:</p>
<ul>
<li><code>chat_agent</code>: Handles direct user prompts and generates text responses.</li>
<li><code>summary_agent</code>: Activated specifically when the “Play Audio Summary” is triggered.</li>
</ul>
<p>Defining the agent’s role and profile is intuitive with ADK. The code snippet below demonstrates how to configure an agent using MedGemma—served locally via Ollama—as its central “brain”.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> google.adk.agents <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Agent</span>
<span id="cb2-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> google.adk.models.lite_llm <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> LiteLlm</span>
<span id="cb2-3"></span>
<span id="cb2-4">model_name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ollama_chat/amsaravi/medgemma-4b-it:q6"</span></span>
<span id="cb2-5">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> LiteLlm(model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>model_name)</span>
<span id="cb2-6"></span>
<span id="cb2-7">chat_agent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb2-8">    model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>model,</span>
<span id="cb2-9">    name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"medical_chat_service"</span>,</span>
<span id="cb2-10">    description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"An agent that answers medical queries, optionally with images."</span>,</span>
<span id="cb2-11">    instruction<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""You are a helpful medical assistant. </span></span>
<span id="cb2-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    Answer the user's questions accurately and concisely. Always answer in English.</span></span>
<span id="cb2-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    If an image is provided, analyze it in the context of the medical question.</span></span>
<span id="cb2-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb2-15">)</span>
<span id="cb2-16"></span>
<span id="cb2-17">summary_agent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb2-18">    model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>model,</span>
<span id="cb2-19">    name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"medical_summary_service"</span>,</span>
<span id="cb2-20">    description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"An agent that summarizes medical advice into concise paragraphs.."</span>,</span>
<span id="cb2-21">    instruction<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb2-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    You are an expert medical summarizer. </span></span>
<span id="cb2-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    Your goal is to summarize the provided medical advice into a concise, single paragraph. </span></span>
<span id="cb2-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        Ensure the summary flows naturally when read aloud. </span></span>
<span id="cb2-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        YOU MUST USE periods (.) and commas (,) to create natural pauses. </span></span>
<span id="cb2-26"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">      DO NOT use other punctuation marks like question marks (?), exclamation marks (!), colons (:), or semicolons (;). </span></span>
<span id="cb2-27"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        Keep the summary focused on the key medical advice and safety instructions.</span></span>
<span id="cb2-28"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb2-29">)</span></code></pre></div></div>
<p>For audio processing and synthesis, the system employs the <strong>Whisper</strong> library for Speech-to-Text (STT) and utilizes <strong>Coqui TTS</strong> (implemented via PyTorch) for Text-to-Speech (TTS) generation.</p>
</section>
</section>
<section id="voice-command-capability-stt-service" class="level2">
<h2 class="anchored" data-anchor-id="voice-command-capability-stt-service">Voice Command Capability (STT Service)</h2>
<p>To improve user interactiveness, it will be more handful if users can talk directly with the AI assistant rather than typing only, just like in ChatGPT or Gemini applications. To allow this capability, we can use a speech-to-text (STT) or automatic speech recognition (ASR) model to transcribe user’s speech into text, which will then be processed by MedGemma.</p>
<p>One of the most capable open-source ASR models is <a href="https://github.com/openai/whisper">Whisper</a> developed by OpenAI (<a href="https://arxiv.org/pdf/2212.04356">Radford et al.&nbsp;2022</a>). It is an innovative STT focused on achieving robust speech recognition via large-scale weak supervision. Its primary distinguishing factor is the immense scale of its training data and resulting robustness:</p>
<ul>
<li><strong>Massive data scale:</strong> Whisper was scaled up to 680,000 hours of labeled audio data, which includes weak supervision like multilingual and multitask supervision (see Figure 5 below).</li>
<li><strong>Focus on zero-shot transfer</strong>: The system aims to work reliably “out of the box” in a broad range of environments without requiring supervised fine-tuning for every new deployment distribution.</li>
<li><strong>Superior robustness:</strong> Whisper exhibits fundamentally different robustness properties; the best zero-shot Whisper models approach human accuracy and robustness when evaluated across various datasets.</li>
</ul>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-11-29-sovereign-ai-personal-healthcare-medgemma/media/Screenshot_2025-11-13_at_10.32.27.png" class="img-fluid figure-img"></p>
<figcaption>Figure 5: Whisper leverages a seq2seq Transformer model trained on many different speech processing tasks, including multilingual speech recognition, speech translation, spoken language identification, and voice activity detection.</figcaption>
</figure>
</div>
<p>Figure 5: Whisper leverages a seq2seq Transformer model trained on many different speech processing tasks, including multilingual speech recognition, speech translation, spoken language identification, and voice activity detection.</p>
<p>OpenAI offers several variants of the Whisper model to balance speed and accuracy, ranging from <strong>tiny</strong> (39M parameters) to <strong>large</strong> (1550M parameters), as well as the optimized <strong>turbo</strong> model (809M). Depending on the hardware constraints and accuracy requirements, we can select the appropriate size.</p>
<p>Here is a code example demonstrating how to implement audio transcription using Whisper in Python:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> whisper</span>
<span id="cb3-2"></span>
<span id="cb3-3">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> whisper.load_model(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"turbo"</span>)</span>
<span id="cb3-4">...</span>
<span id="cb3-5">result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.transcribe(audio_file_path) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># transcribe text from an audio file</span></span></code></pre></div></div>
</section>
<section id="voice-synthesis-tts-service" class="level2">
<h2 class="anchored" data-anchor-id="voice-synthesis-tts-service">Voice Synthesis (TTS Service)</h2>
<p>To elevate the user experience beyond simple text display, we can give our AI system a voice. It needs a functionality to synthesize speech from text. For this, I utilize Coqui TTS, a popular open-source deep learning toolkit designed to run advanced speech synthesis models directly on local hardware. While the company behind Coqui has sadly ceased operations, the library remains a powerful, community-maintained tool.</p>
<p>Coqui TTS offers a wide registry of pretrained deep learning models, making it easy to find the right fit for our needs. In this setup, I selected the VITS model (specifically <code>tts_models/en/ljspeech/vits</code>). VITS (Variational Inference with adversarial learning for end-to-end Text-to-Speech) is a parallel, end-to-end architecture known for generating highly natural sounding audio (<a href="https://arxiv.org/abs/2106.06103">Kim et al.&nbsp;2021</a>).</p>
<p>Unlike traditional TTS pipelines that require two separate stages — generating acoustic features (like mel-spectrograms) first, and then using a vocoder to create the waveform — VITS unifies these processes. It employs a Variational Autoencoder (VAE) to connect these modules through latent variables, allowing for efficient, high-fidelity waveform generation in a single framework.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-11-29-sovereign-ai-personal-healthcare-medgemma/media/Screenshot_2025-11-27_at_14.55.44.png" class="img-fluid figure-img"></p>
<figcaption>Figure 6: Training and inference mechanism of VITS</figcaption>
</figure>
</div>
<p>Figure 6: Training and inference mechanism of VITS</p>
<p>Functionally, VITS employs Monotonic Alignment Search (MAS) during the training phase to automatically eliminate the alignment between the input text phonemes and the target speech, removing the need for external aligners — Figure 6 shows the difference between the training and inference phases of VITS. Because the model is non-autoregressive, it allows for parallel sampling, which significantly improves synthesis speed compared to sequential models.</p>
<p>The following code snippet illustrates the utilization of VITS model through Coqui TTS.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch</span>
<span id="cb4-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> TTS.api <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> TTS <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Importing Coqui TTS</span></span>
<span id="cb4-3">...</span>
<span id="cb4-4"></span>
<span id="cb4-5">device <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cuda"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> torch.cuda.is_available() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cpu"</span></span>
<span id="cb4-6">model_name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tts_models/en/ljspeech/vits"</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Using a VITS model</span></span>
<span id="cb4-7">tts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> TTS(model_name).to(device)</span>
<span id="cb4-8"></span>
<span id="cb4-9">text <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" ... "</span></span>
<span id="cb4-10">tts.tts_to_file(text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>text, file_path<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>file_path)</span></code></pre></div></div>
</section>
<section id="spec-driven-development-with-spec-kit-optional" class="level2">
<h2 class="anchored" data-anchor-id="spec-driven-development-with-spec-kit-optional">Spec-Driven Development with Spec Kit (Optional)</h2>
<p>I’ve marked this section as “optional” because we don’t have to adopt this approach (Spec-Driven Development) when building the complete application — we could simply follow conventionally way of application development. I’m including it mainly out of curiosity on trying a new productivity tool.</p>
<p>To build the complete system locally, I use <a href="https://github.com/github/spec-kit">Spec Kit</a> — an open-source toolkit that enables Spec-Driven Development (SDD), essentially extending CLI-driven AI agents into a more structured workflow. It’s a bit like the freedom of “vibe coding”, but with much more control and editability through clearly defined specifications. With Spec Kit, we begin with the specification first, and let the code follow — keeping product requirements, intentions, and architecture front and center. The unified source empowers both AI assistants and developers to implement features with consistent outcomes.</p>
<p>For me, adopting this approach has been a breakthrough. While many “vibe-coding” tools can spin off in unpredictable directions, Spec Kit introduces the right amount of structure and guardrails. It allows developers to collaborate more effectively especially with product managers / owners, align with real software-development phases, and retain control—even while harnessing AI capabilities.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-11-29-sovereign-ai-personal-healthcare-medgemma/media/speckit.png" class="img-fluid figure-img"></p>
<figcaption>Figure 7: Spec-Driven Development Life Cycle with Spec</figcaption>
</figure>
</div>
<p>Figure 7: Spec-Driven Development Life Cycle with Spec</p>
<p>Figure 7 illustrates the life cycle steps in SDD. In general, within the workflow we will:</p>
<ol type="1">
<li><strong>Define the “what”</strong>: Using the <code>/speckit.specify</code> command, we describe the feature in plain English — what we want to build, and why — without worrying about the tech stack yet. Outputs: <code>spec.md</code></li>
<li><strong>Plan the “how”</strong>: With <code>/speckit.plan</code> , we pick the architecture, libraries, database, and overall structure. This step translates the spec into a technical implementation blueprint. Outputs: <code>plan.md</code>, <code>quickstart.md</code>, <code>research.md</code>, <code>data-model.md</code>, <code>contracts/openapi.json</code>.</li>
<li><strong>Break into tasks &amp; implement</strong>: Use <code>/speckit.tasks</code> to split the plan into actionable slices, then let <code>/speckit.implement</code> (with AI assistants or our team) generate the code. Specs remain alive and authoritative through process. Outputs: <a href="http://tasks.md"><code>tasks.md</code></a> and your generated codes.</li>
</ol>
<p>The <code>/speckit.constitution</code> step is mostly static — you’ll probably only need to run it once at the very beginning. Think of it as setting the guiding principles: it defines the ground rules for what the AI should or shouldn’t do in subsequent steps. The real iterative action happens between <code>/speckit.specify</code> and <code>/speckit.implement</code>, where ideas evolve into actual features. Each time we run <code>/speckit.specify</code>, it spins up a new Git branch — just like when we’re introducing a quite major change to the application.</p>
<p>It is worth noting that the <code>/speckit.clarify</code>, which is an optional step, is useful to clarify the initially generated specification, involving a human-in-the-loop workflow for detailing some information or reducing ambiguity.</p>
<p>Under the hood, SpecKit relies on an AI code assistant tool as the core engine such as GitHub Copilot, Gemini CLI, Claude Code, Codex CLI, Cursor, and so on. I use <a href="https://github.com/google-gemini/gemini-cli">Gemini CLI</a> for my main AI code assistant in this entire project.</p>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>We have walked through a simple, complete pipeline of building a sovereign healthcare AI—from serving <strong>MedGemma</strong> locally with <strong>Ollama</strong> to enabling speech interaction via <strong>Whisper</strong> and <strong>Coqui TTS</strong>.</p>
<p>While this prototype highlights the potential of the <strong>MedGemma</strong> 4B model, the possibilities for expansion are vast. You could extend the agents to handle actual medical records, fine-tune the TTS voice for a more empathetic persona, or deploy the system on an edge device for portability. The tools for building private, powerful AI are now in your hands—it’s time to build something that protects as much as it assists.</p>
<p>The complete code implementation of this prototype is available on GitHub: <a href="https://github.com/ghif/personal-doctor">https://github.com/ghif/personal-doctor</a>.</p>


</section>

 ]]></description>
  <category>generative-ai</category>
  <category>implementation</category>
  <guid>https://ghif.github.io/posts/2025-11-29-sovereign-ai-personal-healthcare-medgemma/</guid>
  <pubDate>Sat, 29 Nov 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>[Paper Review] 2025 State of AI Code Quality</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2025-11-13-state-of-ai-code-quality/</link>
  <description><![CDATA[ 




<p>I have just reviewed an interesting <a href="https://www.qodo.ai/reports/state-of-ai-code-quality/">report from Qodo: “2025 State of AI Code Quality”</a> that surveyed 609 developers on how AI tools are being used.</p>
<p>The bottom line:&nbsp;<strong>AI is mainstream, but trust is still the biggest blocker to realizing its promised efficiency gains</strong>. I identify, at least, 3 critical themes that can maximize AI workflow value:</p>
<ol type="1">
<li><strong>Context is the Foundation of Trust</strong></li>
</ol>
<p>The primary complaint about current AI tools isn’t how much code they generate, but how&nbsp;<strong>relevant</strong>&nbsp;it is.</p>
<ul>
<li><strong>The context gap is huge:</strong>&nbsp;65% of developers using AI for refactoring and approximately 60% using it for testing, writing, or reviewing report that the assistant “misses relevant context”.</li>
<li><strong>The top fix:</strong>&nbsp;“Improved contextual understanding” is the #1 requested fix (26% of all votes), rising to about 30% when “customization to team standards” is included.</li>
<li><strong>Actionable insight:</strong>&nbsp;AI must act like a teammate who knows the codebase. A learned, repo-wide context engine is necessary for accuracy, quality, and trust. Manually selecting context is broken; 54% of developers who do this still AI misses relevance.</li>
</ul>
<p><strong>2. Confidence Drives Adoption (The Hallucination Hurdle)</strong></p>
<p>If AI output isn’t accurate, adoption stalls and engineers waste time reviewing everything.</p>
<ul>
<li><strong>The confidence metric:</strong>&nbsp;Developers who experience fewer than 20% hallucinations are 2.5x more likely to merge code without reviewing it (24% vs 9% of others).</li>
<li><strong>The red zone:</strong>&nbsp;A massive 76% of developers fall into the “high hallucinations, low confidence” group. This means they use AI, but don’t trust the results, leading to manual review, delays, and limited ROI.</li>
<li><strong>Confidence and morale:</strong>&nbsp;High-confidence engineers are 1.3x more likely to say AI makes their job more enjoyable (46% vs.&nbsp;35% of those with low confidence).</li>
</ul>
<p><strong>3. Automated Review is the Quality Multiplier</strong></p>
<p>Speed alone doesn’t guarantee quality; automated review converts raw velocity into durable code quality.</p>
<ul>
<li><strong>Productivity &amp; quality synergy:</strong>&nbsp;When teams report “considerable” productivity gains, 70% also report better code quality — a 3.5x jump over stagnant teams.</li>
<li><strong>The AI review benefit:</strong>&nbsp;With AI review integrated, quality improvements soar to 81% for fast teams (compared to 55% for equally fast teams without review).</li>
<li><strong>Even without a speed boost</strong>, teams using AI review see 2x the quality gains (36% vs 17%). This continuous, opinionated review is the force-multiplier we need.</li>
</ul>
<p>In summary, AI tools are generating a quarter or more of our code. To close the trust gap and truly transform our development process, we must <strong>focus less on raw speed and more on AI tools that provide deep, automated contextual awareness and robust, continuous quality review</strong>. This creates a “confidence flywheel” that reinforces accuracy, quality, and trust.</p>



 ]]></description>
  <category>reading-notes</category>
  <category>opinions</category>
  <guid>https://ghif.github.io/posts/2025-11-13-state-of-ai-code-quality/</guid>
  <pubDate>Thu, 13 Nov 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Demystifying Text-to-Speech and Voice Cloning for Content Creation</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2025-11-09-demystifying-text-to-speech-and-voice-cloning/</link>
  <description><![CDATA[ 




<p>For a while now, I’ve wanted to create more educational content, both in writing and in audio-visual forms. So far, my written/static content is holding up pretty well, but when it comes to audio-visual, I’ve been stuck. I feel that inertia creeping in: recording myself speaking, doing the audio/video editing, and all that extra overhead.</p>
<p>May be I can stretch my creative comfort zone by sticking to what I’m comfortable at — writing — and then letting AI take care the audio-visual part. I picture an animated digital character that looks like me, speaking in my natural voice, all triggered by a simple textual script.</p>
<p>To make that vision real, some generative AI tools would be needed:</p>
<ul>
<li><strong>Text-to-speech:</strong> generate realistic speech from text.</li>
<li><strong>Voice cloning / conversion</strong>: make the speech sound like my voice.</li>
<li><strong>Visual character generation</strong>: create a visual character or avatar from text prompt.</li>
<li><strong>Audio-to-animation:</strong> animate a talking face in sync with the speech.</li>
</ul>
<p>These days, there are already many no-code AI tools available out there that can do these things. For examples, <a href="https://elevenlabs.io/">ElevenLabs</a> (for TTS and voice cloning), <a href="https://www.midjourney.com/home">MidJourney</a>, <a href="https://aistudio.google.com/models/gemini-2-5-flash-image">Nano Banana</a>, <a href="https://aistudio.google.com/models/veo-3">Veo</a> (for visual image or video generation), <a href="https://www.heygen.com/">HeyGen</a>, <a href="https://tools.dreamfaceapp.com/">Dreamface</a> (for face lip sync / animation). Many content creators have shown the way to do so.</p>
<p>Since I’m also curious about how the machinery works behind the scenes, not just using the tools, I want to explore the with-code approach (though I may try the full flow with no-code too). In this article, I’m going to start by delving into more details on the voice side (TTS and voice cloning). Let’s dive in.</p>
<section id="text-to-speech" class="level2">
<h2 class="anchored" data-anchor-id="text-to-speech">Text-to-Speech</h2>
<p>Have you ever had your phone read an article to you or heard audiobooks? That’s Text-to-Speech (TTS) in action, the tech that turns written text into spoken words. TTS has come a long way — from flat, robotic voices to ones that can whisper, pause, emphasize, or sound emotional. It’s everywhere now: voice assistants, screen readers, podcasts, navigation, etc.</p>
<p>TTS also has a long history, dating back to the early 20th century in the analog era. Here’s a brief overview of its evolution:</p>
<section id="early-beginnings-pre-1970s" class="level3">
<h3 class="anchored" data-anchor-id="early-beginnings-pre-1970s">Early beginnings (pre-1970s)</h3>
<p>Some of the earliest work was in analog / mechanical / signal-processing devices. For instance, <a href="https://www.youtube.com/watch?v=5hyI_dM5cGo">Bell Labs’ Voder</a> (Voice Operating Demonstrator) in the 1930s was a machine that could produce recognizable speech by manually controlling parameters. Later, devices like the <a href="https://archive.ll.mit.edu/publications/journal/pdf/vol03_no2/3.2.1.vocoder.pdf">vocoder</a> were developed for speech coding and synthesis.</p>
<p>In the 1950s - 60s, formant synthesis was introduced: modeling the human vocal tract and resonant frequencies (formants), then generating speech via parametric methods. While not very natural or expressive, it was very flexible and didn’t need huge memory or huge databases of recorded speech.</p>
</section>
<section id="rule-based-and-concatenative-approaches-1970s-1990s" class="level3">
<h3 class="anchored" data-anchor-id="rule-based-and-concatenative-approaches-1970s-1990s">Rule-based and concatenative approaches (1970s-1990s)</h3>
<p>As computers became more powerful, TTS system began to include linguistic rules: text analysis (tokenization, phonemes), prosody rules (intonation, duration, stress). An early example is Klatt’s speech synthesizer (KlattTalk), developed in the 1980s. DECtalk (1983-84) is another landmark: a commercial system that used rule-based (source-filter) methods, and could be customized in terms of speech rate, pitch, etc.</p>
<p>Over time, methods that concatenated actual recorded speech segments (diphones, units, larger segments) became popular. These used large speech databases. The idea is that recorded speech pieces have the natural prosody and timbre, so stitching them can give more natural speech. Early systems like those in the 1990s, and commercial systems (e.g., AT&amp;T Natural Voices) used these ideas.</p>
</section>
<section id="statistical-and-hybrid-methods-2000s" class="level3">
<h3 class="anchored" data-anchor-id="statistical-and-hybrid-methods-2000s">Statistical and hybrid methods (2000s)</h3>
<p>In the 2000s, TTS moved toward statistical modeling: Hidden Markov Models (HMMs) for acoustic modeling, duration prediction, etc. Furthermore, hybrid systems that combined unit selection (concatenative) with statistical models to smooth prosody, control variation, etc. These improved intelligibility and were more robust.</p>
<p>Tools like Festival (Univ of Edinburgh), Flite, FreeTTS, etc., made TTS more accessible for research and smaller applications. Also, datasets started to grow.</p>
</section>
<section id="neural-tts-era-2015-present" class="level3">
<h3 class="anchored" data-anchor-id="neural-tts-era-2015-present">Neural TTS era (2015-present)</h3>
<p>A major breakthrough has come following the deep learning explosion. DeepMind’s WaveNet (2016) introduced raw-waveform autoregressive modeling, conditioned on linguistic features, and achieved naturalness significantly higher than earlier concatenative or parametric models. It could model multiple speakers and switch between them.</p>
<p>Systems like Tacotron, Tacotron2, Transformer-based TTS, FastSpeech, etc., simplified the pipeline by reducing manual feature engineering (text front-end, prosody models) and enabling better expressivity. Also improvements in speed/latency, allowing practical deployment.</p>
<p>More recently, TTS has seen diffusion-based models, neural vocoders, and focused work on controllable TTS: being able to adjust style, emotion, pitch, speaking rate etc. Surveys such as “Towards Controllable Speech Synthesis in the Era of Large Language Models” give a taxonomy of these methods. Another survey discusses the use of diffusion models (acoustic model, vocoder, end-to-end) like in “A Survey on Audio Diffusion Models: Text to Speech Synthesis”.</p>
</section>
</section>
<section id="modern-tts-pipeline" class="level2">
<h2 class="anchored" data-anchor-id="modern-tts-pipeline">Modern TTS Pipeline</h2>
<p>Modern TTS has evolved far beyond robotic-sounding voices of the past. Today, it can produce speech nearly indistinguishable from a human speaker. It typically involves 3 main stages:</p>
<ol type="1">
<li><strong>Text Analysis:</strong> The input text is normalized (e.g., numbers —&gt; words, punctuation —&gt; pauses) and converted into phonemes or linguistic representations.</li>
<li><strong>Acoustic Modeling:</strong> A neural model predicts mel-spectrograms — visual representations of sound frequency over time — which capture the tonal and rhythmic qualities of speech.</li>
<li><strong>Vocoder:</strong> Another neural model converts the spectrogram into a raw audio waveform.</li>
</ol>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-11-09-demystifying-text-to-speech-and-voice-cloning/media/tts-pipeline.png" class="img-fluid figure-img"></p>
<figcaption>Figure 1: Modern TTS Pipeline</figcaption>
</figure>
</div>
<p>Figure 1: Modern TTS Pipeline</p>
<p>By harnessing the power of neural networks, recent advancements have simplified the TTS pipeline into a 2-stage approach. Some methods skip the explicit acoustic modeling step altogether — letting deep vocoders generate waveforms directly from linguistic features such as WaveNet (<a href="https://arxiv.org/pdf/1609.03499">Oord et al., 2016</a>), Parallel WaveNet (<a href="https://arxiv.org/pdf/1711.10433">Oord et al., 2017</a>), DeepVoice 1 (<a href="https://arxiv.org/pdf/1702.07825">Arik et al., 2017</a>), DeepVoice 2 (<a href="https://arxiv.org/pdf/1705.08947">Arik et al., 2017</a>), or HiFi-GAN (<a href="https://arxiv.org/pdf/2010.05646">Kong et al.&nbsp;2020</a>). Others take a different route, using a single deep acoustic model to produce Mel-Spectrograms straight from text, as seen in systems like DeepVoice 3, TransformerTTS (<a href="https://arxiv.org/pdf/1809.08895">Li et al., 2019</a>), FastSpeech 1 (<a href="https://arxiv.org/pdf/1905.09263">Ren et al., 2019</a>), or FastSpeech 2 (<a href="https://arxiv.org/pdf/2006.04558">Ren et al., 2022</a>), which is a dominant approach recently. Another branch of work even attempts to solve the TTS task in an end-to-end manner such as WaveGrad 2 (<a href="https://arxiv.org/pdf/2106.09660">Chen et al., 2021</a>), FastDiff (<a href="https://arxiv.org/pdf/2204.09934">Huang et al., 2022</a>).</p>
<section id="mel-spectrograms" class="level3">
<h3 class="anchored" data-anchor-id="mel-spectrograms">Mel-Spectrograms</h3>
<p>When we talk about how machines “understand” sound, we need a way to translate raw audio — a constantly changing waveform — into something that algorithms can process and learn from. That’s where the <strong>Mel-spectrogram</strong> comes in.</p>
<p>A spectrogram is like a visual fingerprint of sound. It shows how the energy (or intensity) of different frequencies changes over time. Imagine slicing an audio signal into tiny chunks and measuring how much of each frequency is present in each slice. Stack those slices together, and we get a heatmap-like image — bright areas show strong frequencies, and darker ones show weaker ones.</p>
<p>The Mel-spectrogram takes this one step further by mimicking how humans actually perceive sound. Instead of spacing frequencies evenly, it uses the Mel scale, which is nonlinear — humans are more sensitive to pitch changes at lower frequencies than at higher ones. This means the Mel-spectrogram captures sound in a way that’s much closer to how we hear it.</p>
<p>In modern TTS, Mel-spectrograms are the key intermediate representation. After a model predicts the Mel-spectrogram from text, another component — often a neural vocoder like WaveNet, HiFi-GAN, or WaveRNN — takes that “image of sound” and turns in back into smooth, natural audio. We can think of it like this: the Mel-spectrogram is the blueprint, and the vocoder is the voice speaker that brings it to life.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-11-09-demystifying-text-to-speech-and-voice-cloning/media/waveform_plot.png" class="img-fluid figure-img"></p>
<figcaption>waveform_plot.png</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-11-09-demystifying-text-to-speech-and-voice-cloning/media/mel-spectrogram.png" class="img-fluid figure-img"></p>
<figcaption>Figure 2: An example of waveform and its mel-spectrogram representations</figcaption>
</figure>
</div>
<p>Figure 2: An example of waveform and its mel-spectrogram representations</p>
</section>
</section>
<section id="voice-cloning" class="level2">
<h2 class="anchored" data-anchor-id="voice-cloning">Voice Cloning</h2>
<p>Speech synthesis becomes even more fascinating and useful when it can mimic a real person’s unique voice. Imagine this: you record yourself speaking for a few seconds, then type any sentence you want — and boom, it’s spoken back in your own voice. Voice cloning / conversion is turning that into reality. It’s one of the cooler (and also ethically complex) branches of speech AI.</p>
<p>Voice cloning can be understood as an extension of TTS systems, where the synthesized voice is personalized to match a specific individual. Achieving this requires careful modification of the acoustic modeling or, possibly, vocoder components to capture the unique vocal characteristics of the target speaker. However, the definition of voice cloning itself has varied across research efforts. (<a href="https://arxiv.org/abs/2505.00579">Azzuni and Saddik, 2025</a>) proposed a more structured taxonomy that clarifies its various forms:</p>
<ol type="1">
<li><strong>Voice Cloning</strong>: Replicating a specific person’s voice using a TTS system</li>
<li><strong>Speaker Adaptation</strong>: Fine-tuning a pre-trained TTS model to replicate a specific user’s voice using limited data.</li>
<li><strong>Few-shot Voice Cloning</strong>: A special case of speaker adaptation, where the amount of data or reference audio is much smaller — typically from just a few seconds up to a maximum of 5 minutes.</li>
<li><strong>Zero-shot Voice Cloning:</strong> Adapting speaker’s voice without model finetuning at all, which is more challenging but highly desirable for real-world scalability and flexibility.</li>
</ol>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-11-09-demystifying-text-to-speech-and-voice-cloning/media/vc-taxonomoy.png" class="img-fluid figure-img"></p>
<figcaption>Figure 3: Taxonomy of Voice Cloning</figcaption>
</figure>
</div>
<p>Figure 3: Taxonomy of Voice Cloning</p>
</section>
<section id="building-a-simple-tts-with-voice-cloning" class="level2">
<h2 class="anchored" data-anchor-id="building-a-simple-tts-with-voice-cloning">Building a Simple TTS with Voice Cloning</h2>
<p>As part of my personal content creation toolkit, I want to build a lightweight TTS that can run locally on my machine or on a cheap cloud environment like Google Colab. To begin, I utilize <a href="https://cloud.google.com/text-to-speech/docs/chirp3-hd">Chirp 3</a> to generate speech directly from scripted text.</p>
<p>Chirp 3 represents Google’s recent generation of its neural speech models, offering both Speech-to-Text (ASR / transcription) and Text-to-Speech (TTS) with high-definition voice synthesis. It is available through Google Cloud / Vertex AI, supporting a large number of pre-defined voices (~248 distinct voices) across many languages (~31 locales), each crafted to deliver realism and emotional expressiveness.</p>
<p>Although technical documentation about Chirp’s underlying architecture remains limited, it likely to follow the neutral TTS pipeline described earlier, with some modifications or advancements.</p>
<p>Chirp 3 also provides voice cloning via its <a href="https://cloud.google.com/text-to-speech/docs/chirp3-instant-custom-voice">Instant Custom Voice</a> feature. However, since this capability is still in private preview, I opted to use another solution for the voice cloning process borrowed from an open-source model.</p>
<p>The overall pipeline can be illustrated as follows:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-11-09-demystifying-text-to-speech-and-voice-cloning/media/tts-vc.png" class="img-fluid figure-img"></p>
<figcaption>Figure 4: TTS with Voice Cloning</figcaption>
</figure>
</div>
<p>Figure 4: TTS with Voice Cloning</p>
<p>In this setup, the voice cloning module functions as a post-processing step, where it receives two audio inputs (source and reference speech) and generates a target speech output that conveys the linguistic content of the source while adopting the vocal characteristics of the reference.</p>
<section id="applying-chirp-3-tts" class="level3">
<h3 class="anchored" data-anchor-id="applying-chirp-3-tts">Applying Chirp 3 TTS</h3>
<p>Using the Chirp 3 API to implement TTS is quite straightforward with the Python client libraries, as shown in the <a href="https://docs.cloud.google.com/text-to-speech/docs/chirp3-hd">documentation</a>. Below is a sample function demonstrating how to use the Google Cloud Text-to-Speech API to generate an MP3 audio file given the input text, a voice character and language code. The generated audio is referred as the source speech, as illustrated in Figure 4.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> google.cloud <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> texttospeech_v1beta1 <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> texttospeech</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> google.api_core.client_options <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> ClientOptions</span>
<span id="cb1-3"></span>
<span id="cb1-4">...</span>
<span id="cb1-5"></span>
<span id="cb1-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_speech(text, voice<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Leda"</span>, language_code<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"id-ID"</span>):</span>
<span id="cb1-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Generate speech using Google's Chirp 3 Text-to-Speech API</span></span>
<span id="cb1-9"></span>
<span id="cb1-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb1-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        text (str): The text to be synthesized.</span></span>
<span id="cb1-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        voice (str): The name of the voice to use (e.g., "Leda", "Charon").</span></span>
<span id="cb1-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        language_code (str): The language code for the voice (e.g., "id-ID" for Indonesian, "en-US" for English).</span></span>
<span id="cb1-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns:</span></span>
<span id="cb1-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        audio_content (bytes): The synthesized speech audio in MP3 format.</span></span>
<span id="cb1-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        temporary_file_name (str): The filename of the synthesized audio.</span></span>
<span id="cb1-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        </span></span>
<span id="cb1-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb1-19">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Obtain default credentials and create an authorized session</span></span>
<span id="cb1-20">    voice_name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>language_code<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">-Chirp3-HD-</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>voice<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb1-21">    voice <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> texttospeech.VoiceSelectionParams(</span>
<span id="cb1-22">        name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>voice_name,</span>
<span id="cb1-23">        language_code<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>language_code,</span>
<span id="cb1-24">    )</span>
<span id="cb1-25"></span>
<span id="cb1-26">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Initialize the TTS client with the specified API endpoint</span></span>
<span id="cb1-27">    client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> texttospeech.TextToSpeechClient(</span>
<span id="cb1-28">        client_options<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ClientOptions(api_endpoint<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>API_ENDPOINT)</span>
<span id="cb1-29">    )</span>
<span id="cb1-30"></span>
<span id="cb1-31">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Perform the text-to-speech request on the text input with the selected</span></span>
<span id="cb1-32">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># voice parameters and audio file type</span></span>
<span id="cb1-33">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.synthesize_speech(</span>
<span id="cb1-34">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>texttospeech.SynthesisInput(text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>text),</span>
<span id="cb1-35">        voice<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>voice,</span>
<span id="cb1-36">        audio_config<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>texttospeech.AudioConfig(</span>
<span id="cb1-37">            audio_encoding<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>texttospeech.AudioEncoding.MP3</span>
<span id="cb1-38">        ),</span>
<span id="cb1-39">    )</span>
<span id="cb1-40"></span>
<span id="cb1-41">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Create a temporary MP3 file</span></span>
<span id="cb1-42">    audio_content <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> response.audio_content</span>
<span id="cb1-43"></span>
<span id="cb1-44">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> tempfile.NamedTemporaryFile(delete<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, suffix<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">".mp3"</span>) <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> tmp_file:</span>
<span id="cb1-45">        tmp_file.write(audio_content)</span>
<span id="cb1-46">    </span>
<span id="cb1-47">    schedule_cleanup(tmp_file.name)</span>
<span id="cb1-48">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> audio_content, tmp_file.name</span></code></pre></div></div>
</section>
<section id="zero-shot-voice-cloning" class="level3">
<h3 class="anchored" data-anchor-id="zero-shot-voice-cloning">Zero-shot Voice Cloning</h3>
<p>Since I don’t yet have access to the voice cloning feature of Chirp 3, I explored other solutions—particularly in the open-source space. It would be ideal to leverage zero-shot voice cloning, since it requires no extra training or fine-tuning.</p>
<p>I ended up trying two open-source projects for voice cloning: <a href="https://github.com/myshell-ai/OpenVoice">OpenVoice</a> and <a href="https://github.com/resemble-ai/chatterbox">Chatterbox</a>. After several trial generating target speeches, I found that Chatterbox produces more personalized and natural results. It turns out many of Chatterbox’s voice-cloning components are borrowed or derived from <a href="https://github.com/FunAudioLLM/CosyVoice">CosyVoice</a>, another cool open-source voice cloning tool.</p>
<p>To understand zero-shot voice cloning, think of it as disentangling “what is said” (<strong>content</strong>) from “how it’s said” (<strong>style</strong>). The content represents the <em>what —</em> the main message we want to deliver — while style captures the <em>how —</em> the prosody (pitch, stress, rhythm, loudness, pause duration, etc) and timbre/color/identity of the voice. Voice cloning succeeds when it preserves the content from the source speech and the style from a reference speech, then merge both to generate a new target voice that reflects the reference’s style speaking the source’s content.</p>
<p>In CosyVoice, the “what to say” aspect of speech is captured through <strong>S3 (Supervised Semantic Speech) tokenization</strong>. This module, built on an ASR encoder, transforms raw audio into semantic tokens that encapsulate phonetic and linguistic information — essentially, the verbal content of the utterance.</p>
<p>The “how-to-stay” part is constructed from a blend of several complementary representations of the reference speech:</p>
<ul>
<li><strong>X-vector embeddings:</strong> These are compact numerical vectors derived from raw audio that capture a speaker’s unique vocal identity (<a href="https://www.danielpovey.com/files/2018_icassp_xvectors.pdf">Snyder et al., 2018</a>). They encode speaker-specific features like vocal tract characteristics, speaking style, accent, and other prosodic elements that make each person’s voice distinctive.</li>
<li><strong>Reference spectrogram: H</strong>igh-resolution mel-spectrograms that preserve fine-grained acoustic details of the target speaker’s voice, including pitch contours and format structures that define timbre and tonal quality.</li>
</ul>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-11-09-demystifying-text-to-speech-and-voice-cloning/media/voice-cloner-cosyvoice.png" class="img-fluid figure-img"></p>
<figcaption>Figure 5: Zero-shot voice cloning in CosyVoice / Chatterbox</figcaption>
</figure>
</div>
<p>Figure 5: Zero-shot voice cloning in CosyVoice / Chatterbox</p>
<p>Once both the content and style representations are prepared, <strong>Conditional Flow Matching (CFM)</strong> is used to synthesize the target spectrogram (<a href="https://arxiv.org/pdf/2210.02747">Lipman et al.&nbsp;2023</a>, <a href="https://arxiv.org/pdf/2309.03199">Mehta et al.&nbsp;2024</a>). The X-vectors and reference mel-spectrograms act as conditioning signals, guiding the model on whose voice to generate and how it should sound. During generation, the CFM model aligns these signals to produce speech that naturally mimics the target speaker’s tone and rhythm (see Figure 5). Finally, a <strong>vocoder</strong> converts the resulting mel-spectrogram into a high-quality waveform — examples of the vocoder are such as HiFi-GAN (<a href="https://arxiv.org/pdf/2010.05646">Kong et al., 2020</a>) or HiFTNet (<a href="https://arxiv.org/pdf/2309.09493">Li et al., 2023</a>). Figure 5 illustrates the complete process of the voice cloning implemented in CosyVoice or Chatterbox.</p>
<p>The following Python class implements the voice cloning process:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pathlib <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Path</span>
<span id="cb2-2"></span>
<span id="cb2-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> librosa</span>
<span id="cb2-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch</span>
<span id="cb2-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> huggingface_hub <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> hf_hub_download</span>
<span id="cb2-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> safetensors.torch <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_file</span>
<span id="cb2-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> models.s3gen <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> S3Gen</span>
<span id="cb2-8"></span>
<span id="cb2-9">S3_SR <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16_000</span></span>
<span id="cb2-10">S3GEN_SR <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">24_000</span></span>
<span id="cb2-11"></span>
<span id="cb2-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Hugging Face repo ID for the pre-trained models</span></span>
<span id="cb2-13">REPO_ID <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ResembleAI/chatterbox"</span></span>
<span id="cb2-14"></span>
<span id="cb2-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> VoiceCloner:</span>
<span id="cb2-16">    ENC_COND_LEN <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> S3_SR</span>
<span id="cb2-17">    DEC_COND_LEN <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> S3GEN_SR</span>
<span id="cb2-18"></span>
<span id="cb2-19">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(</span>
<span id="cb2-20">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>,</span>
<span id="cb2-21">        s3gen: S3Gen,</span>
<span id="cb2-22">        device: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>,</span>
<span id="cb2-23">        ref_dict: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>,</span>
<span id="cb2-24">    ):</span>
<span id="cb2-25">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.sr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> S3GEN_SR</span>
<span id="cb2-26">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.s3gen <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> s3gen</span>
<span id="cb2-27">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.device <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> device</span>
<span id="cb2-28">        </span>
<span id="cb2-29">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> ref_dict <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb2-30">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ref_dict <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb2-31">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb2-32">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ref_dict <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb2-33">                k: v.to(device) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> torch.is_tensor(v) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> v</span>
<span id="cb2-34">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k, v <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ref_dict.items()</span>
<span id="cb2-35">            }</span>
<span id="cb2-36"></span>
<span id="cb2-37">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@classmethod</span></span>
<span id="cb2-38">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> from_local(cls, ckpt_dir, device) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'VoiceCloner'</span>:</span>
<span id="cb2-39">        ckpt_dir <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Path(ckpt_dir)</span>
<span id="cb2-40">        </span>
<span id="cb2-41">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Always load to CPU first for non-CUDA devices to handle CUDA-saved models</span></span>
<span id="cb2-42">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> device <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cpu"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mps"</span>]:</span>
<span id="cb2-43">            map_location <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.device(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'cpu'</span>)</span>
<span id="cb2-44">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb2-45">            map_location <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb2-46">            </span>
<span id="cb2-47">        ref_dict <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb2-48">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (builtin_voice <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:=</span> ckpt_dir <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"conds.pt"</span>).exists():</span>
<span id="cb2-49">            states <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.load(builtin_voice, map_location<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>map_location)</span>
<span id="cb2-50">            ref_dict <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> states[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'gen'</span>]</span>
<span id="cb2-51"></span>
<span id="cb2-52">        s3gen <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> S3Gen()</span>
<span id="cb2-53">        s3gen.load_state_dict(</span>
<span id="cb2-54">            load_file(ckpt_dir <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"s3gen.safetensors"</span>), strict<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span></span>
<span id="cb2-55">        )</span>
<span id="cb2-56">        s3gen.to(device).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span>()</span>
<span id="cb2-57"></span>
<span id="cb2-58">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> cls(s3gen, device, ref_dict<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ref_dict)</span>
<span id="cb2-59"></span>
<span id="cb2-60">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@classmethod</span></span>
<span id="cb2-61">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> from_pretrained(cls, device) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'VoiceCloner'</span>:</span>
<span id="cb2-62">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Check if MPS is available on macOS</span></span>
<span id="cb2-63">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> device <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mps"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> torch.backends.mps.is_available():</span>
<span id="cb2-64">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> torch.backends.mps.is_built():</span>
<span id="cb2-65">                <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MPS not available because the current PyTorch install was not built with MPS enabled."</span>)</span>
<span id="cb2-66">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb2-67">                <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MPS not available because the current MacOS version is not 12.3+ and/or you do not have an MPS-enabled device on this machine."</span>)</span>
<span id="cb2-68">            device <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cpu"</span></span>
<span id="cb2-69">            </span>
<span id="cb2-70">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> fpath <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"s3gen.safetensors"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"conds.pt"</span>]:</span>
<span id="cb2-71">            local_path <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hf_hub_download(repo_id<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>REPO_ID, filename<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>fpath)</span>
<span id="cb2-72"></span>
<span id="cb2-73">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> cls.from_local(Path(local_path).parent, device)</span>
<span id="cb2-74"></span>
<span id="cb2-75">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> set_target_voice(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, wav_fpath):</span>
<span id="cb2-76">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Load reference wav</span></span>
<span id="cb2-77">        s3gen_ref_wav, _sr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> librosa.load(wav_fpath, sr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>S3GEN_SR)</span>
<span id="cb2-78">        s3gen_ref_wav <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> s3gen_ref_wav[:<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.DEC_COND_LEN]</span>
<span id="cb2-79">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ref_dict <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.s3gen.embed_ref(s3gen_ref_wav, S3GEN_SR, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.device)</span>
<span id="cb2-80"></span>
<span id="cb2-81">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate(</span>
<span id="cb2-82">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>,</span>
<span id="cb2-83">        audio,</span>
<span id="cb2-84">        target_voice_path<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>,</span>
<span id="cb2-85">    ):</span>
<span id="cb2-86">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> target_voice_path:</span>
<span id="cb2-87">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.set_target_voice(target_voice_path)</span>
<span id="cb2-88">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb2-89">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ref_dict <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Please `prepare_conditionals` first or specify `target_voice_path`"</span></span>
<span id="cb2-90"></span>
<span id="cb2-91">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> torch.inference_mode():</span>
<span id="cb2-92">            audio_16, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> librosa.load(audio, sr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>S3_SR)</span>
<span id="cb2-93">            audio_16 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.from_numpy(audio_16).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>().to(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.device)[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, ]</span>
<span id="cb2-94"></span>
<span id="cb2-95">            s3_tokens, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.s3gen.tokenizer(audio_16)</span>
<span id="cb2-96">            wav, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.s3gen.inference(</span>
<span id="cb2-97">                speech_tokens<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>s3_tokens,</span>
<span id="cb2-98">                ref_dict<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ref_dict,</span>
<span id="cb2-99">            )</span>
<span id="cb2-100">            wav <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> wav.squeeze(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>).detach().cpu().numpy()</span>
<span id="cb2-101">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.from_numpy(wav).unsqueeze(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span></code></pre></div></div>
<p>This class is a high-level wrapper that simplifies voice cloning by:</p>
<ul>
<li>Loading pre-trained models from <a href="https://huggingface.co/ResembleAI/chatterbox/tree/main">Hugging Face</a> provided by ResembleAI’s Chatterbox.</li>
<li>Converting any audio to match a target speaker’s voice.</li>
<li>Handling different audio sample rates automatically.</li>
<li>Managing device placement (CPU, GPU, MPS).</li>
</ul>
</section>
<section id="result-examples" class="level3">
<h3 class="anchored" data-anchor-id="result-examples">Result Examples</h3>
<p>To showcase the workflow, I built a simple prototype using Gradio as an interactive interface for generating both synthetic speech and its cloned version.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-11-09-demystifying-text-to-speech-and-voice-cloning/media/Screenshot_2025-11-09_at_06.42.31.png" class="img-fluid figure-img"></p>
<figcaption>Screenshot 2025-11-09 at 06.42.31.png</figcaption>
</figure>
</div>
<p>The following is the result example from the prototype.</p>
<p><strong>Source Text (Bahasa Indonesia)</strong></p>
<blockquote class="blockquote">
<p>Halo semuanya. Selamat datang di demo text-to-speech menggunakan Google Chirp 3 dan voice cloning menggunakan Chatterbox. Silakan coba dengan teks buatanmu sendiri!</p>
</blockquote>
<p><strong>Source Speech (Chirp 3)</strong></p>
<p><a href="media/source_speech.mp3">source_speech.mp3</a></p>
<p><strong>Reference Speech</strong></p>
<p><a href="media/ref_speech.wav">ref_speech.wav</a></p>
<p><strong>Target Speech (Voice Cloning with Chatterbox)</strong></p>
<p><a href="media/target_speech.wav">target_speech.wav</a></p>
<p>As illustrated above, the system first synthesizes a base voice from the Google’s Chirp 3 TTS. Then, using the reference audio, the voice cloner reproduces the same utterance in the voice of the reference speaker — capturing both their tone and speaking style while retaining the original content.</p>
<p>All source codes, including scripts for inference and UI prototype, are available in my Github repository: <a href="https://github.com/ghif/tts-vc">https://github.com/ghif/tts-vc</a>.</p>


</section>
</section>

 ]]></description>
  <category>generative-ai</category>
  <category>implementation</category>
  <guid>https://ghif.github.io/posts/2025-11-09-demystifying-text-to-speech-and-voice-cloning/</guid>
  <pubDate>Sun, 09 Nov 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Mengapa Model Bahasa AI Berhalusinasi?</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2025-09-11-why-language-models-hallucinate/</link>
  <description><![CDATA[ 




<p>Bagi para pengguna Large Language Models (LLM), mungkin hampir semua pernah mengalaminya: bertanya kepada LLM pertanyaan yang tampaknya sederhana, lalu menerima jawaban yang terdengar masuk akal tetapi ternyata salah. Fenomena ini dikenal dengan <strong>halusinasi,</strong> yang bahkan masih terdapat pada LLM tercanggih saat ini. Berikut contoh pertanyaan sederhana kepada ChatGPT dengan model GPT-5, yang tentu saja memberikan jawaban yang salah!</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-09-11-why-language-models-hallucinate/media/Screenshot_2025-09-11_at_08.42.21.png" class="img-fluid figure-img"></p>
<figcaption>Screenshot 2025-09-11 at 08.42.21.png</figcaption>
</figure>
</div>
<p>Sebuah paper dari OpenAI dan Georgia Tech <a href="https://cdn.openai.com/pdf/d04913be-3f6f-4d2b-b283-ff432ef4aaa5/why-language-models-hallucinate.pdf">(Kalai et al.&nbsp;2025) “Why Language Models Hallucinate”</a> membahas secara mendalam akar masalah halusinasi ini. Temuan utama dari penelitian teoretis ini yaitu LLM berhalusinasi karena prosedur pelatihan dan evaluasi lebih memberikan insentif terhadap jawaban tebakan dibandingkan pengakuan ketidakpastian atau ketidaktahuan.</p>
<p>Bayangkan kita sedang menjawab soal pilihan ganda. Ketika kita tidak tahu jawabannya, kita bisa melakukan 2 pilihan: 1) memberi jawaban tebakan dan berharap benar, atau 2) tidak menjawab dan mengakui bahwa kita tidak tahu. Metode pelatihan LLM saat ini memberikan insentif lebih banyak ke pilihan 1 dibandingkan 2 — mungkin bahasa mudahnya, prosedur pelatihannya lebih menghargai AI untuk “sok tahu” dibandingkan mengatakan “saya tidak tahu” 🙂</p>
<p>Mari kita selami lebih dalam.</p>
<section id="dua-aspek-penyebab-halusinasi" class="level2">
<h2 class="anchored" data-anchor-id="dua-aspek-penyebab-halusinasi">Dua Aspek Penyebab Halusinasi</h2>
<p>Studi tersebut menguraikan penyebab halusinasi menjadi 2 tahap utama dalam paradigma pelatihan modern LLM: pra-pelatihan (pre-training) dan pasca-pelatihan (post-training).</p>
<section id="kekurangan-yang-disebabkan-pada-pra-pelatihan" class="level3">
<h3 class="anchored" data-anchor-id="kekurangan-yang-disebabkan-pada-pra-pelatihan">1. Kekurangan yang disebabkan pada pra-pelatihan</h3>
<p>Pra-pelatihan merupakan suatu prosedur melatih LLM yang pada umumnya dilakukan dengan cara self-supervised learning dengan memprediksi huruf kata/kalimat/token berikutnya. Prosedur tersebut menghasilkan model dasar bahasa yang mengestimasi statistik distribusi probabilitas kemunculan token berikutnya dari token-token sebelumnya.</p>
<p>Pendekatan tersebut yang sebenarnya menjadi standar baku pada <em>machine learning</em> (meminimalkan <em>cross entropy</em> antara data dan hasil prediksi dari model), ternyata memiliki efek samping. Bahkan jika data latih yang digunakan itu bebas dari kesalahan, tujuan / objektif pelatihan standar tersebut masih berpeluang menimbulkan efek samping halusinasi.</p>
<p>Argumen tersebut diformulasikan secara formal dengan mengaitkannya kepada konsep model klasifikasi biner yang diterapkan untuk mengidentifikasi validitas output dari model — dinamakan dengan <strong>(Is-This-Valid)</strong> <strong>IIV classifier</strong>. Secara intuitif, IIV classifier mengerjakan hal-hal seperti di bawah ini:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-09-11-why-language-models-hallucinate/media/Screenshot_2025-09-11_at_16.00.30.png" class="img-fluid figure-img"></p>
<figcaption>Screenshot 2025-09-11 at 16.00.30.png</figcaption>
</figure>
</div>
<p>Menghasilkan IIV classifier dengan error yang kecil bukan perkara yang mudah. Ada kasus yang cukup jelas polanya untuk diklasifikasikan, ada pula kasus-kasus lain yang sulit dibedakan validitasnya karena tidak menunjukkan pola tertentu atau pola yang ambigu. Sebagai contoh, kasus “Birthdays” (no pattern) tidak memiliki pola yang benar-benar membedakan apakah sampel teks output masuk dalam kategori valid vs tidak valid. Kasus semacam ini merupakan fakta arbitrer (<em>arbitrary facts</em>) dimana keterhubungan antar sampel nya memiliki pola yang cenderung acak. Distribusi data / teks bertipe fakta arbitrer inilah yang menjadi salah satu sumber kesalahan IIV classifier yang berasosiasi dengan halusinasi, walaupun informasi pada teks bersifat faktual.</p>
<p>Pada intinya, semakin besar error yang dihasilkan IIV classifier, semakin besar pula kemungkinan LLM berhalusinasi. Pada studi tersebut, ditemukan hubungan yang menarik antara skor kesalahan IIV classifier dengan kesalahan dari hasil luaran LLM. Misalkan <img src="https://latex.codecogs.com/png.latex?%5Cepsilon_%7Bc%7D%20%5Cin%20%5Cmathbb%7BR%7D"> merupakan skor kesalahan (<em>error rate</em>) yang dihasilkan oleh IIV classifier dan <img src="https://latex.codecogs.com/png.latex?%5Cepsilon_g%20%5Cin%20%5Cmathbb%7BR%7D"> merupakan skor kesalahan dari luaran LLM, teridentifikasi hubungan matematis yang menarik:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cepsilon_g%20%5Cgtrsim%202%20%5Ccdot%20%5Cepsilon_c%0A"></p>
<p>Kajian teoritis mengindikasikan bahwa diperlukan suatu prosedur yang dapat menurunkan <img src="https://latex.codecogs.com/png.latex?%5Cepsilon_c"> baik secara langsung maupun tidak langsung untuk mengurangi halusinasi. Dapat disimpulkan pula bahwa apapun yang berkontribusi terhadap tingginya nilai <img src="https://latex.codecogs.com/png.latex?%5Cepsilon_c"> selain fakta arbitrer juga merupakan penyebab halusinasi, seperti performa model IIV yang buruk (<em>poor model</em>), kompleksitas komputasi, pergeseran distribusi data latih vs data lapangan, dan GIGO (Garbage In, Garbage Out).</p>
</section>
<section id="kekurangan-yang-disebabkan-pada-pasca-pelatihan" class="level3">
<h3 class="anchored" data-anchor-id="kekurangan-yang-disebabkan-pada-pasca-pelatihan">2. Kekurangan yang disebabkan pada pasca-pelatihan</h3>
<p>Pasca-pelatihan merupakan suatu prosedur pelatihan untuk menyempurnakan model dasar, seperti Reinforcement Learning from Human Feedback (RLHF), LoRA, dan lain-lain, agar LLM lebih fasih untuk menjawab instruksi yang spesifik pada domain tertentu, dan juga mengurangi halusinasi. Walaupun demikian, halusinasi masih tetap bertahan setelah proses pasca-pelatihan.</p>
<p>Studi tersebut mengklaim bahwa penyebab utamanya berasal dari aspek sosio-teknikal, terutama bagaimana mekanisme evaluasi dan pemberian umpan balik (<em>feedback</em>) selama fase pasca-latih LLM. Mayoritas <em>benchmark</em> untuk LLM saat ini (GPQA, MMLU-Pro, IFEval, Omni-MATH, BBH, MATH, MuSR, SWE-bench, HLE) mencerminkan seperti kita menjawab soal ujian yang tidak memberikan penalti untuk jawaban tebakan atau asal-asalan. Misalkan terdapat model A yang selalu jujur dan mengakui ketidakpastian atau ketidaktahuan dan model B selalu menebak jawaban saat tidak yakin. Mekanisme benchmark saat ini akan memberikan insentif lebih untuk model B.</p>
<p>Beberapa usulan untuk memitigasi halusinasi melalui pasca-pelatihan yaitu:</p>
<ul>
<li><strong>Memberikan target kepercayaan secara eksplisit (<em>explicit confidence target</em>),</strong> seperti menambahkan pernyataan: “Jawab hanya jika kamu yakin dengan nilai ambang kepercayaan &gt; t, karena kesalahan akan dihukum sebesar t / (t -1)”. Dengan ambang batas kepercayaan ini, jawaban yang optimal adalah hanya memberikan respon jika probabilitas kebenarannya melebihi ambang betas. Di bawah itu, LLM akan menjawab “saya tidak tahu”.</li>
<li><strong>Transparansi</strong>: memberikan spesifikasi ambang kepercayaan secara eksplisit pada instruksi.</li>
<li><strong>Integrasi ke Benchmark</strong>: memasukkan unsur <em>explicit confidence target</em> ini pada perangkat evaluasi / benchmark yang sudah lazim digunakan.</li>
</ul>
</section>
</section>
<section id="kesimpulan" class="level2">
<h2 class="anchored" data-anchor-id="kesimpulan">Kesimpulan</h2>
<p>Paper ini cukup membuka tabir halusinasi pada model bahasa AI, mulai dari asal-usulnya pada tahap pra-pelatihan dan mengapa tetap bertahan pada tahap pasca-pelatihan. Pada fase pra-pelatihan, halusinasi berkaitan erat dengan kesalahan klasifikasi apakah teks luaran LLM valid atau tidak valid. Pada fase pasca-pelatihan, halusinasi seperti “terpelihara” dengan prosedur sosio-teknikal dalam melakukan evaluasi yang memberikan penghargaan yang lebih terhadap model AI yang <em>over-confident</em> dibandingkan yang mengakui ketidaktahuan.</p>
<p>Sepertinya diperlukan kajian mendalam ke depan agar metode <em>machine learning</em> tidak hanya untuk membuat model AI menjadi pintar, namun juga bijak.</p>


</section>

 ]]></description>
  <category>generative-ai</category>
  <category>research-notes</category>
  <guid>https://ghif.github.io/posts/2025-09-11-why-language-models-hallucinate/</guid>
  <pubDate>Thu, 11 Sep 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>5 Hal yang Dipelajari dari Google I/O Connect Shanghai 2025</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2025-08-25-google-io-connect-shanghai-2025/</link>
  <description><![CDATA[ 




<p>Tahun ini saya berkesempatan menghadiri acara Google I/O Connect (<a href="https://ioconnectchina.googlecnapps.cn/intl/en_cn/">https://ioconnectchina.googlecnapps.cn/intl/en_cn/</a>) yang diadakan di Shanghai, China pada tanggal 13 - 14 Agustus 2025. Acara ini merupakan perpanjangan dari Google I/O (konferensi tahunan yang diadakan Google) yang memperkenalkan dan berbagi pengetahuan mengenai teknologi terkini yang dikembangkan Google.</p>
<p>Ada 4 tema besar yang dibahas pada Google I/O Connect kali ini:</p>
<ul>
<li><strong>AI</strong>: Pembahasan tentang pengembangan agentic AI dengan Agent Development Kit (ADK), small language models, JAX ecosystem untuk membangun model AI, dan sebagainya.</li>
<li><strong>Web</strong>: Beberapa pengkinian tentang fitur-fitur/<em>extensions</em> di Chrome untuk membantu pengembang, client-side AI, Firebase App Hosting, dan sebagainya.</li>
<li><strong>Android</strong>: Pengkinian tentang Jetpack Compose, Kotlin Multiplatform (KMP), Gemini Nano pada Android untuk on-device AI, dan sebagainya.</li>
<li><strong>Cloud</strong>: Pembahasan topik-topik seputar membangun dan menyebarkan (deploy) AI agents pada GCP, media generatif (Imagen, Veo) pada Vertex AI, Cloud Assist, pengkinian tentang Firebase, Flutter, dan Go.</li>
</ul>
<p>Terlihat bahwa tema AI mendominasi Google I/O Connect tahun ini – tema-tema non-AI pun banyak yang berkenaan dengan AI. Sebagai Google Developer Experts (GDE) pada kategori AI, saya jadi agak bingung topik apa saja yang mesti mendapatkan perhatian lebih, karena semuanya menarik 🙂</p>
<p>Terdapat 3 tipe sesi yang diselenggarakan: presentasi <em>keynote</em> ,<em>workshop</em>, dan <em>tech demo</em>, yang dibawakan oleh para ahli dari Google dan juga para pelaku teknologi lain yang bekerjasama dengan Google. Selama 2 hari penuh mengikuti Google I/O Connect, ada 5 hal yang menarik bagi saya untuk dipelajari lebih lanjut:</p>
<section id="yang-terbaru-pada-gemmaverse" class="level2">
<h2 class="anchored" data-anchor-id="yang-terbaru-pada-gemmaverse">1. Yang terbaru pada Gemmaverse</h2>
<p>Gemma merupakan sekumpulan model <em>lightweight</em> AI open-source yang dikembangkan dengan teknologi yang sama dengan Gemini namun dengan ukuran dan kapasitas yang lebih sederhana. Versi terbaru dari Gemma saat ini yaitu <a href="https://deepmind.google/models/gemma/gemma-3/">Gemma 3</a> yang terdiri dari beberapa jenis model: 1B, 4B, 12B, dan 27B, dengan peningkatan performa yang signifikan dibandingkan Gemma 2.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-08-25-google-io-connect-shanghai-2025/media/gemmaverse.png" class="img-fluid figure-img"></p>
<figcaption>gemmaverse.png</figcaption>
</figure>
</div>
<p>Terdapat pula beberapa varian dari Gemma yang di <em>post-trained</em> / <em>fine-tuned</em> pada domain tertentu yang menghasilkan <em>special-purpose models</em>:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th><strong>CodeGemma</strong></th>
<th>Model untuk membantu pekerjaan koding / pemrograman.</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>PaliGemma</strong></td>
<td><em>Vision Language Model</em> (VLM) untuk pengolahan dan analisis citra.</td>
</tr>
<tr class="even">
<td><strong>RecurrentGemma</strong></td>
<td>Model bahasa berdasarkan arsitektur <em>recurrent network</em> <a href="https://arxiv.org/pdf/2402.19427">Griffin</a>.</td>
</tr>
<tr class="odd">
<td><strong>ShieldGemma</strong></td>
<td>Model yang berfungsi sebagai <em>guardrail</em> / moderasi konten untuk mengevaluasi keamanan atau kepatutan dari suatu konten/</td>
</tr>
<tr class="even">
<td><strong>DataGemma</strong></td>
<td>Model sebagai alat bantu riset yang dilengkapi konteks berbagai data statistik dari repositori Data Commons.</td>
</tr>
<tr class="odd">
<td><strong>MedGemma</strong></td>
<td>Model multimodal berbasis Gemma 3 yang dilatih dengan teks dan citra medis untuk mengakselerasi pengembangan aplikasi AI di bidang kesehatan.</td>
</tr>
</tbody>
</table>
<p>Kontribusi dari komunitas terhadap Gemma juga semakin berkembang. Telah banyak aplikasi berbasis Gemma yang dikembangkan untuk berbagai <em>use cases</em>, baik di <a href="https://deepmind.google/models/gemma/gemmaverse/">sektor publik maupun privat</a>. Berbagai varian dari Gemma yang dikembangkan komunitas juga semakin banyak, seperti yang dapat ditemui di <a href="https://huggingface.co/models?sort=trending&amp;search=gemma">Hugging Face</a>.</p>
</section>
<section id="small-language-models-dengan-google-ai-edge-keynote" class="level2">
<h2 class="anchored" data-anchor-id="small-language-models-dengan-google-ai-edge-keynote">2. Small Language Models dengan Google AI Edge (keynote)</h2>
<p>Saat ini cara yang paling mudah untuk memanfaatkan model AI adalah melalui API Cloud. Namun ada kalanya kita membutuhkan solusi AI on-device dengan ukuran model lebih kecil (<em>small foundation models</em>) untuk menjaga privasi data dan kebutuhan mode offline / tanpa koneksi internet.</p>
<p>Sesi ini menjelaskan tentang <a href="https://ai.google.dev/edge">Google AI Edge</a>, sekumpulan <em>tech stacks</em> yang dapat dimanfaatkan untuk merealisasikan deployment AI on-device. Framework utama yang digunakan adalah <a href="https://ai.google.dev/edge/mediapipe/solutions/guide">MediaPipe</a> dan <a href="https://ai.google.dev/edge/litert">LiteRT</a> (sebelumnya bernama TFLite). MediaPipe menyediakan low-code API untuk memudahkan pengaturan inferensi berbagai model AI <em>off-the-shelves</em>. LiteRT merupakan lingkungan <em>runtime</em> berkinerja tinggi untuk menjalankan inferensi model AI pada <em>device</em> tujuan secara efisien.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-08-25-google-io-connect-shanghai-2025/media/slm.png" class="img-fluid figure-img"></p>
<figcaption>slm.png</figcaption>
</figure>
</div>
<p>Google AI Edge memungkinkan pula untuk menginstalasi <em>small language model (SLM)</em> <a href="https://developers.googleblog.com/en/gemma-3-on-mobile-and-web-with-google-ai-edge/">Gemma 3</a> secara <em>on-device</em>. Untuk mengimplementasikan Retrieval Augmented Generation (RAG) agar SLM dikondisikan dengan informasi atau konteks tertentu, disediakan juga <a href="https://github.com/google-ai-edge/ai-edge-apis/tree/main/local_agents/rag">RAG SDK</a> sebagai bagian dari Google AI Edge. Saat ini kita juga dapat memanfaatkan <a href="https://developers.googleblog.com/en/introducing-gemma-3n-developer-guide/">Gemma 3n</a> sebagai pilihan SLM yang lebih efektif dan teroptimalkan untuk <em>mobile device</em>.</p>
<p>Dengan terus berkembangnya pilihan dan kemampuan dari berbagai SLM beserta ekosistem dan <em>tools</em> pendukungnya, saat ini merupakan waktu yang tepat untuk membangun berbagai aplikasi berbasis AI on-device tanpa ketergantungan pada koneksi internet.</p>
</section>
<section id="membangun-model-transformers-dengan-jax-workshop" class="level2">
<h2 class="anchored" data-anchor-id="membangun-model-transformers-dengan-jax-workshop">3. Membangun model Transformers dengan JAX (workshop)</h2>
<p>Sesi workshop ini dibawakan oleh Wei Wei, Developer Advocate dari Google, yang menjelaskan secara terperinci implementasi arsitektur dan pelatihan model Transformer dari nol dengan menggunakan <a href="https://docs.jax.dev/en/latest/">JAX</a> dan <a href="https://flax.readthedocs.io/en/latest/nnx_basics.html">Flax NNX</a>. JAX merupakan sebuah <em>framework</em> <em>open-source</em> berkinerja tinggi untuk riset dan pengembangan AI, yang dikembangkan Google dengan dukungan dari NVIDIA dan komunitas. JAX didesain seperti NumPy, mendukung komputasi numerik diferensiabel dan kompilasi JIT (Just In Time) untuk akselerator seperti GPU dan TPU. Flax NNX merupakan library berisi berbagai API untuk memudahkan implementasi <em>neural networks</em>, mirip dengan modul-modul pada Keras dan PyTorch.</p>
<p>Selain Flax NNX, terdapat library lain pada ekosistem JAX untuk mendukung <em>developer experience</em> seperti Optax, Orbax, Grain, dan sebagainya. Untuk memudahkan instalasi, JAX AI Stack (<a href="https://github.com/jax-ml/jax-ai-stack">https://github.com/jax-ml/jax-ai-stack</a>) berisi <em>all-in-one package</em> yang kita butuhkan untuk melakukan pengembangan berbasis JAX.</p>
<p>Materi utama yang disediakan pada workshop ini berupa Google Colab Notebook, yang dapat diakses secara publik di <a href="https://colab.research.google.com/github/windmaple/LLM_from_scratch.JAX/blob/main/01.miniGPT/01.miniGPT_GPU.ipynb#scrollTo=hTmz5Cbco7n_">sini</a>. Instruktur mendemokan bagaimana melakukan pra-pelatihan terhadap model GPT-2 dengan dataset teks yang cukup sederhana, yang berjalan hanya dalam waktu beberapa menit jika menggunakan akselerator GPU atau TPU pada Colab. Dengan menggunakan Flax NNX, pembangunan layer penyusun utama seperti Transformer Block dan Multi-Head Attention dapat dilakukan lebih mudah.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-08-25-google-io-connect-shanghai-2025/media/transformer_from_scratch.jpg" class="img-fluid figure-img"></p>
<figcaption>transformer_from_scratch.jpg</figcaption>
</figure>
</div>
<p>Instrukturnya juga menyediakan contoh-contoh lain dalam menggunakan JAX untuk post-training dan instruction tuning GPT-2, yang dapat ditemui pada repositori Github: https://github.com/windmaple/LLM_from_scratch.JAX</p>
</section>
<section id="membangun-multi-agent-system-dengan-agent-development-kit-workshop" class="level2">
<h2 class="anchored" data-anchor-id="membangun-multi-agent-system-dengan-agent-development-kit-workshop">4. Membangun Multi-Agent System dengan Agent Development Kit (workshop)</h2>
<p><a href="https://google.github.io/adk-docs/">Agent Development Kit (ADK)</a> merupakan sebuah kerangka kerja open-source dari Google untuk mengembangkan dan menyebarkan agentic AI, yang dirancang agar pengembangan dan pemanfaatan <em>AI agents</em> memiliki pengalaman seperti pengembangan perangkat lunak pada umumnya. Walaupun ADK dioptimalkan untuk menggunakan Gemini dan Google Cloud, desain kerangka kerjanya cukup <em>model-agnostic</em> dan <em>deployment agnostic</em>, yang berarti dapat menggunakan model GenAI dan infrastruktur lainnya.</p>
<p>Dibandingkan dengan kerangka kerja serupa seperti LangChain / LangGraph dan CrewAI, ADK relatif baru namun perkembangannya cukup pesat. Salah satu kekuatan dari ADK yaitu kemudahan implementasi arsitektur <em>multi-agent systems</em> (MAS). Dengan prinsip MAS, pengembang berfokus pada perancangan <em>special-purpose, simple AI agent</em> secara modular, dimana solusi dari suatu permasalahan dikerjakan oleh beberapa agen yang saling bekerjasama, dengan instruksi/prompt yang sederhana untuk tiap-tiap agen. Hal ini lebih menjamin keandalan sistem dan juga kemudahan pengelolaan dibandingkan menggunakan satu agen dengan prompt yang panjang dan kompleks.</p>
<p>Dengan menggunakan ADK, kita dapat merancang MAS dengan struktur <em>Hierarchical Agent Tree</em>, yang memudahkan untuk mengontrol dan memprediksi rute penalaran atau percakapan antar agen pada suatu pohon pengambilan keputusan. Struktur pohon hirarkis ini terinspirasi dari kolaborasi tim pada dunia nyata dan juga selaras dengan berbagai <em>design pattern</em> pada pengembangan perangkat lunak konvensional.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-08-25-google-io-connect-shanghai-2025/media/adk.png" class="img-fluid figure-img"></p>
<figcaption>adk.png</figcaption>
</figure>
</div>
<p>Pada workshop ini instruktur membagikan kelas Lab untuk membangun Multi-Agent System dengan ADK yang dapat diakses secara publik. Bagi yang ingin mencoba sendiri, bisa langsung mengunjungi <a href="https://www.cloudskillsboost.google/course_templates/1275/labs/564337">cloudskillsboost.google</a>.</p>
</section>
<section id="living-canvas-tech-demo" class="level2">
<h2 class="anchored" data-anchor-id="living-canvas-tech-demo">5. Living Canvas (tech demo)</h2>
<p>Di luar ruangan ballroom maupun kelas, tersedia area <em>technical demo</em> dimana beberapa <em>stand</em> siap dikunjungi untuk menyaksikan <em>project</em> <em>showcase</em> secara interaktif. Salah satu <em>project</em> yang menarik buat saya yaitu Living Canvas, sebuah aplikasi game puzzle berbasis web dimana pemain langsung menggambar sketsa pada kanvas, lalu mengkonversi sketsa tersebut menjadi objek yang relevan untuk menyelesaikan sebuah <em>puzzle</em> (mendekatkan kunci ke anak kunci).</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-08-25-google-io-connect-shanghai-2025/media/lv1.png" class="img-fluid figure-img"></p>
<figcaption>lv1.png</figcaption>
</figure>
</div>
<p>Pembangunan aplikasi web ini memanfaatkan beberapa teknologi yaitu:</p>
<ul>
<li><strong>Angular</strong>: Membuat antarmuka keseluruhan aplikasi</li>
<li><strong>Firebase App Hosting</strong>: Serverless web hosting untuk otomasi proses deployment.</li>
<li><strong>Vertex AI:</strong> Pintu masuk untuk mengakses Gemini dan Imagen melalui berbagai API yang tersedia.</li>
<li><strong>Gemini:</strong> Melakukan analisis terhadap gambar input sketsa dari pengguna dan memetakan ke tipe objek yang sudah terdefinisi.</li>
<li><strong>Imagen:</strong> Mengkonversi gambar input menjadi gambar dengan grafis berkualitas tinggi berdasarkan tipe objek yang dipetakan oleh Gemini.</li>
<li><strong>Veo</strong>: Membuat animasi pada gambar yang dihasilkan oleh Imagen.</li>
</ul>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-08-25-google-io-connect-shanghai-2025/media/lv2.png" class="img-fluid figure-img"></p>
<figcaption>lv2.png</figcaption>
</figure>
</div>
<p>Bagi yang ingin mencoba menjalankan langsung, dapat langsung mengakses ke repositori kode <a href="https://github.com/FirebaseExtended/solution-living-canvas">https://github.com/FirebaseExtended/solution-living-canvas</a>.</p>
<hr>
<p>Selain mendapatkan ilmu baru dari materi-materi yang disampaikan, dapat bertemu dan berinteraksi dengan Googlers, GDE, dan juga para peserta dari berbagai negara merupakan pengalaman yang tak kalah berharga. Terima kasih banyak kepada Googlers, terutama para Community Managers, dan juga penyelenggara yang telah memfasilitasi para GDE untuk bisa menghadiri langsung Google I/O Connect Shanghai 2025.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-08-25-google-io-connect-shanghai-2025/media/20250825_040439000_iOS.jpg" class="img-fluid figure-img"></p>
<figcaption>20250825_040439000_iOS.jpg</figcaption>
</figure>
</div>


</section>

 ]]></description>
  <category>reading-notes</category>
  <category>opinions</category>
  <guid>https://ghif.github.io/posts/2025-08-25-google-io-connect-shanghai-2025/</guid>
  <pubDate>Mon, 25 Aug 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Implementing Neural Radiance Fields (NeRF) with Keras 3</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2025-06-28-implementing-neural-radiance-fields-keras/</link>
  <description><![CDATA[ 




<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>Imagine walking around your living room with just a handful of photo views. Could you magically reconstruct it in 3D, spin it on your phone, and view it from any angle you choose?</p>
<p>That’s the magic of <strong>novel view synthesis —</strong> a frontier of vision and graphics R&amp;D focused on generating realistic, previously unseen perspectives from existing images. It’s what powers immersive VR walkthroughs, cinematic camera relighting, and virtual twins of real-world scenes.</p>
<p>But with only a few snapshots, how can we handle missing corners, occlusions, or complex lighting? That’s where <strong>Neural Radiance Fields (NeRF)</strong> steps in (<a href="https://arxiv.org/abs/2003.08934">Mildenhall et al., ECCV 2020</a>). It reframed the problem by modeling a scene as a continuous volumetric function from 5D inputs, i.e., point location <img src="https://latex.codecogs.com/png.latex?(x,%20y,%20z)%20%5Cin%20%5Cmathbb%7BR%7D%5E3"> and viewing direction <img src="https://latex.codecogs.com/png.latex?(%5Ctheta,%20%5Cphi)%20%5Cin%20%5Cmathbb%7BR%7D%5E2">, to RGB colors (<img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bc%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E3">) and volume density or depth <img src="https://latex.codecogs.com/png.latex?(%5Csigma%20%5Cin%20%5Cmathbb%7BR%7D)">, then using differentiable volume rendering to paint brand-new views.</p>
<p>Historically, novel view synthesis was performed through hole-filled meshes or patchwork image blending. NeRF skips those processes by learning light interaction, geometry, and view-dependent effects in a single neural network, producing photo-realistic novel views from sparse input — no stitch, no mesh, just neural rendering magic.</p>
<p>This article will elaborate the technical details of the basic NeRF implementation with <a href="https://keras.io/">Keras 3</a>.</p>
</section>
<section id="novel-view-synthesis-with-nerf" class="level2">
<h2 class="anchored" data-anchor-id="novel-view-synthesis-with-nerf">Novel View Synthesis with NeRF</h2>
<p>Before delving into the detailed implementation, let’s formulate the novel view synthesis problem and how NeRF tries to solve it. Novel view synthesis is a task which consists of generating images of a specific scene from a specific point of view, when the only available information is pictures taken from different points of view.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-06-28-implementing-neural-radiance-fields-keras/Implementing Neural Radiance Fields (NeRF) with Ke/image.png" class="img-fluid figure-img"></p>
<figcaption>Source: <a href="https://paperswithcode.com/task/novel-view-synthesis">https://paperswithcode.com/task/novel-view-synthesis</a></figcaption>
</figure>
</div>
<p>Source: <a href="https://paperswithcode.com/task/novel-view-synthesis">https://paperswithcode.com/task/novel-view-synthesis</a></p>
<p>Initially, we have a set of source images <img src="https://latex.codecogs.com/png.latex?%5C%7B%20%5Cmathbf%7BI%7D_i%20%5C%7D_i%5EN"> and their corresponding camera poses <img src="https://latex.codecogs.com/png.latex?%5C%7B%20%5Cmathbf%7BP%7D_i%20%5C%7D">. The goal is to predict the novel view image <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BI%7D"> from a new target pose <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BP%7D">.</p>
<p>Formally, novel view synthesis attempts to construct a function <img src="https://latex.codecogs.com/png.latex?F"> leveraging a dataset of image views <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BD%7D%20=%20%5C%7B%20%5Cmathbf%7BI%7D_i,%20%5Cmathbf%7BP%7D_i%5C%7D_i%5EN"> such that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7BI%7D(u,%20v)%20=%20F(%5Cmathbf%7BP%7D(u,%20v);%20%5Cmathcal%7BD%7D)%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?(u,%20v)"> are pixel coordinates and the space of <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BI%7D"> represents a scene. Early methods factorize this into:</p>
<ul>
<li>3D geometry estimation (e.g., depth or point clouds)</li>
<li>View rendering (warping textures onto geometry)</li>
<li>Hole-filling (inpainting unseen regions)</li>
</ul>
<p>NeRF frames this problem into modeling a scene as a 5D continuous function <img src="https://latex.codecogs.com/png.latex?f_%5Comega:%20%5Cmathbb%7BR%7D%5E5%20%5Crightarrow%20%5Cmathbb%7BR%7D%5E4">, which a trainable neural network with parameter <img src="https://latex.codecogs.com/png.latex?%5Comega">.</p>
<p>The 5D input space comprises a point location randomly sampled on the camera ray (which is basically a straight line), <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Br%7D%20=%20(x,%20y,%20z)"> and the ray’s viewing angle <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bd%7D=%20(%5Cphi,%20%5Ctheta)">. In practice, the 2D viewing angle can be converted into a 3D unit directional vector <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bd%7D%20=%20(d_x,%20d_y,%20d_z)">.</p>
<p>The 2D output space contains the volume density <img src="https://latex.codecogs.com/png.latex?%5Csigma%20%5Cin%20%5Cmathbb%7BR%7D"> and the RGB color <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bc%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E3">.</p>
<p>In other words, the neural network mapping looks like the following:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Af_%5Comega:%20(%5Cmathbf%7Br%7D,%20%5Cmathbf%7Bd%7D)%20%5Crightarrow%20(%5Csigma,%20%5Cmathbf%7Bc%7D)%0A"></p>
<p>Through differentiable volume rendering (will be explained later), the model is optimized to match observed pixel colors—unifying geometry, view-dependent effects, and rendering in one trainable neural net.</p>
<p>By doing so, NeRF can yield:</p>
<ul>
<li>High photographic quality</li>
<li>Realistic specular and lighting variations</li>
<li>No explicit geometry required</li>
</ul>
</section>
<section id="camera-ray-data-as-inputs" class="level2">
<h2 class="anchored" data-anchor-id="camera-ray-data-as-inputs">Camera Ray Data as Inputs</h2>
<p>One may ask where we can the data representing the inputs <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Br%7D"> and <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bd%7D">. It all starts with the camera parameters (poses, intrinsics, and bounds) — those can be obtained by using COLMAP structure-from-motion (SfM) on real-world image samples or generated through 3D modeling software if using synthetic scenes.</p>
<section id="ray-origin-and-directions" class="level3">
<h3 class="anchored" data-anchor-id="ray-origin-and-directions">Ray Origin and Directions</h3>
<p>Firstly, we need to figure out the information of ray origin <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bo%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E3"> and its direction <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bd%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E3">, which can be derived from the camera extrinsics and intrinsics. Think of a camera as a mapping between the real-world and 2D image.</p>
<p><strong>Camera Poses (Extrinsics)</strong></p>
<p>A camera pose is formally represented by a <img src="https://latex.codecogs.com/png.latex?4%20%5Ctimes%204"> transformation matrix depicting both the position and orientation in the real world:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7BP%7D%20=%0A%5Cbegin%7Bbmatrix%7D%20%20%20%0A%5Cmathbf%7BR%7D%20&amp;%20%5Cmathbf%7Bt%7D%20%5C%5C%0A%5Cmathbf%7B0%7D%5E%5Ctop%20&amp;%201%5C%5C%0A%5Cend%7Bbmatrix%7D%0A"></p>
<p>where</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BR%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E%7B3%20%5Ctimes%203%7D"> is the rotation matrix</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bt%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E3"> is the translation vector</li>
</ul>
<p>It provides a transformation from the camera coordinate system to the world coordinate system, i.e.,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7Bx%7D_%7B%5Ctext%7Bworld%7D%7D%20=%20%5Cmathbf%7BR%7D%5E%5Ctop%20%5Cmathbf%7Bx%7D_%7B%5Ctext%7Bcam%7D%7D%20+%20%5Cmathbf%7Bt%7D%0A"></p>
<p><strong>Ray Origin</strong></p>
<p>From the pose matrix, the ray origin <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bo%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E3"> is simply the translation vector <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bt%7D">.</p>
<p><strong>Ray Direction</strong></p>
<p>To calculate the ray direction, we need the information of <em>focal length</em> <img src="https://latex.codecogs.com/png.latex?l">, which is part of the camera intrinsics (camera’s internal configuration). Assuming a pinhole camera, the focal length allows us to convert pixel coordinates <img src="https://latex.codecogs.com/png.latex?(u,%20v)"> into normalized ray directions:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7Bd%7D_%7B%5Ctext%7Bcam%7D%7D%20=%0A%5Cbegin%7Bbmatrix%7D%0A%5Cfrac%7Bu-W/2%7D%7Bl%7D%20%5C%5C%0A%5Cfrac%7Bv-H/2%7D%7Bl%7D%20%5C%5C%0A-1%0A%5Cend%7Bbmatrix%7D%0A"></p>
<p>This direction is then rotated into world space to get the value for <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bd%7D">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7Bd%7D%20=%20%5Cmathbf%7BR%7D%5E%5Ctop%20%5Cmathbf%7Bd%7D_%7B%5Ctext%7Bcam%7D%7D%0A"></p>
<p>Since this operation works on pixel level, each pixel has a different value for <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bd%7D">.</p>
<p>Here is the Python code snippet with Keras Ops API implementing the computation of ray origin and directions:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> keras <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> ops</span>
<span id="cb1-2"></span>
<span id="cb1-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_rays(height, width, focal, pose):</span>
<span id="cb1-4">    u, v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.meshgrid(</span>
<span id="cb1-5">        ops.arange(width, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"float32"</span>),</span>
<span id="cb1-6">        ops.arange(height, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"float32"</span>),</span>
<span id="cb1-7">        indexing<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"xy"</span>,</span>
<span id="cb1-8">    )</span>
<span id="cb1-9">    transformed_u <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (u <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> width <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> focal</span>
<span id="cb1-10">    transformed_v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> height <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> focal</span>
<span id="cb1-11">    directions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.stack(</span>
<span id="cb1-12">        [transformed_u, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>transformed_v, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>ops.ones_like(u)], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-13">    )</span>
<span id="cb1-14">    camera_matrix <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pose[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, :<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>]</span>
<span id="cb1-15">    translations <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pose[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb1-16">    transformed_dirs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> directions[..., <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :]</span>
<span id="cb1-17">    camera_dirs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> transformed_dirs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> camera_matrix</span>
<span id="cb1-18">    ray_directions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(camera_dirs, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb1-19">    ray_origins <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.broadcast_to(translations, ops.shape(ray_directions))</span>
<span id="cb1-20">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (ray_origins, ray_directions)</span></code></pre></div></div>
</section>
<section id="sampling-points-along-ray" class="level3">
<h3 class="anchored" data-anchor-id="sampling-points-along-ray"><strong>Sampling Points along Ray</strong></h3>
<p>Now that we know which way each ray goes. Then, we sample a set of 3D points along each one. This approach comes from volume ray casting, which is a common rendering technique in computer graphics.</p>
<p>Each ray is parameterized as:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bequation%7D%0A%5Cmathbf%7Br%7D(t)%20=%20%5Cmathbf%7Bo%7D%20+%20t%20%5Ccdot%20%20%5Cmathbf%7Bd%7D%0A%5Cend%7Bequation%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?t%20%5Cin%20%5Bt_%7Bnear%7D,%20t_%7Bfar%7D%5D"> represents the depth along the ray. The depth bounds <img src="https://latex.codecogs.com/png.latex?t_%7Bnear%7D,%20t_%7Bfar%7D"> are also part of the camera parameters captured through SfM (or created synthetically).</p>
<p>For each pixel, we can construct a set of finite ray samples from selected depth values <img src="https://latex.codecogs.com/png.latex?t_i">, where <img src="https://latex.codecogs.com/png.latex?%5Cforall%20i%20=%201,%20%5Cldots,%20N">. Assuming that it performs a linear sampling, the following visualization illustrate the sampling process:</p>
<pre><code>                Camera
           ●
           |
           |   t1      t2      t3       tN
           |----●-------●-------●--------●----→ ray direction
               near                        far</code></pre>
<p>The number of samples <img src="https://latex.codecogs.com/png.latex?N"> needs to be specified manually to obtain the desired points. Then, we compute <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Br%7D(t_1),%20%5Cldots,%20%5Cmathbf%7Br%7D(t_N)"> using equation (1). Furthermore, we can also add a uniform noise to each <img src="https://latex.codecogs.com/png.latex?t_i"> so that the samples correspond to a continuous distribution, as is illustrated below.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-06-28-implementing-neural-radiance-fields-keras/Implementing Neural Radiance Fields (NeRF) with Ke/r9TS2wv.gif" class="img-fluid figure-img"></p>
<figcaption>Source: <a href="https://keras.io/examples/vision/nerf/">https://keras.io/examples/vision/nerf/</a></figcaption>
</figure>
</div>
<p>Source: <a href="https://keras.io/examples/vision/nerf/">https://keras.io/examples/vision/nerf/</a></p>
<p>The follow code snippet implements the generation of the depth parameters <img src="https://latex.codecogs.com/png.latex?t_i"> (<code>generate_t_vals</code>) and also the ray samples (<code>sample_rays</code>).</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> keras</span>
<span id="cb3-2"></span>
<span id="cb3-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_t_vals(near, far, batch_size, num_samples, rand_sampling<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>):</span>
<span id="cb3-4">    t_vals <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.linspace(near, far, num_samples, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"float32"</span>)</span>
<span id="cb3-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> rand_sampling:</span>
<span id="cb3-6">        noise <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> keras.random.uniform(shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ops.shape(t_vals)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (far <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> near) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> num_samples</span>
<span id="cb3-7">        t_vals <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> t_vals <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> noise</span>
<span id="cb3-8">    </span>
<span id="cb3-9">    t_vals <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.broadcast_to(t_vals, (batch_size, num_samples))</span>
<span id="cb3-10"></span>
<span id="cb3-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> t_vals</span>
<span id="cb3-12"></span>
<span id="cb3-13">... </span>
<span id="cb3-14">t_vals <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generate_t_vals(near, far, batch_size, num_samples, rand_sampling<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb3-15">...</span>
<span id="cb3-16"></span>
<span id="cb3-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sample_rays(ray_origins, ray_directions, t_vals):</span>
<span id="cb3-18">    rays <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ray_origins[..., <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (</span>
<span id="cb3-19">        ray_directions[..., <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> t_vals[..., :, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>]</span>
<span id="cb3-20">    )</span>
<span id="cb3-21">    dir_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.shape(rays[..., :<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>])</span>
<span id="cb3-22">    dirs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.broadcast_to(ray_directions[..., <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :], dir_shape)</span>
<span id="cb3-23">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> rays, dirs</span></code></pre></div></div>
<p>The <code>sample_rays()</code> function above produces two outputs: <code>rays</code> <img src="https://latex.codecogs.com/png.latex?%5C%7B%20%5Cmathbf%7Br%7D_i%20%5C%7D_%7Bi=1%7D%5E%7BN%7D"> — the set of ray samples — and <code>dirs</code> <img src="https://latex.codecogs.com/png.latex?%5C%7B%20%5Cmathbf%7Bd%7D_i%20%5C%7D_%7Bi=1%7D%5E%7BN%7D"> — the set of (duplicated) ray directions, which later on become the inputs to the neural net. For each pixel, we now have the input data to the NeRF network with dimension <img src="https://latex.codecogs.com/png.latex?2%20%5Ctimes%20(N%20%5Ctimes%203)"> (adding those of ray samples and directions). If we consider an entire image with height <img src="https://latex.codecogs.com/png.latex?H"> and width <img src="https://latex.codecogs.com/png.latex?W">, then we have <img src="https://latex.codecogs.com/png.latex?2%20%5Ctimes%20(H%20%5Ctimes%20W%20%5Ctimes%20N%20%5Ctimes%203)"> input samples.</p>
</section>
<section id="positional-embeddings" class="level3">
<h3 class="anchored" data-anchor-id="positional-embeddings"><strong>Positional Embeddings</strong></h3>
<p>The final piece that we need to process the input data is the positional embeddings. Without it, the neural net struggles to learn high-frequency functions like detailed textures or sharp geometry from low-dimensional input spaces like <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D%5E3">. This is due to <strong>spectral bias</strong> — networks tend to learn smooth, low-frequency variations first (<a href="https://papers.neurips.cc/paper_files/paper/2020/file/55053683268957697aa39fba6f231c68-Paper.pdf">Tancik et al.&nbsp;NeurIPS 2020</a>).</p>
<p>Positional embeddings (also called Fourier features) try to overcome this by mapping each input coordinate to a richer, high-frequency feature representation feeding it into the neural net.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-06-28-implementing-neural-radiance-fields-keras/Implementing Neural Radiance Fields (NeRF) with Ke/Screenshot_2025-06-26_at_08.24.37.png" class="img-fluid figure-img"></p>
<figcaption>Source: <a href="https://arxiv.org/abs/2003.08934">Mildenhall et al., ECCV 2020</a></figcaption>
</figure>
</div>
<p>Source: <a href="https://arxiv.org/abs/2003.08934">Mildenhall et al., ECCV 2020</a></p>
<p>More formally, given a 3D point <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bx%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E3">, the positional embedding function <img src="https://latex.codecogs.com/png.latex?%5Cgamma:%20%5Cmathbb%7BR%7D%5E3%20%5Crightarrow%20%5Cmathbb%7BR%7D%5E%7B6L%7D"> convert it into:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bequation%7D%0A%5Cgamma(%5Cmathbf%7Bx%7D)%20=%20%5Cleft%5B%5Csin(2%5E0%20%5Cpi%20%5Cmathbf%7Bx%7D),%20%5Ccos(2%5E1%20%5Cpi%20%5Cmathbf%7Bx%7D),%20%5Cldots,%20%5Csin(2%5E%7BL-1%7D%20%5Cpi%20%5Cmathbf%7Bx%7D,%20%5Ccos(2%5E%7BL-1%7D%20%5Cpi%20%5Cmathbf%7Bx%7D)%20%5Cright%5D%0A%5Cend%7Bequation%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?L"> is the number of frequency bands (e.g., <img src="https://latex.codecogs.com/png.latex?L=10)">.</p>
<p>This mapping is then applied to both the ray samples <img src="https://latex.codecogs.com/png.latex?%5C%7B%20%5Cmathbf%7Br%7D_i%20%5C%7D_%7Bi=1%7D%5E%7BN%7D"> and directions <img src="https://latex.codecogs.com/png.latex?%5C%7B%20%5Cmathbf%7Bd%7D_i%20%5C%7D_%7Bi=1%7D%5E%7BN%7D">.</p>
<p>The final representations for the neural net are the concatenation between the original samples and their embeddings:</p>
<ul>
<li>Ray sample: <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bz%7D_r%20=%20%5B%5Cmathbf%7B%5Cmathbf%7Br%7D%7D,%20%5Cgamma(%5Cmathbf%7Br%7D)%5D%20%5Cin%20%5Cmathbb%7BR%7D%5E%7B6L_r%20+%203%7D"> - Ray direction: <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bz%7D_d%20=%20%5B%5Cmathbf%7Bd%7D,%20%5Cgamma(%5Cmathbf%7Bd%7D)%20%5D%20%5Cin%20%5Cmathbb%7BR%7D%5E%7B6L_d%20+%203%7D"> where <img src="https://latex.codecogs.com/png.latex?L_r"> and <img src="https://latex.codecogs.com/png.latex?L_d"> are the number of frequency bands for the ray sample and direction, respectively.</li>
</ul>
<p>Here is the code implementation:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> encode_position(x, pos_encode_dims):</span>
<span id="cb4-2">    positions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [x]</span>
<span id="cb4-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(pos_encode_dims):</span>
<span id="cb4-4">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> fn <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [ops.sin, ops.cos]:</span>
<span id="cb4-5">            positions.append(fn(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> x))</span>
<span id="cb4-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ops.concatenate(positions, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span></code></pre></div></div>
</section>
</section>
<section id="network-architecture-and-training" class="level2">
<h2 class="anchored" data-anchor-id="network-architecture-and-training">Network Architecture and Training</h2>
<p>The NeRF network is a deep multilayer perceptron (MLP) featuring skip connections to enhance the gradient flow and preserve spatial detail deep into the network. By incorporating position embeddings, the network function becomes something like this:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Af_%5Comega:%20(%5Cmathbf%7Bz%7D_r,%20%5Cmathbf%7Bz%7D_d)%20%5Crightarrow%20(%5Csigma,%20%5Cmathbf%7Bc%7D)%0A"></p>
<section id="architecture" class="level3">
<h3 class="anchored" data-anchor-id="architecture">Architecture</h3>
<p>Here is the network architecture and its implementation with Keras:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-06-28-implementing-neural-radiance-fields-keras/Implementing Neural Radiance Fields (NeRF) with Ke/image 1.png" class="img-fluid figure-img"></p>
<figcaption>Source: <a href="https://pyimagesearch.com/2021/11/17/computer-graphics-and-deep-learning-with-nerf-using-tensorflow-and-keras-part-2/">https://pyimagesearch.com/2021/11/17/computer-graphics-and-deep-learning-with-nerf-using-tensorflow-and-keras-part-2/</a></figcaption>
</figure>
</div>
<p>Source: <a href="https://pyimagesearch.com/2021/11/17/computer-graphics-and-deep-learning-with-nerf-using-tensorflow-and-keras-part-2/">https://pyimagesearch.com/2021/11/17/computer-graphics-and-deep-learning-with-nerf-using-tensorflow-and-keras-part-2/</a></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> create_nerf_complete_model(num_layers, hidden_dim, skip_layer, lxyz, ldir, bn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>):</span>
<span id="cb5-2">    ray_input <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> keras.Input(shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> lxyz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>))</span>
<span id="cb5-3">    dir_input <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> keras.Input(shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> ldir <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>))</span>
<span id="cb5-4"></span>
<span id="cb5-5">    x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ray_input</span>
<span id="cb5-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(num_layers):</span>
<span id="cb5-7">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> bn:</span>
<span id="cb5-8">            x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.Dense(hidden_dim)(x)</span>
<span id="cb5-9">            x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.BatchNormalization()(x)</span>
<span id="cb5-10">            x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.ReLU()(x)</span>
<span id="cb5-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb5-12">            x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.Dense(hidden_dim, activation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"relu"</span>)(x)</span>
<span id="cb5-13"></span>
<span id="cb5-14">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Check if we have to include residual connections</span></span>
<span id="cb5-15">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> skip_layer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb5-16">            x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.concatenate([x, ray_input], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb5-17">        </span>
<span id="cb5-18">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Get the sigma value</span></span>
<span id="cb5-19">    sigma <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.Dense(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)(x)</span>
<span id="cb5-20"></span>
<span id="cb5-21">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Create a feature vector</span></span>
<span id="cb5-22">    feature <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.Dense(hidden_dim)(x)</span>
<span id="cb5-23"></span>
<span id="cb5-24">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Concatenate the feature vector with the direction input</span></span>
<span id="cb5-25">    feature <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.concatenate([feature, dir_input], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb5-26">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> bn:</span>
<span id="cb5-27">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.Dense(hidden_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)(feature)</span>
<span id="cb5-28">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.BatchNormalization()(x)</span>
<span id="cb5-29">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.ReLU()(x)</span>
<span id="cb5-30">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb5-31">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.Dense(hidden_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, activation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"relu"</span>)(feature)</span>
<span id="cb5-32"></span>
<span id="cb5-33">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Get the rgb value</span></span>
<span id="cb5-34">    rgb <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.Dense(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)(x)</span>
<span id="cb5-35"></span>
<span id="cb5-36">    outputs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.concatenate([rgb, sigma], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb5-37"></span>
<span id="cb5-38">    nerf_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> keras.Model(inputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[ray_input, dir_input], outputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>outputs)</span>
<span id="cb5-39">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> nerf_model</span></code></pre></div></div>
<p>The following code illustrates the forward pass through the NeRF network:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">t_vals <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generate_t_vals(near, far, batch_size, num_samples, rand_sampling<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># generate depth parameters t</span></span>
<span id="cb6-2">rays, dirs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sample_rays(ray_origins, ray_directions, t_vals) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># generate ray samples r [batch_size, N * (6 * L + 3)] and directions d [batch_size, N * (6 * L + 3)]</span></span>
<span id="cb6-3"></span>
<span id="cb6-4">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> create_nerf_complete_model(num_layers, hidden_dim, skip_layer, lxyz, ldir) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># define NeRF network</span></span>
<span id="cb6-5"></span>
<span id="cb6-6">(rgbs, sigmas) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model([rays, dirs]) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># forward pass</span></span></code></pre></div></div>
</section>
<section id="volume-rendering" class="level3">
<h3 class="anchored" data-anchor-id="volume-rendering">Volume Rendering</h3>
<p>After querying the NeRF network at sampled points along each ray, we obtain for each sample <img src="https://latex.codecogs.com/png.latex?i">:</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bc%7D_i%20=%20(r,%20g,%20b)">: emitted color, which may be view-dependent</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Csigma_i">: volume density (opacity)</li>
</ul>
<p>The goal is to accumulate these into a single pixel color <img src="https://latex.codecogs.com/png.latex?%5Chat%7BC%7D(r)">, achieved through the classical volume rendering. In continuous space, the equation is given by:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AC(%5Cmathbf%7Br%7D)%20=%20%5Cint_%7Bt_n%7D%5E%7B_f%7D%20T(t)%20%5Csigma%5Cleft(%5Cmathbf%7Br%7D(t)%5Cright)%20c%5Cleft(%5Cmathbf%7Br%7D(t),%20%5Cmathbf%7Bd%7D%5Cright)%20dt%0A"></p>
<p>with transmittance, i.e., the probability the ray reaches point <img src="https://latex.codecogs.com/png.latex?t"> without being blocked:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AT(t)%20=%20%5Cexp%0A%5Cleft(%20-%20%5Cint_%7Bt_n%7D%5E%7Bt_f%7D%0A%5Csigma(%20%5Cmathbf%7Br%7D(s)%20)%20ds%0A%5Cright)%0A"></p>
<p>Since only <img src="https://latex.codecogs.com/png.latex?N"> ray samples are incorporated, we implement the equation in discrete space:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bequation%7D%0A%5Chat%7BC%7D(%5Cmathbf%7Br%7D)%20=%20%5Csum_%7Bi=1%7D%5EN%20T_i%20%5Calpha_i%20%5Cmathbf%7Bc%7D_i%0A%5Cend%7Bequation%7D%0A"></p>
<p>where</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?T_i%20=%20%5Cprod_%7Bj=1%7D%5E%7Bi-1%7D%20(1%20-%20%5Calpha_j)">: accumulated transparency up to <img src="https://latex.codecogs.com/png.latex?i"> - <img src="https://latex.codecogs.com/png.latex?%5Calpha_i%20=%201%20-%20%5Cexp(-%5Csigma_i%20%5Cdelta_i)">: opacity for the segment</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cdelta_i%20=%20t_%7Bi+1%7D%20-%20t_i">: distance/segment between adjacent intervals</li>
</ul>
<p>The <code>volume_render()</code> function below implements equation (3).</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> volume_render(preds, t_vals):</span>
<span id="cb7-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Get rgb and sigma from the predictions</span></span>
<span id="cb7-3">    rgb <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.sigmoid(preds[..., :<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb7-4">    sigma_a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.relu(preds[..., <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb7-5"></span>
<span id="cb7-6">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Get the distance of adjacent intervals</span></span>
<span id="cb7-7">    delta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> t_vals[..., <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> t_vals[..., :<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb7-8">    const <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.broadcast_to([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e10</span>], shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(delta.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb7-9">    delta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.concatenate([delta, const], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb7-10"></span>
<span id="cb7-11">    alpha <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> ops.exp(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>sigma_a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> delta)</span>
<span id="cb7-12">    exp_term <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> alpha</span>
<span id="cb7-13">    epsilon <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-10</span></span>
<span id="cb7-14"></span>
<span id="cb7-15">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Compute transmittance: Cumulative prod with exclusive mode</span></span>
<span id="cb7-16">    tm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.cumprod(exp_term <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> epsilon, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb7-17">    tm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.roll(tm, shift<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb7-18">    transmittance <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.concatenate([ops.ones((tm.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)), tm[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:]], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb7-19"></span>
<span id="cb7-20">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Compute weights</span></span>
<span id="cb7-21">    weights <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> alpha <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> transmittance</span>
<span id="cb7-22">    rgb_w <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(weights[..., <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> rgb, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)    </span>
<span id="cb7-23">    depth_map <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(weights <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> t_vals, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb7-24">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (rgb_w, depth_map, weights)</span></code></pre></div></div>
</section>
<section id="nerf-training" class="level3">
<h3 class="anchored" data-anchor-id="nerf-training">NeRF Training</h3>
<p>The NeRF network is trained by minimizing a photometric loss defined as:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BL%7D(%5Comega)%20=%20%5Csum_%7B%5Cmathbf%7Br%7D%20%5Cin%20%5Cmathrm%7BR%7D%7D%20%5Cleft%5C%7C%20%5Chat%7BC%7D_%7B%5Comega%7D(%5Cmathbf%7Br%7D)%20-%20C_%7Bgt%7D(%5Cmathbf%7Br%7D)%20%5Cright%5C%7C_2%5E2%0A"></p>
<p>where</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?%5Chat%7BC%7D_%5Comega(%5Cmathbf%7Br%7D)"> is the rendered color for ray <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Br%7D"> produced by the network with parameter <img src="https://latex.codecogs.com/png.latex?%5Comega"> - <img src="https://latex.codecogs.com/png.latex?C_%7Bgt%7D(%5Cmathbf%7Br%7D)"> is the corresponding ground-truth pixel color</li>
</ul>
<p>Because the volume rendering operation used in NeRF is <strong>fully differentiable</strong>—integrating densities and colors along each ray—the loss function itself is differentiable. This allows us to apply standard <strong>gradient-based optimization</strong> methods (e.g., Adam) to train the NeRF model end-to-end by backpropagating errors through both the rendering and network branches</p>
</section>
<section id="hierarchical-sampling-coarse-and-fine-models" class="level3">
<h3 class="anchored" data-anchor-id="hierarchical-sampling-coarse-and-fine-models">Hierarchical Sampling: Coarse and Fine Models</h3>
<p>NeRF uses a two-stage hierarchical sampling strategy — employing both a <strong>coarse</strong> and fine networks — to efficiently allocate computational effort where it matters most.</p>
<ul>
<li><strong>Coarse network</strong> (<img src="https://latex.codecogs.com/png.latex?f_%7B%5Comega_c%7D">) : Scout where interesting geometry might lie</li>
<li><strong>Fine network</strong> (<img src="https://latex.codecogs.com/png.latex?f_%7B%5Comega_f%7D">): Render those regions with high fidelity</li>
</ul>
<p>Without this strategy, i.e., only relying on a single big network, it will require more computational resources and potentially waste samples on empty space.</p>
<p>To do so, NeRF performs the following steps:</p>
<ol type="1">
<li><p><strong>Uniform sampling along rays</strong></p>
<p>This step basically executes the <code>generate_t_vals()</code> and <code>sample_rays()</code> functions discussed before, with the number of samples <img src="https://latex.codecogs.com/png.latex?N_%7B%5Cmathrm%7Bcoarse%7D%7D">. The generated samples are denoted as <img src="https://latex.codecogs.com/png.latex?%5C%7B%20%5Cmathbf%7Br%7D_t%5C%7D_%7Bt=i%7D%5E%7BN_%7B%5Cmathrm%7Bcoarse%7D%7D%7D">.</p></li>
<li><p><strong>Query the coarse network</strong></p>
<p>Use the coarse MLP <img src="https://latex.codecogs.com/png.latex?f_%7B%5Comega_c%7D"> to predict density <img src="https://latex.codecogs.com/png.latex?%5Csigma_i"> and color <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bc%7D_i">.</p></li>
<li><p><strong>Volume rendering (coarse pass)</strong></p>
<p>Compute weights <img src="https://latex.codecogs.com/png.latex?w_i%20=%20T_i%20%5Calpha_i"> from the predicted densities using the volume rendering equation (3). These weights represent how much each point contributes to the final pixel color and serve as a proxy for surface probability along the ray.</p></li>
<li><p><strong>Construct a probability density function (PDF) from coarse weights and sample from it</strong></p>
<p>Convert the normalized weights into a probability density function (PDF) and then into a cumulative distribution function (CDF) using cumulative summation. After that, sample additional <img src="https://latex.codecogs.com/png.latex?N_%7B%5Cmathrm%7Bfine%7D%7D"> points from the CDF using inverse transformation sampling, biasing new sample locations toward high-weight region, i.e., likely to contain surface.</p></li>
<li><p><strong>Query the fine network</strong></p>
<p>Concatenate the original coarse samples with the new fine samples, sort them along the ray, and query the fine MLP to obtain improved <img src="https://latex.codecogs.com/png.latex?(%5Csigma_i,%20%5Cmathbf%7Bc%7D_i)"> predictions.</p></li>
<li><p><strong>Volume rendering (fine pass)</strong></p>
<p>Perform a second round of volume rendering using the fine samples to produce the final pixel color <img src="https://latex.codecogs.com/png.latex?%5Chat%7BC%7D_%7B%5Cmathrm%7Bfine%7D%7D">.</p></li>
</ol>
<p>Here is the code snippet implementing these steps.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Define coarse and fine models</span></span>
<span id="cb8-2">...</span>
<span id="cb8-3">coarse_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> create_nerf_complete_model(</span>
<span id="cb8-4">    num_layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>NUM_LAYERS,</span>
<span id="cb8-5">    hidden_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>HIDDEN_DIM,</span>
<span id="cb8-6">    skip_layer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>SKIP_LAYER,</span>
<span id="cb8-7">    lxyz<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>L_XYZ,</span>
<span id="cb8-8">    ldir<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>L_DIR,</span>
<span id="cb8-9">    bn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>BATCH_NORM</span>
<span id="cb8-10">)</span>
<span id="cb8-11">fine_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> create_nerf_complete_model(</span>
<span id="cb8-12">    num_layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>NUM_LAYERS,</span>
<span id="cb8-13">    hidden_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>HIDDEN_DIM,</span>
<span id="cb8-14">    skip_layer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>SKIP_LAYER,</span>
<span id="cb8-15">    lxyz<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>L_XYZ,</span>
<span id="cb8-16">    ldir<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>L_DIR,</span>
<span id="cb8-17">    bn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>BATCH_NORM</span>
<span id="cb8-18">)</span>
<span id="cb8-19"></span>
<span id="cb8-20">...</span>
<span id="cb8-21"></span>
<span id="cb8-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Coarse model forward pass </span></span>
<span id="cb8-23">t_vals <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generate_t_vals(...)</span>
<span id="cb8-24">rays, dirs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sample_rays(ray_origins, ray_directions, t_vals)</span>
<span id="cb8-25">rays_enc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> encode_position(rays, pos_encode_dims<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>l_xyz)</span>
<span id="cb8-26">dirs_enc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> encode_position(dirs, pos_encode_dims<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>l_dir)</span>
<span id="cb8-27">predictions_coarse <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> coarse_model([rays_enc, dirs_enc], training<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>training)</span>
<span id="cb8-28">rgb_coarse, depth_coarse, weights_coarse <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> volume_render(predictions_coarse, t_vals)</span>
<span id="cb8-29"></span>
<span id="cb8-30"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Sample PDF</span></span>
<span id="cb8-31">t_vals_coarse_mid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (t_vals[..., <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> t_vals[..., :<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]))</span>
<span id="cb8-32">t_vals_fine <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sample_pdf(t_vals_coarse_mid, weights_coarse, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ns_fine)</span>
<span id="cb8-33">t_vals_fine_all <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.sort(ops.concatenate([t_vals, t_vals_fine], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb8-34"></span>
<span id="cb8-35"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Fine model forward pass</span></span>
<span id="cb8-36">rays_fine, dirs_fine <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sample_rays(ray_origins, ray_directions, t_vals_fine_all)</span>
<span id="cb8-37">rays_fine_enc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> encode_position(rays_fine, pos_encode_dims<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>l_xyz)</span>
<span id="cb8-38">dirs_fine_enc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> encode_position(dirs_fine, pos_encode_dims<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>l_dir)</span>
<span id="cb8-39">predictions_fine <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fine_model([rays_fine_enc, dirs_fine_enc], training<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>training)</span></code></pre></div></div>
<p>The following is the implementation of ray point samplings from a probability density function (PDF) constructed from the coarse weights:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sample_pdf(t_vals_mid, weights, ns_fine):</span>
<span id="cb9-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Get batch_size, H, W</span></span>
<span id="cb9-3">    batch_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.shape(weights)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb9-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(ops.shape(weights)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>: <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (b, h, w, num_samples)</span></span>
<span id="cb9-5">        image_height, image_width <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.shape(weights)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>]</span>
<span id="cb9-6">    </span>
<span id="cb9-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># add a small value to the weights to prevent it from nan</span></span>
<span id="cb9-8">    weights <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-5</span></span>
<span id="cb9-9">    </span>
<span id="cb9-10">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># normalize the weights to get the pdf</span></span>
<span id="cb9-11">    pdf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> weights <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> tf.reduce_sum(weights, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, keepdims<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb9-12">    </span>
<span id="cb9-13">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># from pdf to cdf transformation</span></span>
<span id="cb9-14">    cdf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tf.cumsum(pdf, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb9-15">    </span>
<span id="cb9-16">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># start the cdf with 0sa</span></span>
<span id="cb9-17">    cdf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tf.concat([tf.zeros_like(cdf[..., :<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]), cdf], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb9-18"></span>
<span id="cb9-19">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># get the sample points</span></span>
<span id="cb9-20">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(ops.shape(weights)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>:</span>
<span id="cb9-21">        u_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [batch_size, image_height, image_width, ns_fine]</span>
<span id="cb9-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb9-23">        u_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [batch_size, ns_fine]</span>
<span id="cb9-24"></span>
<span id="cb9-25">    u <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tf.random.uniform(shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>u_shape)</span>
<span id="cb9-26">    </span>
<span id="cb9-27">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># get the indices of the points of u when u is inserted into cdf in a</span></span>
<span id="cb9-28">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># sorted manner</span></span>
<span id="cb9-29">    indices <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tf.searchsorted(cdf, u, side<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>)</span>
<span id="cb9-30"></span>
<span id="cb9-31">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># define the boundaries</span></span>
<span id="cb9-32">    below <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tf.maximum(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, indices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb9-33">    above <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tf.minimum(cdf.shape[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, indices)</span>
<span id="cb9-34">    indices_g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tf.stack([below, above], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb9-35">    </span>
<span id="cb9-36">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># gather the cdf according to the indices</span></span>
<span id="cb9-37">    cdf_g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tf.gather(cdf, indices_g, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, batch_dims<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(indices_g.shape)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb9-38"></span>
<span id="cb9-39">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># gather the tVals according to the indices</span></span>
<span id="cb9-40">    indices_gt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tf.minimum(t_vals_mid.shape[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, indices_g)</span>
<span id="cb9-41">    t_vals_mid_g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tf.gather(t_vals_mid, indices_gt, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb9-42">        batch_dims<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(indices_g.shape)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb9-43">    </span>
<span id="cb9-44">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># create the samples by inverting the cdf</span></span>
<span id="cb9-45">    denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cdf_g[..., <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> cdf_g[..., <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb9-46">    denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tf.where(denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-5</span>, tf.ones_like(denom), denom)</span>
<span id="cb9-47">    t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (u <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> cdf_g[..., <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> denom</span>
<span id="cb9-48">    samples <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (t_vals_mid_g[..., <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> </span>
<span id="cb9-49">        (t_vals_mid_g[..., <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> t_vals_mid_g[..., <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]))</span>
<span id="cb9-50"></span>
<span id="cb9-51">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># return the samples</span></span>
<span id="cb9-52">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> samples</span></code></pre></div></div>
<p>This hierarchical sampling strategy introduces a slight modification to the loss function by incorporating the outputs of both the coarse and fine networks. The combined photometric loss is defined as follows:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7B%5Cbar%7BL%7D%7D(%5Comega)%20=%20%5Csum_%7B%5Cmathbf%7Br%7D%20%5Cin%20%5Cmathrm%7BR%7D%7D%20%5Cleft%5C%7C%20%5Chat%7BC%7D_%7B%5Comega_c%7D(%5Cmathbf%7Br%7D)%20-%20C_%7Bgt%7D(%5Cmathbf%7Br%7D)%20%5Cright%5C%7C_2%5E2%20+%0A%5Cleft%5C%7C%20%5Chat%7BC%7D_%7B%5Comega_f%7D(%5Cmathbf%7Br%7D)%20-%20C_%7Bgt%7D(%5Cmathbf%7Br%7D)%20%5Cright%5C%7C_2%5E2%0A"></p>
<p>To optimize this combined loss, the training loop can be implemented by overriding the <code>train_step()</code> method in a custom <code>keras.Model</code> subclass. This method handles the forward pass through both networks, computes the joint loss, and applies gradient-based updates <img src="https://latex.codecogs.com/png.latex?%5Comega_t%20=%20%5Comega_%7Bt-1%7D%20-%20%5Calpha%20%5Cnabla_%5Comega%20%5Cmathcal%7B%5Cbar%7BL%7D%7D(%5Comega)"> accordingly.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> train_step(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, inputs):</span>
<span id="cb10-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Get the image and the rays</span></span>
<span id="cb10-3">    (images, rays) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> inputs</span>
<span id="cb10-4">    (ray_origins, ray_directions, t_vals) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rays</span>
<span id="cb10-5"></span>
<span id="cb10-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> tf.GradientTape() <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> tape:</span>
<span id="cb10-7">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Get the predictions from the model</span></span>
<span id="cb10-8">        rgbs, _, _, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.forward_pass(ray_origins, ray_directions, t_vals, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.l_xyz, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.l_dir, training<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb10-9">        rgb_coarse, rgb_fine <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rgbs</span>
<span id="cb10-10">        loss_coarse <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.loss_fn(images, rgb_coarse)</span>
<span id="cb10-11">        loss_fine <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.loss_fn(images, rgb_fine)</span>
<span id="cb10-12">            </span>
<span id="cb10-13">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Combine the coarse and fine losses</span></span>
<span id="cb10-14">    loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> loss_coarse <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> loss_fine</span>
<span id="cb10-15"></span>
<span id="cb10-16">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Apply gradient updates for the model</span></span>
<span id="cb10-17">        tv_nerf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.coarse_model.trainable_variables <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.fine_model.trainable_variables</span>
<span id="cb10-18">        grads <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tape.gradient(loss, tv_nerf)</span>
<span id="cb10-19">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.optimizer.apply_gradients(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(grads, tv_nerf))</span>
<span id="cb10-20"></span>
<span id="cb10-21">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Get the PSNR of the reconstructed images and the source images</span></span>
<span id="cb10-22">        psnr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.psnr(images, rgb_fine, max_val<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span>
<span id="cb10-23"></span>
<span id="cb10-24">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Compute the metrics</span></span>
<span id="cb10-25">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.loss_coarse_tracker.update_state(loss_coarse)</span>
<span id="cb10-26">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.loss_tracker.update_state(loss_fine)</span>
<span id="cb10-27">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.psnr_tracker.update_state(psnr)</span>
<span id="cb10-28">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb10-29">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"loss_coarse"</span>: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.loss_coarse_tracker.result(),</span>
<span id="cb10-30">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"loss"</span>: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.loss_tracker.result(),</span>
<span id="cb10-31">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"psnr"</span>: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.psnr_tracker.result(),</span>
<span id="cb10-32">        }</span></code></pre></div></div>
</section>
</section>
<section id="experiments-on-a-synthetic-lego-scene" class="level2">
<h2 class="anchored" data-anchor-id="experiments-on-a-synthetic-lego-scene">Experiments on a Synthetic Lego Scene</h2>
<p>We conducted NeRF training on the synthetic Lego scene dataset, which consists of 106 images at a resolution of 100 x 100 pixels, each accompanied by its corresponding camera pose. The dataset was split into training and validation sets using an 80:20 ratio, resulting in 84 training samples and 22 validation samples. To further reduce the computational complexity, all images were downsamples to a resolution of (H = 50, W = 50) pixels.</p>
<p>Next, we executed the data processing pipeline described earlier to obtain the the positionally encoded coarse ray samples. By setting the number of coarse samples to <img src="https://latex.codecogs.com/png.latex?N_%7B%5Cmathrm%7Bcoarse%7D%7D%20=%2064"> and using positional embedding with <img src="https://latex.codecogs.com/png.latex?L_%7Br%7D%20=%2010"> (hence, the dimensionality of each ray becomes <img src="https://latex.codecogs.com/png.latex?6%20*%2010%20+%203%20=%2063">), the shape of the full training dataset <img src="https://latex.codecogs.com/png.latex?Z_%7Btrain%7D"> is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathrm%7Bdim%7D(Z_%7Btrain%7D)%20=%20%5B%5Cunderbrace%7B84%7D_%7B%5Ctexttt%7B%5C#train%7D%7D,%20%5Cunderbrace%7B50%7D_%7BH%7D,%20%5Cunderbrace%7B50%7D_%7BW%7D,%0A%5Cunderbrace%7B64%7D_%7BN_%7B%5Cmathrm%7Bcoarse%7D%7D%7D,%0A%5Cunderbrace%7B63%7D_%7B%5Ctexttt%7Bpos-emb%20dimension%7D%7D%5D%0A"></p>
<p><strong>Notes on batch training</strong></p>
<p>Since NeRF operates at the pixel level, the effective number of training samples is not just the number of images <code>#train = 84</code> , but rather the total number of rays in every pixel, which equals to <code>#train x H x W = 84 x 50 x 50 = 210000</code>. To enable efficient batch processing, the full training set is reshaped to</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathrm%7Bdim%7D(Z_%7Btrain%7D)%20=%20%5B210000,%2064,%2063%5D%0A"></p>
<p>This reshaping step is essential for enabling mini-batch training — without it, feeding the full 5D tensor into the model would quickly exhaust GPU memory!</p>
<p>By setting the training <code>epoch = 300</code> and <code>batch_size = 1024</code> , we get the following results:</p>
<p><a href="Implementing Neural Radiance Fields (NeRF) with Ke/lego_batch_h256_training.mp4">Rendering results during training, displayed per epoch</a></p>
<p>Rendering results during training, displayed per epoch</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-06-28-implementing-neural-radiance-fields-keras/Implementing Neural Radiance Fields (NeRF) with Ke/Screenshot_2025-06-27_at_15.29.14.png" class="img-fluid figure-img"></p>
<figcaption>Comparison between the ground truth and reconstructed images</figcaption>
</figure>
</div>
<p>Comparison between the ground truth and reconstructed images</p>
<section id="horizontal-view-rendering" class="level3">
<h3 class="anchored" data-anchor-id="horizontal-view-rendering">360 Horizontal View Rendering</h3>
<p>After training for just 1-2 hours on Google Colab using an NVIDIA A100 GPU, we can infer the 360 horizontal renders shown below. For significantly better rendering quality — especially sharper edges and finer details — the network should be trained for a longer duration and with higher-solution images. As noted in the original NeRF paper (<a href="https://arxiv.org/abs/2003.08934">Mildenhall et al., ECCV 2020</a>), training a single scene typically takes around 1-2 days on an NVIDIA V100 GPU.</p>
<p><a href="Implementing Neural Radiance Fields (NeRF) with Ke/lego_batch_h256_rgb_video_v2.mp4">360 horizontal renders (height: 50, weight: 50) trained with Colab GPU A100</a></p>
<p>360 horizontal renders (height: 50, weight: 50) trained with Colab GPU A100</p>
<p>Using the original image resolution (<code>height: 100, width: 100</code>) and training on a <a href="https://cloud.google.com/tpu/docs/v3">Cloud TPU v3 (8 TensorCores)</a> over an extended period (<code>1000 epochs</code>), we can certainly achieve substantially higher-quality renders:</p>
<p><a href="Implementing Neural Radiance Fields (NeRF) with Ke/lego_batch_h256_tpu_rgb_video_tpu.mp4">360 horizontal renders (height: 100, width: 100) trained with cloud [TPU v3](https://cloud.google.com/tpu/docs/v3) (8 TensorCores)</a></p>
<p>360 horizontal renders (height: 100, width: 100) trained with cloud <a href="https://cloud.google.com/tpu/docs/v3">TPU v3</a> (8 TensorCores)</p>
<p>The full code implementation used in this article can be explored at <a href="https://github.com/ghif/nerf-keras">https://github.com/ghif/nerf-keras</a>.</p>
</section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>This article walked you through the complete implementation of a basic NeRF, demystifying every major component: problem setup, ray generation and sampling, positional embeddings, network architecture, volume rendering, hierarchical sampling, and training setup.</p>
<p>These days, NeRF has evolved remarkably and several exciting developments are pushing the field forward for better generalization, faster training, and real-time rendering, such as:</p>
<ul>
<li>(<a href="https://arxiv.org/pdf/2308.11793">Cong et al.&nbsp;ICCV2023</a>): Generalization and Volume Rendering advancements, such as transformer-based architectures for cross-scene NeRF, enable <strong>few-shot synthesis</strong> of new content and improved boilerplate-free deployments.</li>
<li>(<a href="https://arxiv.org/abs/2311.12490">Wang et al.&nbsp;WACV2024</a>): Hyb‑NeRF uses multiresolution <em>hybrid encoding</em> (e.g., hash grids) to drastically speed up inference while maintaining visual quality.</li>
<li>(<a href="https://arxiv.org/pdf/2504.00950">Ding et al.&nbsp;2025</a>): Neural Pruning methods apply structured parameter reduction during training, achieving up to 50% model size reduction and 35% faster training, with minimal accuracy loss.</li>
<li>(<a href="https://arxiv.org/pdf/2505.06894">Qazi et al.&nbsp;2025</a>): NeuGen introduces brain-inspired normalization to improve domain generalization, enabling NeRF-like models to perform robustly across diverse environments.</li>
</ul>
<p>See also the curated list of papers: https://github.com/awesome-NeRF/awesome-NeRF.</p>


</section>

 ]]></description>
  <category>computer-vision</category>
  <category>implementation</category>
  <guid>https://ghif.github.io/posts/2025-06-28-implementing-neural-radiance-fields-keras/</guid>
  <pubDate>Sat, 28 Jun 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>State of the Art in AR, VR, and XR tech (2024-2025)</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2025-05-19-state-of-the-art-ar-vr-xr/</link>
  <description><![CDATA[ 




<aside>
<p>💡</p>
<p>This article was written with the help of <a href="https://openai.com/index/introducing-deep-research/">OpenAI’s deep research</a>, an agentic AI tool that employs reasoning to synthesize extensive online information and execute complex multi-step research tasks.</p>
</aside>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction"><strong>Introduction</strong></h2>
<p>Augmented reality (AR), virtual reality (VR), and mixed/extended reality (XR) technologies have advanced rapidly in the past year, driven by major tech firms and innovative startups worldwide. The global XR market (encompassing AR and VR) was valued around <strong>$25.7 billion in 2024</strong> , and hardware adoption is accelerating – for example, <strong>AR/VR headset shipments grew ~12.8% year-on-year in Q3 2024</strong>, with Meta’s Quest devices alone accounting for about <strong>70%</strong> of units thanks to new releases . These “spatial computing” technologies are increasingly moving from hype to practical deployment across industries. This report details the latest state-of-the-art hardware and software, key company developments, and XR applications in gaming, healthcare, education, enterprise, and retail on a global scale.</p>
</section>
<section id="hardware-advancements-in-ar-vr-and-xr" class="level2">
<h2 class="anchored" data-anchor-id="hardware-advancements-in-ar-vr-and-xr"><strong>Hardware Advancements in AR, VR, and XR</strong></h2>
<p><strong>High-End Mixed Reality Headsets:</strong>&nbsp; A milestone in 2024 was <strong>Apple’s entry into XR</strong> with the <em>Vision Pro</em> headset (available starting Feb 2024). Apple Vision Pro is a <em>“spatial computer”</em> that <strong>seamlessly blends digital content with the physical world</strong>, enabling powerful mixed-reality experiences in a user’s environment . It features advanced sensors (cameras and LiDAR), dual 4K micro-OLED displays, an Apple M2 chip plus a dedicated R1 chip for real-time sensor processing, and an innovative transparent lens design. The Vision Pro is controlled via natural inputs – <strong>eye tracking, hand gestures, and voice</strong> – with no controllers . Priced at$3,499, it targets developers and prosumer users initially. Apple’s visionOS platform allows Vision Pro to run thousands of iPad and iPhone apps plus new spatial apps via an all-new app store . Apple has been steadily improving visionOS (e.g.&nbsp;a September 2024 update and a March 2025 update adding AI-powered “Apple Intelligence” features) and expanding content, such as immersive 3D videos and concerts for Vision Pro . This marks a significant step forward in XR hardware, with <em>TechCrunch</em> calling it <em>“the most advanced consumer electronics device ever created”</em>, and it has spurred competition across the industry.</p>
<p><strong>Meta’s VR/MR Headsets:</strong> Meta (Facebook) has solidified its lead in consumer VR with the <strong>Meta Quest 3</strong>, launched in October 2023. The Quest 3 is billed as <em>“the world’s first mass-market mixed reality headset”</em> . At <strong>$499</strong>, it delivers significant improvements over its predecessor: a <strong>30% leap in visual resolution, 2× GPU performance (Snapdragon XR2 Gen 2 chip), 40% louder audio</strong>, and a slimmer, more comfortable design . Notably, Quest 3 introduces high-fidelity <strong>full-color passthrough cameras</strong> enabling mixed reality – users can see their surroundings in color and overlay virtual objects with 10× the pixel count of Quest 2’s passthrough . With a quick double-tap on the side, wearers can <strong>seamlessly toggle between fully immersive VR and mixed reality</strong>, blending virtual elements into their real space . This allows experiences like playing a <strong>virtual piano on your coffee table</strong> or opening a portal in your living room, as Meta showcases . The Quest 3, powered by inside-out tracking and self-contained, represents the mainstream trend toward standalone XR headsets that do both VR and passthrough AR. Meta also offers the higher-end Quest Pro (launched late 2022) oriented at enterprise use with features like eye and face tracking, but the Quest 3 has largely overtaken it for consumers. In addition, Meta introduced updated <strong>Ray-Ban Meta smart glasses</strong> in late 2023 – while these are camera-and-audio wearables (no display AR yet), Meta’s roadmap includes developing true AR glasses later in the decade.</p>
<p><strong>Advanced VR for Enterprise:</strong> Outside the mass market, specialized manufacturers have pushed VR/MR hardware to extremes of fidelity. Notably, Finland’s <strong>Varjo</strong> released its next-generation <em>Varjo XR-4</em> series headsets in late 2023, aimed at enterprise and government use. The XR-4 features the <strong>highest-resolution displays in any XR device (over 28 million pixels)</strong> combined with <strong>custom aspheric lenses, foveated eye-tracking, and LiDAR depth sensing</strong> . According to Varjo, the result is virtual and mixed reality visuals <em>“practically indistinguishable from natural sight”</em> – a huge claim enabled by human-eye resolution in the center of view and real-time depth scanning. The XR-4 (starting around$3,990) is not for gamers; it’s positioned to <em>“go far beyond consumer-grade headsets”</em> and serve <strong>industrial training, simulation, and design</strong> where top fidelity is required . In fact, over <strong>25% of Fortune 100 companies use Varjo’s technology</strong> for use cases like training pilots and astronauts, speeding up automotive design, and powering medical breakthroughs . Varjo’s systems even achieved regulatory milestones – in 2024, a VR-based pilot training program using Varjo headsets became the <strong>first to earn FAA approval</strong> toward actual pilot certification . This underscores how professional-grade XR hardware is transforming workforce training. Other startups are also innovating in high-performance XR: for example, <strong>Bigscreen Beyond</strong>, a VR startup, began shipping an ultra-compact PC VR headset weighing just ~127g (much lighter than typical 500+g headsets) to improve comfort, and companies like <strong>Pimax</strong> (China) offer wide-FOV, high-resolution VR headsets for enthusiasts. While these remain niche, they showcase state-of-the-art advances in optics, displays, and ergonomics that will trickle down to consumer devices over time.</p>
<p><strong>AR Glasses and Wearables:</strong> In parallel, there’s intense development of <strong>augmented reality glasses</strong> that are lightweight and all-day wearable. Many efforts in the past year focused on making AR glasses more practical for consumers and enterprises. For instance, California-based startup <strong>Xreal (formerly Nreal)</strong> introduced the <em>Xreal Air 2</em> series of glasses in 2024 – lightweight smart glasses that connect to phones/laptops. The new Xreal Air 2 <strong>Ultra</strong> model adds onboard cameras and processors, enabling true <strong>spatial computing experiences comparable to pricier headsets from Apple or Meta, but in a <img src="https://latex.codecogs.com/png.latex?699%20glasses%20form%20factor**%20.%20These%20glasses%20can%20project%20a%20virtual%20screen%20or%203D%20apps%20into%20the%20wearer%E2%80%99s%20view%20while%20remaining%20visibly%20like%20regular%20sunglasses.%20Xreal%20also%20launched%20a%20companion%20device%20(Xreal%20Beam)%20to%20run%20Android%20apps%20and%20content%20for%20the%20glasses%20.%20Their%20strategy%20of%20%E2%80%9Cphone-tethered%20AR%E2%80%9D%20seems%20to%20be%20gaining%20traction%20%E2%80%93%20Xreal%20announced%20in%202024%20that%20it%20has%20**sold%20over%20430,000%20AR%20glasses%20to%20date%20and%20raised"> 60 million for further development</strong> . This indicates a growing consumer appetite for affordable AR eyewear that leverages existing devices. Another notable player is <strong>Magic Leap</strong>, which shifted fully to enterprise AR with its <em>Magic Leap 2</em> headset (released 2022 but rolled out broadly through 2023–24). Magic Leap 2 features one of the <strong>most advanced optic systems in see-through AR</strong>, with a wide field of view, high clarity, and robust occlusion of dark objects. It targets use cases like surgical visualization, remote expert support, and 3D design. Magic Leap made headlines in mid-2024 by <strong>partnering with Google</strong> to combine its optics and AR engineering expertise with Google’s software platforms . This strategic alliance aims to <em>“advance the future of the XR ecosystem with unique and innovative product offerings”</em> – possibly hinting that the next Magic Leap devices will integrate Google’s operating systems or AI capabilities (more on Google’s XR platform below). Beyond these, many other AR wearables emerged: <strong>Lenovo’s ThinkReality</strong> division, Vuzix, Rokid (China), and others released or announced new smart glasses for enterprise (typically offering heads-up info display or video calls for field workers). While true consumer AR glasses with full 3D graphics remain a challenge (due to optics, battery, and privacy concerns), the past year saw clear progress toward <strong>smaller, more powerful AR wearables</strong> that inch closer to everyday glasses.</p>
<p><strong>Supportive Hardware and Haptics:</strong> XR hardware advances are not limited to headsets. There have been improvements in <strong>controllers and input devices</strong> – for example, Meta’s Quest 3 controllers feature improved haptic feedback and inside-out tracking (no external sensors needed), and some VR systems (HTC VIVE, Valve) support <em>hand tracking</em> so users can interact without controllers. In haptics, startups like <strong>HaptX</strong> and <strong>Manus</strong> are refining haptic gloves that provide force feedback for realistic touch in VR, which saw continued R&amp;D and pilot uses (e.g.&nbsp;in VR training for feeling virtual objects). Additionally, <strong>full-body tracking and suits</strong> (such as Teslasuit or Sony’s Mocopi trackers) are being used for motion capture and more immersive experiences, though still niche. In spatial computing, there’s also focus on <strong>better spatial audio</strong> hardware – new headsets (Apple, Meta) incorporate advanced spatial speakers so virtual sounds come from proper directions, enhancing realism. All combined, the hardware ecosystem in 2024–2025 offers a spectrum from cutting-edge high-end XR systems to smaller, more accessible AR/VR devices, setting the stage for broader adoption.</p>
</section>
<section id="software-and-platform-innovations" class="level2">
<h2 class="anchored" data-anchor-id="software-and-platform-innovations"><strong>Software and Platform Innovations</strong></h2>
<p>Hardware gains have been matched by <strong>major software advancements</strong> in AR/VR platforms and tools:</p>
<ul>
<li><strong>Operating Systems and Toolkits:</strong> A significant development came from <strong>Google</strong>, which in December 2024 announced <strong>Android XR</strong>, a new open platform built specifically for XR devices . Developed in partnership with Samsung and Qualcomm, Android XR is essentially an XR-optimized version of Android that will power upcoming headsets and smart glasses. Google is providing a developer preview (with support for familiar tools like ARCore, Android Studio, Unity, OpenXR, etc.) so developers can start building apps for <strong>upcoming Android XR devices</strong> . The first device, codenamed <em>“Project Moohan”</em> by Samsung, is slated for release in 2025 . Android XR also integrates Google’s latest AI assistant (Project <strong>Gemini</strong>) to enable voice and vision intelligence – e.g.&nbsp;a user can ask the glasses about what they’re seeing, get contextual info, translations, or have the AI help with tasks . This shows a trend of <strong>convergence between XR and AI</strong>, making software more intuitive. Apple, similarly, has been evolving its <strong>visionOS</strong> for Vision Pro, adding features like persona avatars and incorporating on-device AI (referred to as <em>“Apple Intelligence”</em> in a 2025 update) for smarter interactions.</li>
<li><strong>Content and Developer Ecosystems:</strong> The XR content ecosystem expanded significantly. Both <strong>Unity and Unreal Engine</strong> – the two leading 3D engines – released updates to better support AR/VR development. For example, Unity’s 2023 and 2024 versions include built-in support for XR plugins, OpenXR standard, and even specific support for <strong>Apple Vision Pro’s unique features</strong> . Epic’s Unreal Engine 5 continues to be used for high-fidelity VR experiences and has templates for AR projects. Cross-platform frameworks like <strong>OpenXR</strong> have gained traction, allowing developers to write an app once and deploy to multiple VR/AR devices. This was underscored by companies like Varjo achieving <strong>OpenXR 1.0 compliance</strong> for their high-end headsets , and by Microsoft and Meta also aligning to OpenXR on their platforms.</li>
<li><strong>XR Cloud Services:</strong> The concept of the <strong>“AR cloud”</strong> (a persistent 3D map of the world for shared AR experiences) saw progress. Niantic’s <strong>Lightship VPS</strong> (Visual Positioning System) expanded to more cities, allowing phones and headsets to localize in specific real-world locations with cm-level accuracy for AR content. Niantic also updated its Lightship AR SDK (e.g.&nbsp;ARDK 3.0) to let developers blend Unity AR experiences with its real-world scanning and multiplayer features . Other players like <strong>Snap</strong> and <strong>8th Wall</strong> (web AR platform) similarly grew their AR cloud offerings, enabling things like location-based AR advertising and city-scale AR games. <strong>Microsoft</strong>, while stepping back from its own VR hardware, continues to offer <strong>Azure Mixed Reality services</strong> (like Azure Object Anchors and Remote Rendering) to enterprise developers building cloud-connected AR applications (e.g.&nbsp;visualizing big 3D models via HoloLens or other devices by streaming from the cloud). We also saw more adoption of <strong>3D content standards</strong>: USD (Universal Scene Description) is being championed by NVIDIA and others for metaverse interoperability, and glTF for 3D models on the web, helping standardize XR content pipelines.</li>
<li><strong>Social and Collaborative Platforms:</strong> In the past year, there have been upgrades to platforms enabling multi-user XR experiences. Meta’s <strong>Horizon Worlds</strong> social VR, while off to a slow start, got improvements and opened to web and mobile users to expand its reach (so people can join VR environments from a phone or PC). Microsoft’s <strong>Mesh for Teams</strong> – which blends mixed-reality avatars and meetings – was showcased and is gradually rolling out, allowing workers to meet as avatars in a shared virtual space via VR or computer. Startups like <strong>Spatial</strong> and <strong>Frame</strong> provided browser-based collaborative VR spaces, used for everything from virtual offices to conferences. The enterprise collaboration space is vibrant, with solutions for virtual training sessions, design reviews in VR, and remote assistance via AR (e.g.&nbsp;using a HoloLens or phone to get a remote expert’s guidance overlayed on machinery). The ongoing challenge has been interoperability, but initiatives through the <strong>Metaverse Standards Forum</strong> (launched 2022) continued through 2024 to align formats and protocols so virtual worlds can connect.</li>
<li><strong>Creation and AI Integration:</strong> Another notable trend is <strong>AI-powered content creation for XR</strong>. Generative AI is being leveraged to create 3D assets, virtual characters, and even entire virtual environments more efficiently. Companies like NVIDIA introduced tools to generate 3D models from 2D images or to assist in scene creation for simulation. Startups (e.g.&nbsp;<strong>Singularity 6</strong> or <strong>Inworld AI</strong>) are making AI-driven NPCs that can interact naturally with users in VR. Snap’s Lens Studio now has AI features that let creators generate AR Lens effects or even create AR filters via text prompts. The Fast Company <em>“most innovative companies”</em> list for 2025 highlights that Snap’s platform now <strong>lets millions of users easily create their own AR Lenses with AI tools</strong> . This convergence of XR and AI lowers the barrier to content creation and will likely yield more dynamic, personalized XR experiences moving forward.</li>
</ul>
<p>Overall, the software stack for XR is maturing: robust OS support (visionOS, Android XR, Windows Mixed Reality), powerful engines and standards (Unity, Unreal, OpenXR), and cloud-backed services to enable rich, shared experiences. This maturation is crucial, as it supports the explosion of use cases for XR across different sectors.</p>
</section>
<section id="major-players-and-industry-developments" class="level2">
<h2 class="anchored" data-anchor-id="major-players-and-industry-developments"><strong>Major Players and Industry Developments</strong></h2>
<p>The XR landscape in 2024–2025 involves both tech giants and agile startups, each driving innovation:</p>
<ul>
<li><strong>Meta (Facebook):</strong> Meta remains the market leader in VR device shipments and continues to invest heavily in the “metaverse” vision. Besides Quest 3 hardware, Meta has been improving its <strong>Quest software platform</strong>, adding features like <strong>Meta Reality</strong> mixed-reality APIs for developers, fitness tracking, and better avatar graphics. It has also acquired VR game studios (e.g.&nbsp;the makers of <em>Iron Man VR</em> and <em>Twisted Pixel</em> in 2023) to bolster content. In 2024 Meta emphasized making its social VR more accessible (Horizon Worlds) and integrating <strong>work tools</strong> – e.g.&nbsp;they partnered with Microsoft so Quest headsets can run Microsoft 365 apps and Teams, aiming at enterprise productivity use . While Meta shelved its earlier consumer AR glasses plans (and its Realtime Ray-Ban glasses remain simple cameras), it reportedly continues R&amp;D on true AR glasses for mid-decade. Globally, Meta’s aggressive pricing of Quest (even cutting Quest 2’s price) and expanding content library (over 5000 apps) have kept it at the forefront, though it faces new competition from Apple and others.</li>
<li><strong>Apple:</strong> Apple’s <strong>Vision Pro</strong> launch in 2024 is perhaps the most significant new entry to XR. Apple’s approach focuses on a <strong>premium, productivity and creativity-oriented MR device</strong> rather than pure VR gaming. With its strong ecosystem integration (e.g.&nbsp;iCloud, Apple services, and continuity with Mac/iPhone apps), Apple is carving a niche for “spatial computing” in work and personal productivity (think virtual monitors, 3D design, immersive FaceTime calls, etc.). The company has also been courting developers – by late 2024, Apple announced <em>over 600 Vision Pro apps ready at launch</em> , from office apps to games and entertainment. While initial sales are limited (US only, expanding to more countries in 2025), Apple’s entry has spurred investment across the XR industry, as many see it as a catalyst for mainstream acceptance of AR/MR. There are also rumors of Apple working on a lower-cost version of the headset or AR glasses in the coming years, but nothing official yet. In the meantime, Apple continues to enhance <strong>ARKit</strong> (its AR developer framework for iOS), which in 2024 gained improvements in scene understanding and rendering, benefiting the massive installed base of iPhones and iPads that do mobile AR.</li>
<li><strong>Microsoft:</strong> Microsoft has pivoted its XR strategy. In October 2024, Microsoft <strong>ceased production of its HoloLens 2 AR headset</strong>, indicating no immediate successor hardware . The company announced it will <em>“no longer produce HoloLens 2”</em> and has signaled last-call for purchases, though it will <strong>support existing devices until end of 2027</strong> . This marked an end (at least for now) of Microsoft’s ambitious HoloLens hardware line that began in 2015. However, Microsoft isn’t exiting XR altogether – instead it stated it will <em>“continue to invest in mixed reality through first-party software solutions and by partnering with the broader mobile and MR hardware ecosystem”</em> . Indeed, Microsoft has taken a partnership route: it has collaborated with Meta to bring <em>Windows 11 streaming</em> and Xbox Cloud Gaming to Quest headsets, and it remains involved in the U.S. military’s <strong>IVAS</strong> project (an AR combat headset based on HoloLens tech) in partnership with defense firms . Microsoft’s <strong>Mesh</strong> platform also received updates (showcased at Ignite 2024), indicating that the company sees its strength in <strong>collaboration software and cloud services for XR</strong>, rather than consumer hardware. We can expect Microsoft to focus on enabling its software (Teams, Dynamics 365, Azure MR services) to work on others’ XR devices.</li>
<li><strong>Google:</strong> After the ill-fated Google Glass and a canceled early AR project, Google has re-emerged in XR through alliances and Android. The <strong>Android XR</strong> initiative (as detailed above) positions Google to be a key software provider for many OEMs building XR hardware (much as Android is ubiquitous in phones). Google’s partnership with <strong>Samsung</strong> is especially noteworthy – Samsung is expected to release a mixed-reality headset (likely a high-end device potentially branded in the Galaxy lineup) using Google’s OS and Qualcomm’s XR chips, possibly by 2025 . Additionally, Google in mid-2024 struck a <strong>partnership with Magic Leap</strong> to collaborate on AR technology , and acquired some R&amp;D teams (reportedly some from HTC’s VR division) . All signs point to Google providing the <strong>software ecosystem (Android, Play Store, AI services)</strong> for multiple XR devices in the coming years – an approach that could rapidly scale XR to many vendors (from smart glasses like Xreal to VR headsets from Lenovo or HTC). Meanwhile, Google continues to integrate AR features into its core products: AR search results and navigation in Google Maps Live View have become more sophisticated, and Google’s <strong>Immersive View</strong> (3D map flythroughs) is bridging the gap to XR by letting users explore cities in a quasi-VR mode on mobile. Although Google doesn’t sell a consumer XR device today, its global influence through Android and cloud means it will remain a major player shaping XR’s future.</li>
<li><strong>Startups and New Entrants:</strong> Beyond Big Tech, a host of startups globally are contributing to XR innovation:
<ul>
<li><em>Hardware Startups:</em> In AR glasses, besides Xreal and Magic Leap, companies like <strong>Rokid</strong> (China) and <strong>Enreal</strong> are releasing consumer AR glasses with cinema-like displays. <strong>Lynx</strong>, a French startup, launched its <em>Lynx-R1</em> mixed reality headset (small form-factor passthrough MR) to backers in 2023 and is iterating on its design, now also a partner in Google’s Android XR program . Enthusiast VR firms like <strong>Pimax</strong> (China) and <strong>StarVR</strong> (Taiwan) push boundaries in specs (e.g.&nbsp;200° field of view, dual 8K displays). <strong>UltraLeap</strong> (UK) provides hand-tracking and mid-air haptic modules that are being integrated into other XR products. There’s also movement in components: firms are developing better <strong>micro-displays</strong> (e.g.&nbsp;OLED-on-silicon, microLED for AR) and <strong>optics</strong> (pancake lenses are now standard in new VR headsets for slimmer profiles, and companies like Lumus or DigiLens are making waveguide optics for transparent AR). These component innovations often come from specialized startups and will feed the next generations of devices.</li>
<li><em>Content &amp; Platform Startups:</em> On the software side, many startups are tackling specific XR verticals. For example, <strong>FundamentalVR</strong> focuses on medical simulation – its platform lets surgeons practice procedures in VR with realistic instrument feedback, and this has shown measurable improvement in surgical training outcomes . <strong>Squint</strong> is a startup that uses AR and AI to train manufacturing workers: its tool can turn a how-to video of a task into an interactive AR guide automatically, helping industrial companies reduce training time . In cultural entertainment, France-based <strong>Excurio</strong> builds multi-user VR experiences for museums and tourism – in 2024 it ran immersive exhibits where up to 100 visitors don VR headsets to explore 19th-century Paris or ancient Egypt together, drawing in new museum audiences . Social VR startups like <strong>VRChat</strong> and <strong>Rec Room</strong> continue to thrive with user-generated worlds and millions of users (notably, Rec Room reported over 3 million monthly VR users in 2024). <strong>Roblox</strong> also launched on VR (Meta Quest) in late 2023, bringing its huge creator community into VR and exemplifying how gaming platforms are merging with XR. Startups in <strong>XR for retail</strong> (like AR try-on platforms), <strong>XR in real estate</strong> (virtual property tours), and <strong>location-based VR entertainment</strong> (VR arcades such as Sandbox VR, Zero Latency expanding locations worldwide) also saw significant business growth, as discussed in the industry sections below.</li>
<li><em>Global Landscape:</em> It’s worth noting that XR innovation is truly global. In <strong>China</strong>, the government has a national VR/AR industry plan (to ship tens of millions of devices by 2025) and companies like ByteDance (with Pico headsets) and Tencent are heavily investing. ByteDance’s <strong>Pico 4</strong> headset, launched late 2022, continued to sell in Asia and Europe in 2023–24 as a strong Quest alternative (though ByteDance has not entered the US market). Several Chinese AR glasses (Rokid, Nreal/Xreal, etc.) are among the first consumer shipments in that category. <strong>South Korea’s</strong> Samsung, as mentioned, is re-entering XR in partnership with Google, and its rival LG is also reportedly working on XR devices. <strong>Europe</strong> hosts high-end players like Varjo and many enterprise-focused XR software firms, and the EU has even discussed “metaverse regulation” and funding programs to support XR content creation . The Middle East, notably Saudi Arabia and UAE, have poured money into metaverse and XR initiatives as part of future-city projects (e.g.&nbsp;NEOM’s digital twin). This global effort means breakthroughs can come from anywhere – for instance, India’s Reliance Jio launched an affordable JioDive VR viewer for smartphones in 2023 to drive VR content in its market, and in Africa XR startups are exploring uses like virtual education in remote regions. Cross-border partnerships (such as Magic Leap + Saudi investment, or Microsoft + Indian HoloLens developers) have also grown. In short, XR is no longer confined to Silicon Valley – it’s a worldwide race, blending big tech resources with startup agility.</li>
</ul></li>
</ul>
</section>
<section id="applications-and-industry-trends" class="level2">
<h2 class="anchored" data-anchor-id="applications-and-industry-trends"><strong>Applications and Industry Trends</strong></h2>
<p>XR technologies are increasingly applied across a wide range of industries. Below we highlight key developments and trends in <strong>gaming/entertainment, healthcare, education, enterprise (workplace and industrial), and retail/marketing</strong>, demonstrating how AR/VR is transforming each sector:</p>
<section id="gaming-and-entertainment" class="level3">
<h3 class="anchored" data-anchor-id="gaming-and-entertainment"><strong>Gaming and Entertainment</strong></h3>
<p>Gaming remains a primary driver of consumer VR adoption and a fertile ground for AR innovation. In the past year, <strong>VR gaming</strong> reached new heights: the <strong>PlayStation VR2</strong> (launched February 2023) brought high-end VR to console players, with over 100 titles by late 2024 and blockbuster games like <em>Horizon: Call of the Mountain</em> and VR modes for <em>Gran Turismo 7</em> and <em>Resident Evil 8</em>. While PS VR2 initial sales were modest, the growing library and Sony’s backing have solidified console VR’s presence. On PC and standalone VR, games like <em>Beat Saber</em> (now owned by Meta) continued to top the charts, and new hits emerged – e.g.&nbsp;the tactical shooter <strong>“Ghosts of Tabor”</strong> surpassed 1 million players and <img src="https://latex.codecogs.com/png.latex?30M%20revenue%20within%20a%20year%20of%20its%202023%20early%20access%20launch,%20indicating%20viable%20success%20for%20VR-first%20titles%20%20.%20Meta%E2%80%99s%20Quest%20platform%20saw%20more%20developers%20cross%20the"> 1M revenue mark, and the Quest 3’s improved performance is enabling more <strong>graphically rich VR games</strong> on standalone hardware. Moreover, 2024 saw the arrival of major gaming platforms into VR: as mentioned, <strong>Roblox</strong> released on Quest, allowing its huge user base to experience user-made games in VR, and <strong>Minecraft</strong> and <strong>Fortnite</strong> both have unofficial or official pathways to VR play, signaling that big franchises are acknowledging VR demand.</p>
<p>In <strong>augmented reality gaming</strong>, the mobile AR phenomenon persists beyond Pokémon GO. Niantic, the studio behind Pokémon GO, launched <strong>Monster Hunter Now</strong> in late 2023, translating Capcom’s popular franchise into an AR mobile game with location-based monster battles. It quickly amassed millions of downloads, reinforcing that well-designed AR games can engage mainstream audiences. Companies are also blending AR with physical play – for example, <strong>Nintendo’s Mario Kart Live</strong> and <strong>Hot Wheels Rift</strong> use toy cars and AR to merge real and virtual play spaces at home. <strong>Location-based entertainment (LBE)</strong> bounced back as pandemic restrictions eased: AR scavenger hunts, AR-enhanced theme parks, and VR arcade experiences drew crowds. A notable trend is <em>mixed reality gaming</em> using passthrough: with devices like Quest 3 and Apple’s Vision Pro, developers can create games that insert virtual gameplay into your real room (fighting aliens bursting through your walls, etc.). Early MR games are already on Quest (like <em>I Expect You To Die: Home Sweet Home</em>, an escape-room that uses your actual room in puzzles). This MR gaming blur real and virtual, providing a fresh twist that could attract gamers who might find pure VR too isolating.</p>
<p>Social entertainment in XR also grew. <strong>Virtual concert platforms</strong> like Fortnite’s Party Royale and VRChat hosted more immersive music events. There were also experiments in blending real and virtual concerts – e.g.&nbsp;in November 2024, Apple debuted <em>The Weeknd’s immersive music experience</em> for Vision Pro , and in early 2025 Apple partnered with rock band Metallica to release a <strong>concert filmed with 3D 180° cameras</strong> for Vision Pro , allowing fans to feel almost present at live shows. These indicate how XR is opening new distribution channels for music and live events, a trend likely to continue.</p>
</section>
<section id="healthcare-and-wellness" class="level3">
<h3 class="anchored" data-anchor-id="healthcare-and-wellness"><strong>Healthcare and Wellness</strong></h3>
<p>Healthcare has embraced AR and VR for training, treatment, and patient education at an accelerating pace. <strong>Medical training</strong> using VR has proven effective: platforms like <strong>Osso VR</strong> and <strong>FundamentalVR</strong> provide realistic surgical simulations, enabling surgeons and medical students to practice procedures in a safe virtual environment. Hospitals report that this leads to improved skill and confidence – for instance, FundamentalVR’s solution has been used to train surgeons on new orthopedic techniques, letting them repeat procedures virtually and receive performance feedback . By 2024, some surgical residency programs even grant credit for VR training hours. VR is also used to train for rare or complex surgeries that a surgeon might not often encounter in real life, thereby improving preparedness and patient outcomes.</p>
<p><strong>Therapy and mental health</strong> applications of XR have also expanded. The FDA-approved VR therapy for chronic pain (AppliedVR’s EaseVRx) gained traction in clinics as a digital therapeutic: patients use a VR headset at home to undergo cognitive-behavioral therapy modules in calming virtual environments, which has been shown to reduce pain levels. VR exposure therapy for phobias and PTSD is increasingly offered by therapists, as it allows controlled exposure to fears (like virtual heights, or simulated combat scenarios for veterans with PTSD) in a safe setting. Notably, some veterans’ hospitals in the U.S. and mental health services in Europe have invested in VR systems for this purpose. <strong>VR meditation and wellness</strong> apps boomed for consumers – titles like <em>Tripp</em> (a guided meditation in immersive visuals) saw user growth as people seek stress relief in VR. On the AR side, mental health researchers have experimented with AR for treating psychosis (e.g.&nbsp;projecting calming digital avatars to talk patients through episodes) or using AR games to encourage physical exercise and social interaction for those struggling with depression.</p>
<p>In <strong>clinical healthcare</strong>, AR is aiding surgeons and practitioners during actual procedures. For example, some neurosurgeons now wear see-through AR headsets in the operating room to project MRI and CT scans as a hologram “heads-up display” aligned onto the patient, improving precision. HoloLens and Magic Leap have been trialed for displaying a patient’s vein maps, identifying tumors during surgery, or guiding catheter insertions – effectively giving surgeons x-ray vision. Companies like <strong>Medivis</strong> and <strong>SentiAR</strong> developed AR software that lets doctors see 3D anatomy floating over patients in real-time. Early studies report that this can reduce surgery time and error rates. In dentistry as well, AR is used to project guides for drilling and implant placement onto AR glasses.</p>
<p>Another active area is <strong>remote healthcare and assistance</strong>. AR glasses can enable an on-site nurse or junior doctor to consult with a remote specialist who “sees what they see” through the glasses and can annotate in the wearer’s view (telepresence). This has been piloted in emergency medicine and paramedic response (e.g.&nbsp;an ER doctor remotely guiding a paramedic through a complex intervention via AR overlays). During the pandemic, such use cases were explored to minimize staff exposure – and they continue to find utility in improving access to expertise in underserved areas.</p>
<p>Lastly, the <strong>health and fitness</strong> sector has seen XR crossover: VR fitness apps (like supernatural boxing, dancing games) are hugely popular on Quest, turning workouts into engaging games. Some gyms started offering VR workout stations. And AR is used in consumer wellness – e.g.&nbsp;smart mirrors with AR overlays for form correction, or AR yoga apps that project a virtual instructor into your room. The coming years will likely see regulated medical XR solutions (digital therapeutics) become more common alongside the still-growing fitness and wellbeing apps.</p>
</section>
<section id="education-and-training" class="level3">
<h3 class="anchored" data-anchor-id="education-and-training"><strong>Education and Training</strong></h3>
<p>Education has been a promising arena for AR/VR, and the past year saw wider adoption from K-12 to higher ed and corporate training. <strong>Classroom learning with AR</strong> became more accessible through tablet and smartphone apps. For example, using an iPad’s AR capabilities, students can now see a 3D model of the solar system hovering over their desk or explore a human cell in AR during biology class. In 2024, publishers released more <strong>AR-enabled textbooks</strong> – scanning a page with a device might bring up interactive 3D content, like historical figures coming to life or chemical structures popping out. Some schools have experimented with class sets of VR headsets for virtual field trips: Google’s VR Expeditions program (now integrated into Google Arts &amp; Culture) offers guided VR tours of historical sites, underwater ecosystems, outer space, etc., giving students immersive exposure that would be impossible otherwise. The <strong>cost of VR hardware dropping</strong> has helped here – a Quest 2 or similar can be under$300, making class deployments more feasible, and some educational institutions use simpler smartphone-based VR for introductory use.</p>
<p>At the university level, XR is increasingly part of the curriculum itself. A notable example is <strong>Texas A&amp;M University’s Visualization program</strong>, which integrated AR/VR production so students learn to create immersive content as part of their studies . More universities now have dedicated XR labs or even degrees in immersive media. They use VR for virtual labs (letting students practice chemistry experiments in VR, avoiding material costs and safety issues) or AR for engineering and medical anatomy classes. Remote learning also benefited: in a “metaversity” trend, a few colleges (like Morehouse College in the US) ran some courses entirely in VR – students and instructor meet as avatars in a virtual classroom or even on a virtual replica of campus, which reportedly increased engagement for remote learners.</p>
<p><strong>Corporate and skills training</strong> is one of the strongest enterprise use cases for XR. In 2024, more Fortune 500 firms expanded VR training programs for employees. Examples: Walmart, which had already deployed 17,000 Oculus Go headsets for employee training a few years ago, upgraded to interactive Quest training modules to teach new associates in simulated store environments. <strong>Accenture</strong> has used thousands of VR headsets to onboard new hires with a virtual campus called the Nth Floor, where employees learn about company culture and meet colleagues’ avatars. VR training is shown to improve retention of information and is used for everything from <strong>safety drills</strong> (e.g.&nbsp;practicing warehouse machinery operation or emergency evacuations in VR) to <strong>soft skills</strong> (practicing public speaking or customer interactions with virtual avatars). A study by PwC found VR learners can train up to 4x faster than in classroom settings and with greater emotional connection.</p>
<p>On the AR side, <strong>on-the-job training and guidance</strong> is big: a technician wearing AR glasses can see step-by-step instructions overlaid on equipment as they perform a task. This “see what I see” guidance not only trains them but also ensures quality. Startups like Squint (as mentioned) use AR to convert expert procedures into on-demand training overlays . <strong>Field service technicians</strong> in companies like Boeing and GE have AR apps that recognize a machine and overlay wiring diagrams or highlight parts that need maintenance. This year, some companies began pairing AR with AI copilots – an engineer can ask an AR assistant (via voice) for troubleshooting help and the system, using documentation, can highlight likely problem areas on the equipment through the AR display. Such intelligent AR training reduces dependence on printed manuals and can significantly cut training time for complex tasks .</p>
<p>In summary, XR in education and training is moving past pilot stage into broader usage as hardware becomes more affordable and success stories accumulate. It is enhancing experiential learning and helping workers acquire skills in safer, more efficient ways.</p>
</section>
<section id="enterprise-and-industry" class="level3">
<h3 class="anchored" data-anchor-id="enterprise-and-industry"><strong>Enterprise and Industry</strong></h3>
<p>In enterprise settings – from offices to factories – XR is driving new efficiencies and ways of working. A few major trends in the past year include:</p>
<ul>
<li><strong>Remote Collaboration &amp; Meetings:</strong> Businesses have been exploring VR meeting solutions as a complement to Zoom. With tools like Meta’s Horizon Workrooms, Microsoft Mesh, or Spatial, teams can gather in a virtual conference room as avatars, manipulate 3D models together, or whiteboard on virtual screens. While not yet mainstream for everyday meetings, these have proven valuable for specific cases like design reviews (where a 3D model can be examined by all participants from any angle) or when teams are globally distributed and want a greater sense of presence than video calls. As an example, automaker BMW has used VR for internal design evaluations of car prototypes, saving travel by having designers and engineers meet in VR to inspect life-size virtual cars. Another example: architecture firms during 2024 often met with clients inside a VR model of a building to walk them through the design. The enterprise push is also evidenced by partnerships – <strong>Meta and Microsoft’s</strong> collaboration means Quest headsets can natively run Microsoft Teams, and Microsoft 365 apps are accessible in VR, aiming to integrate XR with the regular work toolbox . Still, challenges like device comfort for long meetings and software maturity mean these are in early stages, but interest remains high.</li>
<li><strong>Design, Prototyping and Visualization:</strong> XR has become a powerful tool in product design and engineering. <strong>Virtual prototyping</strong> in VR can save significant time and cost by allowing designers to visualize a product at scale without physical mockups. Companies like Ford have long used VR caves for car design; now they are moving to headsets like Varjo XR for even higher fidelity. In 2024, Varjo noted that using its XR headsets, carmakers cut down design cycles drastically by spotting issues in VR models early . Similarly, aerospace companies use VR to review airplane or spacecraft designs collaboratively. <strong>Augmented reality</strong> is used on factory floors to overlay digital twins of equipment for planning layout changes or to compare CAD designs with the as-built reality for quality control.</li>
<li><strong>Industrial Maintenance and Logistics:</strong> AR is having a strong impact in maintenance, repair, and operations. Wearing AR smart glasses, technicians can see overlay instructions or even live IoT data from a machine (e.g.&nbsp;temperatures, pressures) while working, which speeds up diagnostics. This year, more companies moved from pilot to deployment – for example, <strong>airlines</strong> have started using AR for aircraft maintenance: an engineer can pull up wiring schematics in their view rather than flipping through manuals. <strong>Logistics and warehousing</strong> benefit from AR too: “vision picking” solutions give warehouse pickers AR glasses that highlight the item to pick on a shelf and the optimal route, improving speed and reducing errors. DHL and others have reported double-digit efficiency gains from such AR implementations in warehouses.</li>
<li><strong>Field Service and Remote Expert Assistance:</strong> As touched on in healthcare, this applies to enterprise broadly – a field service worker can call a remote expert who sees a live AR video feed and can draw annotations onto the worker’s view (“turn this knob”, “check this part”). Enterprises in telecom, manufacturing, and energy have expanded use of these AR remote assistance tools to reduce downtime and travel. For example, an offshore oil rig worker can get an expert’s guidance via AR instead of that expert flying out to the rig for a minor issue. This trend has been facilitated by improved connectivity (5G, etc.) and robust software platforms (like Microsoft Dynamics 365 Guides or AR startup platforms like Help Lightning).</li>
<li><strong>Data Visualization and Analytics:</strong> XR offers new ways to comprehend big data. In 2024 some financial and data firms experimented with VR “war rooms” where analysts visualize multidimensional data in 3D space or monitor multiple virtual screens more effectively than limited physical monitors. Likewise, cybersecurity teams used VR simulations to visualize network topologies and cyber attack patterns (a startup called <strong>Haiku</strong> even built a “cybersecurity metaverse” for training and operations ). While niche, these illustrate XR’s potential beyond physical tasks – reimagining how we interface with abstract data and complex systems.</li>
<li><strong>Enterprise Adoption and ROI:</strong> A notable development is that enterprises now look at XR more pragmatically – success is measured by ROI, not hype. Over the past year, evidence of ROI has grown. A report by PwC and XRA (XR Association) highlighted that well-implemented VR/AR training can yield significant ROI in reduced training time and improved safety. Companies are also recognizing XR’s role in attracting younger, tech-savvy talent and in preserving institutional knowledge (recording expert procedures in AR for future training, etc.). Many large organizations have an “XR strategy” or dedicated innovation teams for XR now. That said, challenges remain such as device management at scale, security (especially for AR recording devices in sensitive workplaces), and content creation costs. The trend is that XR is moving from experimentation to a <em>productivity tool</em> in various enterprise workflows, especially where spatial understanding or remote presence is valuable.</li>
</ul>
</section>
<section id="retail-marketing-and-consumer-engagement" class="level3">
<h3 class="anchored" data-anchor-id="retail-marketing-and-consumer-engagement"><strong>Retail, Marketing and Consumer Engagement</strong></h3>
<p>XR is reshaping how consumers discover and interact with products, both online and in stores, leading to the rise of “immersive commerce”:</p>
<ul>
<li><strong>AR Shopping and Virtual Try-Ons:</strong> By 2024, using AR to try products before purchase became commonplace. <strong>Retail accounts for an estimated 55% of AR use cases in 2024</strong> by one analysis . Shoppers can virtually <strong>try on clothes, jewelry, or cosmetics</strong> through AR filters on their smartphone – for instance, Amazon’s app and Walmart’s app allow customers to see how a shirt or dress might look on their own body using AR and AI (some use a single photo to realistically overlay outfits). Beauty brands like L’Oréal (through its Modiface tech) let users test different makeup shades in AR with high fidelity. Eyewear retailers (Warby Parker, Zenni, etc.) have AR glasses try-on so customers can see frames on their face. This has proven to increase conversion rates – Snap reported that interacting with AR try-on lenses makes users <strong>94% more likely to purchase</strong>, and Threekit (a 3D commerce platform) cited up to <strong>40% conversion lift</strong> for retailers using AR . Such stats have driven many brands to adopt AR either in their own apps or through <strong>Snapchat</strong>, <strong>Instagram</strong>, and <strong>TikTok</strong> which all offer AR lens shopping experiences. In 2024, Snap even launched a business unit <em>Snap AR Enterprise Services</em> to build AR try-on for other retailers, indicating demand for turnkey AR commerce solutions.</li>
<li><strong>In-Store AR Experiences:</strong> Brick-and-mortar retail also uses AR to enhance shopping. Stores deployed AR mirrors or kiosks – e.g.&nbsp;cosmetic stores have “magic mirrors” where you stand in front of a screen and see virtual makeup applied to your face in real-time. Furniture retailers like <strong>IKEA</strong> and <strong>Home Depot</strong> have AR apps to visualize furniture or paint colors in your home (you can point your phone at your living room and see a sofa or a new wall color at true scale). Some grocery stores in Asia introduced AR navigation: hold up your phone and arrows or mascot characters appear on the floor guiding you to the right aisle or offering promotional info above products. Additionally, AR is used in product packaging – scanning a product with your phone might show an interactive animation or additional info (for instance, a cereal box AR experience for kids). These efforts aim to make in-person shopping more engaging and informative.</li>
<li><strong>Virtual Stores and Showrooms:</strong> A few brands have experimented with fully virtual shopping environments. For example, “metaverse” shopping malls or showrooms where your avatar can walk around and view 3D products. While still experimental, luxury brands have created VR boutiques or spaces in platforms like Decentraland and Roblox to build brand engagement among younger consumers. Car companies have virtual showrooms: prospective buyers can put on a VR headset at a dealership (or their own device at home) and virtually sit in a car’s interior, customize features, and take a virtual test drive. During 2024, Ferrari and Lucid Motors rolled out VR showroom experiences. Even after a purchase, AR is used for product onboarding – e.g.&nbsp;when you buy a new appliance, an AR manual can show you how to install and use it by overlaying instructions on the device through your phone.</li>
<li><strong>Advertising and Marketing:</strong> AR advertising grew substantially via social media lenses and WebAR. Brands find AR lenses a compelling way to engage – millions of Snapchat users play with sponsored AR filters (turning into a character, trying a product in AR, etc.), which increases brand recall. In late 2023, Snap reported that <strong>over 250 million Snapchat users engage with AR every day</strong>. TikTok introduced AR ads that let users virtually place a new TV in their room or see a 3D car model. WebAR (AR experiences launched via a browser, no app needed) also lowered friction for campaigns – for example, a QR code on a bus shelter could instantly launch an AR game or product demo on your phone. Brands like Pepsi, Burger King, and Nike have all run creative AR campaigns (such as “AR scavenger hunts” or interactive AR games tied to promotions). These marketing uses show how AR can capture consumer attention in ways static ads cannot, by making the consumer an active participant.</li>
<li><strong>Consumer Electronics and Media:</strong> XR is even changing how we consume media and electronics. For instance, <strong>virtual screens</strong> in VR are replacing some use of TVs or monitors – apps like Netflix VR or Virtual Desktop let people watch movies on a giant virtual theater screen or have a multi-monitor computer setup in VR, which is appealing to some and could influence future media consumption habits. Augmented reality is blending with entertainment – popular apps like <strong>Pokemon GO</strong> continue to release AR features (a 2024 update allowed players to “buddy up” with their Pokémon in AR more interactively), and new AR-capable devices like the Vision Pro mean there’s a push to create <strong>immersive 3D movies and sports viewing</strong>. Disney has reportedly been working on VR/AR experiences for its content (though they shut down a standalone metaverse division in early 2023, Disney and others are still exploring XR content delivery on platforms like Vision Pro). The concept of <em>“metaverse concerts”</em> and virtual fan experiences (as noted earlier with music) also falls here – essentially new media formats enabled by XR.</li>
</ul>
<p>In retail and consumer engagement, the overarching theme is <strong>blending the digital and physical shopping experience</strong> and making marketing more interactive. Companies are finding real value in these XR tools – whether it’s reducing returns because customers picked the right product via AR try-on, or increasing online sales through rich 3D product views. As AR becomes even more accessible (with webAR and more powerful phone AR every year), we can expect these use cases to become standard parts of the shopping journey globally.</p>
</section>
</section>
<section id="conclusion-and-outlook" class="level2">
<h2 class="anchored" data-anchor-id="conclusion-and-outlook"><strong>Conclusion and Outlook</strong></h2>
<p>In the span of a year, AR, VR and XR technologies have made remarkable strides. We’ve seen <strong>state-of-the-art hardware</strong> like Apple’s Vision Pro and Varjo’s XR-4 push the boundaries of immersive display and input, while <strong>mainstream devices</strong> like Meta’s Quest 3 and Xreal’s AR glasses make quality XR experiences more affordable and accessible. On the software side, the ecosystem is maturing with cross-platform standards (OpenXR), powerful creation tools, and the infusion of AI to make experiences smarter and content creation easier. Businesses across the world – from gaming studios to hospitals, factories to fashion retailers – are finding novel ways to leverage XR to enhance entertainment, improve training and design, boost productivity, and engage customers.</p>
<p>Not everything is perfect: the <strong>“metaverse” hype of 2021-22 has cooled</strong>, and with it some over-ambitious projects have been reeled in (e.g.&nbsp;Meta significantly refocused its spending, Microsoft discontinued its own XR hardware). This healthy recalibration means the industry is now focusing on <strong>tangible value</strong> and usability. Key challenges remain such as miniaturizing AR hardware to true glasses, improving battery life and comfort for long-term use, and ensuring privacy/security as these devices proliferate. There are also policy and societal questions being addressed – for instance, how to establish norms for wearing AR glasses in public, and how to make XR content accessible to people with disabilities.</p>
<p>Looking ahead through 2025, several trends are anticipated:</p>
<ul>
<li><strong>Mixed Reality Convergence:</strong> Devices that can do both VR and passthrough AR (mixed reality) will likely dominate high-end consumer and enterprise segments. We expect <strong>more MR headsets</strong> in the vein of Quest 3 and Vision Pro from other players (Samsung’s device, potentially an HTC Vive update, etc.). This convergence gives users flexibility in one device, and could hasten adoption as the use cases multiply.</li>
<li><strong>Lighter, More Mobile XR:</strong> There’s an industry push towards <strong>all-day-wearable AR glasses</strong>. By late 2025, we may see the first generation of consumer AR glasses that look close to normal eyewear, likely initially tethered to phones for compute. Progress in waveguide optics, microLED displays, and dedicated AR chips (like Qualcomm’s AR2 platform) will drive this. VR headsets will continue to get lighter with innovations like pancake lenses and better weight distribution (as evidenced by devices like the Bigscreen Beyond and Meta’s research into compact optics). This will reduce fatigue and broaden who can use XR comfortably.</li>
<li><strong>Integration with 5G and Cloud:</strong> As 5G networks roll out globally, XR devices will increasingly leverage cloud rendering and edge computing. This means lighter devices can offload heavy graphics processing to the cloud, streaming the results – enabling high-fidelity experiences on portable glasses. Carriers in Asia, Europe, and North America are already partnering with XR companies to ensure their networks are ready for AR cloud services. By 2025, we may see the first <strong>cloud XR</strong> apps that truly require 5G (e.g.&nbsp;city-wide multiplayer AR games or real-time remote expert guidance with 4K video overlays).</li>
<li><strong>Content Explosion:</strong> With more devices in consumers’ hands, content creation will explode. Expect a wave of <strong>new AR games and utilities</strong> taking advantage of phone and glasses capabilities, as well as more <strong>premium VR titles</strong> as the installed base grows. The entry of companies like Apple often galvanizes developers – we’re likely to see innovative apps in the Vision Pro’s ecosystem (productivity, creativity, entertainment) that rethink how we use computers in 3D. Likewise, Google’s Android XR platform could spur a diverse range of apps across many device makers. Tools that let non-programmers create XR content (using AI or low-code platforms) will also democratize development, meaning more user-generated content in the XR space.</li>
<li><strong>Industry Standards and Interoperability:</strong> Through bodies like the XR Association and Metaverse Standards Forum, 2024 saw movement toward common standards. In 2025, we anticipate <strong>better interoperability</strong> – perhaps avatars that can travel between platforms, or digital assets that can be owned and used across multiple virtual environments (an area where blockchain advocates see a role, although mainstream XR has mostly de-emphasized crypto after the metaverse bubble burst). Governments are also paying attention: the <strong>EU is discussing metaverse regulatory guidelines</strong> , and countries like South Korea are investing in open “metaverse platforms” that aren’t dominated by a single company. All this suggests that cooperation will be key to growing the XR pie for everyone.</li>
</ul>
<p>In conclusion, the state-of-the-art in AR, VR, and XR as of 2024–2025 is one of <strong>dynamic growth and maturation</strong>. What was once confined to labs and niche enthusiast communities is now a global industry delivering real business value and captivating experiences. From a gamer battling robots in her living room with a mixed reality headset, to a surgeon practicing a new technique in VR, to a student exploring an ancient world in AR – extended reality is touching more lives in more ways than ever. The coming years promise further convergence of our digital and physical worlds, with XR technologies playing a central role in how we work, learn, play, and connect. The excitement now is grounded in progress: the long-envisioned potential of AR and VR is steadily becoming reality, one innovative step at a time.</p>
</section>
<section id="references" class="level2">
<h2 class="anchored" data-anchor-id="references">References</h2>
<p>http://apple.com/newsroom/2024/01/apple-vision-pro-available-in-the-us-on-february-2</p>
<p>https://about.fb.com/news/2023/09/meet-meta-quest-3-mixed-reality-headset/#</p>
<p>https://www.theverge.com/2024/10/1/24259369/microsoft-hololens-2-discontinuation-support</p>
<p>https://blog.google/products/android/android-xr/</p>
<p>https://www.magicleap.com/newsroom/magic-leap-and-google-partnership</p>
<p>https://www.auganix.org/xr-news-varjo-announces-new-xr-4-series-of-headsets/</p>
<p>https://www.electronicsweekly.com/news/business/q3-ar-vr-headsets-return-to-growth-2024-12</p>
<p>https://www.fastcompany.com/91269074/augmented-virtual-reality-most-innovative-companies-2025</p>


</section>

 ]]></description>
  <category>research-notes</category>
  <category>opinions</category>
  <guid>https://ghif.github.io/posts/2025-05-19-state-of-the-art-ar-vr-xr/</guid>
  <pubDate>Mon, 19 May 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Melatih Vision Transformer dengan Keras 3</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2025-03-21-training-vision-transformer-keras/</link>
  <description><![CDATA[ 




<p>Arsitektur Transformer (<a href="https://arxiv.org/pdf/1706.03762">Vaswani et al.&nbsp;2017</a>) telah menjadi fenomena pada bidang AI, melahirkan berbagai model fondasi (<em>foundational models</em>) terutama untuk generative AI. Arsitektur ini merevolusi pendekatan pemecahan masalah dengan memanfaatkan <em>attention mechanism</em> (<a href="https://arxiv.org/pdf/1409.0473">Bahdanau et al.&nbsp;2015</a>) dalam ranah pemrosesan bahasa alami (Natural Language Processing/NLP). Namun, sebelum tahun 2020, penerapan Transformer di bidang Computer Vision (CV) belum mencapai keberhasilan yang serupa seperti di NLP — model-model berbasis convolution networks masih mendominasi ranah CV.</p>
<p>Vision Transformer (ViT) (<a href="https://arxiv.org/pdf/2010.11929">Dosovitskiy et al.&nbsp;2021</a>) dirancang untuk menjadi solusi bagi permasalahan di Computer Vision, hanya dengan mengandalkan <em>attention mechanism</em> / tanpa menggunakan lapisan <em>convolution</em>. ViT terinspirasi dari skalabilitas pelatihan Transformer — performa arsitektur Transformer dapat terus meningkat seiring penambahan data latih, lebih baik dibandingkan <em>sequence model</em> lainnya, e.g., <em>recurrent networks</em>.</p>
<p>ViT bukan yang pertama dalam mengajukan ide untuk mengganti <em>convolution</em> sepenuhnya dengan <em>attention mechanism</em>. Terdapat beberapa percobaan sebelumnya yang melakukan hal tersebut (<a href="https://arxiv.org/pdf/1906.05909">Ramachandran et al.&nbsp;2019,</a> <a href="https://arxiv.org/pdf/2003.07853">Wang et al.&nbsp;2020</a>), namun terkendala kompleksitas komputasi dikarenakan desain mekanisme <em>attention</em> yang lebih kompleks dibandingkan Transformer versi awal. ViT mencoba kembali lagi ke mekanisme <em>attention</em> yang lebih simpel. Dengan beberapa trik khusus, ViT mampu menawarkan performa yang setara atau lebih baik dibandingkan model berbasis <em>convolution</em>.</p>
<p>Pada paper (<a href="https://arxiv.org/pdf/2010.11929">Dosovitskiy et al.&nbsp;2021</a>), dilaporkan hasil evaluasi untuk pemecahan problem klasifikasi objek, bahwa ViT memiliki performa agak sedikit dibawah ResNet (model state-of-the-art berbasis convolution) jika jumlah data latih berukuran medium, misalnya seperti pada dataset ImageNet dengan sampel gambar sebanyak ~1 juta. Namun pada kasus dengan data latih yang lebih banyak (14 - 300 juta), ViT memiliki performa lebih baik dibandingkan ResNet. Hal ini secara empiris membuktikan hipotesis skalabilitas pelatihan dari ViT dibandingkan model berbasis <em>convolution</em>.</p>
<p>Artikel ini membahas implementasi arsitektur dan pelatihan ViT dengan menggunakan Keras 3.</p>
<section id="arsitektur-vision-transformer-vit" class="level2">
<h2 class="anchored" data-anchor-id="arsitektur-vision-transformer-vit">Arsitektur Vision Transformer (ViT)</h2>
<p>Agak berbeda dengan model asal Transformer yang berbentuk <em>encoder-decoder</em> karena dirancang untuk dilatih secara <em>self-supervised</em> pada data sekuensial, ViT merupakan model <em>encoder-only</em> yang dilatih secara <em>supervised.</em> Namun, arsitektur <em>encoder</em> ViT sangat mirip dengan encoder pada Transformer<em>,</em> yaitu tersusun dari beberapa blok lapisan yang didalamnya terdapat <em>multi head attention</em>.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-03-21-training-vision-transformer-keras/media/Screenshot_2025-03-09_at_13.54.13.png" class="img-fluid figure-img"></p>
<figcaption>Screenshot 2025-03-09 at 13.54.13.png</figcaption>
</figure>
</div>
<p>Perbedaan lainnya adalah bagaimana input diproses. Desain Transformer secara natural cocok untuk memproses data sekuensial seperti teks, namun tidak serta merta cocok untuk data gambar. ViT mengakalinya dengan memecah gambar menjadi kotak-kotak kecil (diistilahkan sebagai <em>patches</em>), misalnya berukuran 16 x 16 untuk tiap patch, yang dianggap sebagai token untuk diproses pada lapisan berikutnya.</p>
<p>Tidak hanya itu, tiap-tiap patch akan diasosiasikan dengan informasi indeks posisi, untuk secara eksplisit menyatakan bahwa, misalnya, “<em>patch</em> A berada di posisi ke-1”.</p>
<p>Secara umum, arsitektur ViT tersusun dari operasi-operasi berikut:</p>
<ol type="1">
<li><strong>Patch Extractor</strong>: memecah gambar menjadi kotak-kotak kecil (<em>patches</em>)</li>
<li><strong>Patch Encoder</strong>: mengkonversi <em>patch</em> menjadi <em>embeddings</em> melalui proyeksi linear yang hasilnya diasosiasikan dengan informasi posisi (<em>position embedding</em>)</li>
<li><strong>Encoder Block</strong>: blok lapisan utama pada ViT yang terdiri dari Multi-Head Attention (MHA) dan Multi-Layer Perceptron (MLP) secara berulang.</li>
<li><strong>Classification Head</strong>: lapisan paling atas yang terkoneksi dengan label supervisi.</li>
</ol>
<p>Kita akan bedah satu-persatu.</p>
<section id="i.-patch-extractor" class="level3">
<h3 class="anchored" data-anchor-id="i.-patch-extractor">I. Patch Extractor</h3>
<p>Untuk membagi suatu gambar jadi kotak-kotak kecil, kita dapat memanfaatkan fungsi <code>keras.ops.image.extract_patches</code> . Cukup untuk memberitahu seberapa besar dimensi <em>patch</em> yang diharapkan (<code>patch_size</code>), fungsi tersebut akan mengembalikan himpunan <em>patches.</em></p>
<p>Misalnya kita masukkan <code>patch_size=16</code> dengan <code>image_dimension=224</code>, maka fungsi tersebut akan memberikan patches sejumlah <code>image_dimension / patch_size = 224 / 16 = 14</code>.</p>
<p>Kemudian, tiap patch yang bentuk aslinya berupa matriks 2D dikonversi menjadi vektor 1D. Sebuah vektor <em>patch</em> memiliki dimensi <code>d = patch_size * patch_size * 3</code>. Rangkaian operasi secara lengkap dapat dilihat pada fungsi <code>extract_patches</code> berikut.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> extract_patches(images, patch_size):</span>
<span id="cb1-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb1-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Extract patches from a batch of images.</span></span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb1-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        images (Tensor): A batch of images with shape (batch_size, height, width, channels).</span></span>
<span id="cb1-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        patch_size (int): The size of the patches to be extracted.</span></span>
<span id="cb1-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns:</span></span>
<span id="cb1-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Tensor: The extracted patches.</span></span>
<span id="cb1-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb1-11">    (_, height, width, channels) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.shape(images)</span>
<span id="cb1-12">    num_patches_h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> height <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> patch_size</span>
<span id="cb1-13">    num_patches_w <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> width <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> patch_size</span>
<span id="cb1-14">    patches <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.image.extract_patches(</span>
<span id="cb1-15">        images,</span>
<span id="cb1-16">        size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>patch_size</span>
<span id="cb1-17">    )</span>
<span id="cb1-18">    patches <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> keras.layers.Reshape((num_patches_h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> num_patches_w, patch_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> patch_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> channels))(patches)</span>
<span id="cb1-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> patches</span></code></pre></div></div>
</section>
<section id="ii.-patch-encoder" class="level3">
<h3 class="anchored" data-anchor-id="ii.-patch-encoder">II. Patch Encoder</h3>
<p>Langkah ini mengkonversi <em>patch</em> menjadi vektor lain melalui proyeksi linear untuk membentuk sebuah <em>embedding</em>. Misalkan terdapat sebuah patch pada posisi ke- <img src="https://latex.codecogs.com/png.latex?t">: <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bx%7D_p%5E%7B(t)%7D%20%5Cin%20%5Cmathbb%7BR%7D%5Ed">, <em>embedding</em> dari patch tersebut, <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bz%7D_p%5E%7B(t)%7D%20%5Cin%20%5Cmathbb%7BR%7D%5Ek">, didapatkan dengan:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7Bz%7D_p%5E%7B(t)%7D%20=%20%5Cmathbf%7BW%7D_p%20%5Cmathbf%7Bx%7D_p%5E%7B(t)%7D%0A"></p>
<p>dimana <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BW%7D_p%20%5Cin%20%5Cmathbb%7BR%7D%5E%7Bk%20%5Ctimes%20d%7D"> merupakan bagian dari parameter yang akan dilatih. Pada implementasi dimensi <img src="https://latex.codecogs.com/png.latex?k"> akan dibuat sama dengan dimensi <img src="https://latex.codecogs.com/png.latex?d">.</p>
<p>Kemudian, <em>embedding</em> dari patch akan dikombinasikan dengan <em>position embedding <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bz%7D_t%5E%7B(t)%7D%20%5Cin%20%5Cmathbb%7BR%7D%5Ek">:</em></p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7Bz%7D_t%5E%7B(t)%7D%20=%20%5Cmathrm%7BEmbedding%7D(t)%0A"></p>
<p>yang berfungsi sebagai identifier bahwa suatu patch berada diposisi tertentu.</p>
<p>Hasil akhir dari <em>patch embeddings</em> berupa:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7Bz%7D%20=%20%5Cmathbf%7Bz%7D_p%5E%7B(t)%7D%20+%20%5Cmathbf%7Bz%7D_t%5E%7B(t)%7D%0A"></p>
<p>Sebagai catatan, pada paper (<a href="https://arxiv.org/pdf/2010.11929">Dosovitskiy et al.&nbsp;2021</a>) juga mengkombinasikan <em>embeddings</em> dari informasi kelas / kategori (<em>class embeddings</em>). Untuk kemudahan implementasi kita abaikan dulu hal tersebut.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> encode_patches(patch, num_patches, projection_dim):</span>
<span id="cb2-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb2-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Encode a single image patch.</span></span>
<span id="cb2-4"></span>
<span id="cb2-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb2-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        patch (Tensor): A single image patch with shape (patch_dim).</span></span>
<span id="cb2-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        num_patches (int): Number of patches the image is divided into.</span></span>
<span id="cb2-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        projection_dim (int): Dimension of the projection space.</span></span>
<span id="cb2-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns:</span></span>
<span id="cb2-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Tensor: The encoded patch.</span></span>
<span id="cb2-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb2-12">    positions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.expand_dims(</span>
<span id="cb2-13">        ops.arange(start<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, stop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>num_patches, step<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb2-14">    )</span>
<span id="cb2-15">    projection <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.Dense(units<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>projection_dim)(patch)</span>
<span id="cb2-16">    position_embedding <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.Embedding(</span>
<span id="cb2-17">        input_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>num_patches, output_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>projection_dim</span>
<span id="cb2-18">    )(positions)</span>
<span id="cb2-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> projection <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> position_embedding</span></code></pre></div></div>
</section>
<section id="iii.-vit-encoder-block" class="level3">
<h3 class="anchored" data-anchor-id="iii.-vit-encoder-block">III. ViT Encoder Block</h3>
<p>Blok lapisan ini merupakan bagian paling krusial pada ViT. Secara umum, Encoder Block ini terdiri dari 2 sub-block utama:</p>
<ol type="1">
<li>Multi Head Attention (MHA): lapisan yang tersusun dari mekanisme <em>self-attention</em> yang diduplikasi sebagaimana halnya pada (Vaswani et al.&nbsp;2017).</li>
<li>Multi Layer Perceptron (MLP): lapisan penutup dari blok yang berisi MLP sederhana.</li>
</ol>
<p>Blok ini juga memanfaatkan <em>skip connection</em> yang diadopsi dari ResNet (He et al.&nbsp;2016), dan juga menggunakan <em>layer normalization</em> dan <em>dropout</em> sebagai regularisasi untuk mengurangi <em>overfitting</em>.</p>
<p>Skeleton dari arsitektur blok ini secara matematis dapat dinyatakan sebagai berikut:<img src="https://latex.codecogs.com/png.latex?h%20=%20%5Cmathrm%7BLayerNorm%7D(z)%20%5C%5C%0Ah%20=%20%5Cmathrm%7BMultiHeadAttention%7D(h)%20%5C%5C%0Ah%20=%20%5Cmathrm%7BDropout%7D(x)%20%5C%5C%0Ah%20=%20h%20+%20z%20%5C%5C%0Ay%20=%20%5Cmathrm%7BLayerNorm%7D(h)%20%5C%5C%0Ay%20=%20%5Cmathrm%7BMLP%7D(y)%20%5C%5C%0Ay%20=%20y%20+%20h">Fungsi di bawah ini mengimplementasikan arsitektur blok tersebut.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> encoder1d_block(inputs, num_heads, hidden_dim, mlp_dim, attention_dropout_rate, dropout_rate):</span>
<span id="cb3-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Create an Encoder 1D block.</span></span>
<span id="cb3-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb3-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        inputs (Tensor): Input tensor.</span></span>
<span id="cb3-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        num_heads (int): Number of attention heads.</span></span>
<span id="cb3-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        hidden_dim (int): Hidden dimension of the feedforward network.</span></span>
<span id="cb3-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        mlp_dim (int): Hidden dimension of the MLP block.</span></span>
<span id="cb3-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        attention_dropout_rate (float): Dropout rate for the attention layer.</span></span>
<span id="cb3-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        dropout_rate (float): Dropout rate for the block.</span></span>
<span id="cb3-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns:</span></span>
<span id="cb3-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Output tensor.</span></span>
<span id="cb3-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb3-14">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Layer normalization 1</span></span>
<span id="cb3-15">    h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.LayerNormalization(epsilon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-6</span>)(inputs)</span>
<span id="cb3-16"></span>
<span id="cb3-17">    key_dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hidden_dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> num_heads</span>
<span id="cb3-18"></span>
<span id="cb3-19">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Multi Head Attention layer</span></span>
<span id="cb3-20">    h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.MultiHeadAttention(</span>
<span id="cb3-21">        num_heads<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>num_heads,</span>
<span id="cb3-22">        key_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>key_dim,</span>
<span id="cb3-23">        dropout<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>attention_dropout_rate</span>
<span id="cb3-24">    )(h, h)</span>
<span id="cb3-25"></span>
<span id="cb3-26">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Dropout</span></span>
<span id="cb3-27">    h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.Dropout(dropout_rate)(h)</span>
<span id="cb3-28"></span>
<span id="cb3-29">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Skip connection 1</span></span>
<span id="cb3-30">    h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> inputs</span>
<span id="cb3-31"></span>
<span id="cb3-32">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># MLP block</span></span>
<span id="cb3-33">    y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.LayerNormalization(epsilon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-6</span>)(h)</span>
<span id="cb3-34">    y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mlp_block(y, mlp_dim, dropout_rate)</span>
<span id="cb3-35">    </span>
<span id="cb3-36">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Skip connection 2</span></span>
<span id="cb3-37">    y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> h</span>
<span id="cb3-38">    </span>
<span id="cb3-39">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> y</span></code></pre></div></div>
<p>Encoder Block kemudian ditumpuk berlapis-lapis sesuai kebutuhan sejumlah <code>num_layers</code>. Sebagai contoh, salah satu versi model ViT yang dibahas oleh (<a href="https://arxiv.org/pdf/2010.11929">Dosovitskiy et al.&nbsp;2021</a>) yaitu model “<strong>ViT-Base”</strong> dimana <code>num_layers = 12</code>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> vit_encoder(inputs, num_layers, num_heads, hidden_dim, mlp_dim, attention_dropout_rate, dropout_rate):</span>
<span id="cb4-2"></span>
<span id="cb4-3">    x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.Dropout(dropout_rate)(inputs)</span>
<span id="cb4-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(num_layers):</span>
<span id="cb4-5">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> encoder1d_block(</span>
<span id="cb4-6">            x, num_heads, hidden_dim, mlp_dim, attention_dropout_rate, dropout_rate</span>
<span id="cb4-7">        )</span>
<span id="cb4-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> x</span></code></pre></div></div>
<p>Lapisan-lapisan arsitektur yang telah dibahas sebelumnya dapat dienkapsulasi menjadi sebuah <em>backbone</em>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> vit_backbone(image_shape, patch_size, num_layers, num_heads, mlp_dim, attention_dropout_rate, dropout_rate):</span>
<span id="cb5-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb5-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Create a Vision Transformer backbone.</span></span>
<span id="cb5-4"></span>
<span id="cb5-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb5-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        image_shape (tuple): Shape of the images after resizing and augmentation.</span></span>
<span id="cb5-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        patch_size (int): Size of the patches.</span></span>
<span id="cb5-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        num_layers (int): Number of encoder layers.</span></span>
<span id="cb5-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        num_heads (int): Number of attention heads.</span></span>
<span id="cb5-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        mlp_dim (int): Hidden dimension of the MLP block.</span></span>
<span id="cb5-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        attention_dropout_rate (float): Dropout rate for the attention layer.</span></span>
<span id="cb5-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        dropout_rate (float): Dropout rate for the block.</span></span>
<span id="cb5-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns:</span></span>
<span id="cb5-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Vision Transformer backbone (keras.Model).</span></span>
<span id="cb5-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb5-16">    num_patches <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (image_shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> patch_size) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (image_shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> patch_size)</span>
<span id="cb5-17">    hidden_dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> patch_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> patch_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span></span>
<span id="cb5-18"></span>
<span id="cb5-19">    inputs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> keras.Input(shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>image_shape)</span>
<span id="cb5-20">    patches <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> extract_patches(inputs, patch_size)</span>
<span id="cb5-21">    encoded_patches <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> encode_patches(patches, num_patches, hidden_dim)</span>
<span id="cb5-22"></span>
<span id="cb5-23">    y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> vit_encoder(</span>
<span id="cb5-24">        encoded_patches, num_layers, num_heads, hidden_dim, mlp_dim, attention_dropout_rate, dropout_rate</span>
<span id="cb5-25">    )</span>
<span id="cb5-26">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> keras.Model(inputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>inputs, outputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>y)</span></code></pre></div></div>
</section>
<section id="iv.-classification-head" class="level3">
<h3 class="anchored" data-anchor-id="iv.-classification-head">IV. Classification Head</h3>
<p>Terakhir, kita perlu hubungkan arsitektur <em>backbone</em> ke lapisan penutup, yaitu lapisan kelas / kategori, agar siap untuk pelatihan tersupervisi. Kita dapat menggunakan Functional API pada Keras 3 (termasuk menggunakan <code>keras.Model</code>) untuk membentuk arsitektur ViT secara lengkap.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> vit_classifier(image_shape, patch_size, num_layers, num_heads, mlp_dim, attention_dropout_rate, dropout_rate, num_classes):</span>
<span id="cb6-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb6-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Create a Vision Transformer classifier using ViT Backbone.</span></span>
<span id="cb6-4"></span>
<span id="cb6-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb6-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        image_shape (tuple): Shape of the images after resizing and augmentation.</span></span>
<span id="cb6-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        patch_size (int): Size of the patches.</span></span>
<span id="cb6-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        num_layers (int): Number of encoder layers.</span></span>
<span id="cb6-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        num_heads (int): Number of attention heads.</span></span>
<span id="cb6-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        mlp_dim (int): Hidden dimension of the MLP block.</span></span>
<span id="cb6-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        attention_dropout_rate (float): Dropout rate for the attention layer.</span></span>
<span id="cb6-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        dropout_rate (float): Dropout rate for the block.</span></span>
<span id="cb6-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        num_classes (int): Number of output classes.</span></span>
<span id="cb6-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns:</span></span>
<span id="cb6-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Vision Transformer classifier (keras.Model).</span></span>
<span id="cb6-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb6-17">    backbone <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> vit_backbone(</span>
<span id="cb6-18">        image_shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>image_shape,</span>
<span id="cb6-19">        patch_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>patch_size,</span>
<span id="cb6-20">        num_layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>num_layers,</span>
<span id="cb6-21">        num_heads<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>num_heads,</span>
<span id="cb6-22">        mlp_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>mlp_dim,</span>
<span id="cb6-23">        attention_dropout_rate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>attention_dropout_rate,</span>
<span id="cb6-24">        dropout_rate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>dropout_rate</span>
<span id="cb6-25">    )</span>
<span id="cb6-26">    inputs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> backbone.inputs</span>
<span id="cb6-27">    features <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> backbone(inputs)</span>
<span id="cb6-28">    h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.GlobalAveragePooling1D()(features)</span>
<span id="cb6-29">    h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.Dropout(dropout_rate)(h)</span>
<span id="cb6-30">    logits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layers.Dense(num_classes, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"float32"</span>)(h)</span>
<span id="cb6-31"></span>
<span id="cb6-32">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> keras.Model(inputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>inputs, outputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>logits)</span></code></pre></div></div>
<p>Berikut ini merupakan contoh instansiasi arsitektur yang mirip dengan versi ViT-Base yang memiliki ~86 juta parameter, yang siap digunakan untuk klasifikasi objek dengan dimensi gambar (32, 32, 3) dan jumlah kategori sebanyak 100.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">classifier_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> vit_classifier(</span>
<span id="cb7-2">    image_shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">96</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">96</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>),</span>
<span id="cb7-3">    patch_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>,</span>
<span id="cb7-4">    num_layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>,</span>
<span id="cb7-5">    num_heads<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>,</span>
<span id="cb7-6">    mlp_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3072</span>,</span>
<span id="cb7-7">    attention_dropout_rate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>,</span>
<span id="cb7-8">    dropout_rate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>,</span>
<span id="cb7-9">    num_classes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span></span>
<span id="cb7-10">)</span></code></pre></div></div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-03-21-training-vision-transformer-keras/media/Screenshot_2025-04-28_at_07.23.50.png" class="img-fluid figure-img"></p>
<figcaption>Screenshot 2025-04-28 at 07.23.50.png</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-03-21-training-vision-transformer-keras/media/Screenshot_2025-03-17_at_20.40.12.png" class="img-fluid figure-img"></p>
<figcaption>Screenshot 2025-03-17 at 20.40.12.png</figcaption>
</figure>
</div>
<p>Berikut ini merupakan visualisasi arsitektur model ViT dengan hanya 1 layer Encoder Block (<code>num_layers = 1</code>) sekadar untuk penyederhanaan visualisasi.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-03-21-training-vision-transformer-keras/media/vit_base_96_cifar100.png" class="img-fluid figure-img"></p>
<figcaption>vit_base_96_cifar100.png</figcaption>
</figure>
</div>
</section>
</section>
<section id="pelatihan-vit-pada-dataset-cifar100" class="level2">
<h2 class="anchored" data-anchor-id="pelatihan-vit-pada-dataset-cifar100">Pelatihan ViT pada dataset CIFAR100</h2>
<p>Kita akan melatih model ViT dari nol dengan menggunakan dataset CIFAR100. Percobaan ini tidak untuk mereplikasi hasil CIFAR100 yang dilaporkan oleh (<a href="https://arxiv.org/pdf/2010.11929">Dosovitskiy et al.&nbsp;2021</a>) karena di sana merupakan hasil dari <em>finetuning</em>, dimana ViT sudah dilakukan pralatih (<em>pretraining</em>) dengan dataset yang jauh lebih besar.</p>
<p>Pelatihan dijalankan via fungsi Keras <code>fit()</code> dengan menggunakan optimisasi <code>AdamW</code>, <code>batch_size=128</code> dan <code>epochs=200</code>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># setup training and evaluation data (train_dataset, test_dataset)</span></span>
<span id="cb8-2">...</span>
<span id="cb8-3">...</span>
<span id="cb8-4">optimizer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> keras.optimizers.Adam(</span>
<span id="cb8-5">    learning_rate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>conf.LEARNING_RATE,</span>
<span id="cb8-6">    weight_decay<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>conf.WEIGHT_DECAY,</span>
<span id="cb8-7">    global_clipnorm<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb8-8">)</span>
<span id="cb8-9"></span>
<span id="cb8-10">vit_model.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>(</span>
<span id="cb8-11">    optimizer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>optimizer,</span>
<span id="cb8-12">    loss<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>keras.losses.SparseCategoricalCrossentropy(from_logits<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>),</span>
<span id="cb8-13">    metrics<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[</span>
<span id="cb8-14">        keras.metrics.SparseCategoricalAccuracy(name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"accuracy"</span>),</span>
<span id="cb8-15">        keras.metrics.SparseTopKCategoricalAccuracy(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top-5-accuracy"</span>),</span>
<span id="cb8-16">    ],</span>
<span id="cb8-17">)</span>
<span id="cb8-18"></span>
<span id="cb8-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Checkpoint callback</span></span>
<span id="cb8-20">checkpoint_filepath <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"models/</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>MODEL_PREFIX<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">_cifar100.weights.h5"</span></span>
<span id="cb8-21"></span>
<span id="cb8-22">checkpoint_callback <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> keras.callbacks.ModelCheckpoint(</span>
<span id="cb8-23">    checkpoint_filepath,</span>
<span id="cb8-24">    monitor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"val_accuracy"</span>,</span>
<span id="cb8-25">    save_best_only<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>,</span>
<span id="cb8-26">    save_weights_only<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>,</span>
<span id="cb8-27">)</span>
<span id="cb8-28"></span>
<span id="cb8-29">history <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> vit_model.fit(</span>
<span id="cb8-30">    train_dataset,</span>
<span id="cb8-31">    epochs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>conf.EPOCHS,</span>
<span id="cb8-32">    validation_data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>test_dataset,</span>
<span id="cb8-33">    callbacks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[checkpoint_callback],</span>
<span id="cb8-34">)</span></code></pre></div></div>
<p>Kita gunakan mesin komputasi GPU yang ada di Google Colab.</p>
<section id="google-colab" class="level3">
<h3 class="anchored" data-anchor-id="google-colab"><strong>Google Colab</strong></h3>
<p>Sekilas tentang Google Colab (terutama yang versi Pro), kita dapat memanfaatkan beberapa fasilitas sebagai berikut:</p>
<ul>
<li>100 compute units per bulan: akan expired setelah 90 hari</li>
<li>Pilihan GPU lebih cepat dan memori lebih besar dibandingkan versi gratis</li>
<li>Terminal yang terkoneksi dengan VM</li>
</ul>
<p>Saat ini terdapat beberapa pilihan <em>hardware accelerator</em>:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-03-21-training-vision-transformer-keras/media/Screenshot_2025-03-09_at_08.45.36.png" class="img-fluid figure-img"></p>
<figcaption>Screenshot 2025-03-09 at 08.45.36.png</figcaption>
</figure>
</div>
<p>Berikut spesifikasi lebih rinci dari masing-masing <em>hardware accelerator</em>:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th><strong>Mesin</strong></th>
<th><strong>Spesifikasi</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>T4 GPU</td>
<td>Arsitektur: Turing</td>
</tr>
</tbody>
</table>
<p>Jumlah GPU: 1 Memori: 16 GB GDDR6 Kegunaan: - Performa komputasi sekitar 8.1 TFLOPS (FP32), mendukung <em>mixed precision</em> (FP16, Tensor Cores). - Cocok untuk pelatihan deep learning skala menengah. - Banyak digunakan untuk inference karena efisiensi daya yang baik. | | L4 GPU | Arsitektur: Ada Lovelace (khusus untuk <em>inference</em> dan <em>video processing</em>) Jumlah GPU: 1 Memori: 24 GB GDDR6 Kegunaan: - Performa komputasi sekitar 22-24 TFLOPS (FP32). - Sangat baik untuk inference large model, pengolahan video, dan juga fine-tuning model berukuran sedang-besar. | | A100 GPU | Arsitektur: Ampere Jumlah GPU: 1 Memori: 40 GB HBM2 Kegunaan: - Performa komputasi hingga 19.5 TFLOPS (FP32), dan jauh lebih tinggi jika memanfaatkan Tensor Cores (BF16/FP16). - Ideal untuk training model besar seperti Transformers (BERT, GPT, dsb). - Memiliki memori GPU yang cukup besar sehingga mengurangi risiko <em>out-of-memory</em> (OOM) saat training. | | TPU v2-8 | Arsitektur: TPU Generasi 2 Kapasitas: - Satu TPU node memiliki 8 core TPU (sering disebut v2-8). - Setiap TPUP v2-8 umumnya dilengkapi 128 GB HBM secara total Kegunaan: - Performa komputasi sekitar 180 TFLOPS (BF16) per TPU v2-8. - Cocok untuk melatih deep learning berskala menengah hingga besar, terutama menggunakan TensorFlow dan JAX - Sangat cepat untuk pelatihan transformer-based models dengan library yang mendukung TPU. | | TPU v5-1 | Arsitektur: TPU Generasi 5e (generasi lebih baru dari v2-8, varian hemat biaya dan fleksibel) Kapasitas: - Memiliki opsi skala core berbeda, di Colab mungkin hanya 1 core. - Memori per core lebih rendah dibandingkan v2-8 jika hanya 1 core. Kegunaan: - Lebih efisien dari sisi biaya dan daya. - Cocok untuk pelatihan model skala kecil-menengah dan <em>inference.</em> |</p>
</section>
<section id="mode-mixed-precision" class="level3">
<h3 class="anchored" data-anchor-id="mode-mixed-precision"><strong>Mode Mixed Precision</strong></h3>
<p>Pada umumnya pelatihan model <em>deep learning</em> menggunakan angka dan operasi dengan presisi floating point 32 bit. Untuk mempercepat proses pelatihan, kita dapat memanfaatkan mode <em>mixed precision</em>, yaitu memadukan penggunaan floating point presisi lebih rendah 16-bit (BF16/FP16) dan 32-bit (FP32).</p>
<p>Mesin akselerator modern saat ini seperti Google TPU dan NVIDIA GPU memiliki perangkat keras yang didesain khusus untuk presisi 16-bit (BF16/FP16). Tambahan kecepatan yang didapatkan dengan memanfaatkan <em>mixed precision</em> dapat mencapai hingga 3x pada GPU dan 60% pada TPU.</p>
<p>Pada Keras 3, mixed precision dapat diaktifkan dengan mengeksekusi perintah berikut pada awal program sebelum membuat arsitektur model:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On NVIDIA GPU or Apple M Chip</span></span>
<span id="cb9-2">keras.mixed_precision.set_global_policy(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mixed_float16"</span>) </span>
<span id="cb9-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># or</span></span>
<span id="cb9-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On Google TPU</span></span>
<span id="cb9-5">keras.mixed_precision.set_global_policy(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mixed_bfloat16"</span>)</span></code></pre></div></div>
</section>
<section id="evaluasi-hasil-pelatihan" class="level3">
<h3 class="anchored" data-anchor-id="evaluasi-hasil-pelatihan">Evaluasi Hasil Pelatihan</h3>
<p>Hasil pelatihan dalam metrik top-1-accuracy dan top-5-accuracy dapat dilihat pada grafik dan tabel berikut, dengan mode <em>mixed precision</em>.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-03-21-training-vision-transformer-keras/media/vit_base_96_cifar100_plot.png" class="img-fluid figure-img"></p>
<figcaption>vit_base_96_cifar100_plot.png</figcaption>
</figure>
</div>
<table class="caption-top table">
<thead>
<tr class="header">
<th></th>
<th><strong>Top-1 Accuracy (%)</strong></th>
<th><strong>Top-5 Accuracy (%)</strong></th>
<th><strong>Xent Loss</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Train</strong></td>
<td>98.34</td>
<td>99.98</td>
<td>0.05</td>
</tr>
<tr class="even">
<td><strong>Test</strong></td>
<td>49.30</td>
<td>77.28</td>
<td>4.06</td>
</tr>
</tbody>
</table>
<p>Terlihat bahwa ViT berhasil dilatih dengan performa pada data latih mencapai akurasi (top-1) sebesar 98.34%, namun performa pada data test masih agak jauh di bawah itu (49.30%). Tentunya ini hasil yang tidak kompetitif — sebagai perbandingan, ResNet50V2 mampu mencapai akurasi pada data tes yang sama hingga 67%. Ada kemungkinan model ViT masih mengalami overfitting pada CIFAR100. Beberapa hal yang dapat dilakukan untuk mengurangi overfitting misalnya, memperbesar pengaruh regularisasi, e.g., rasio Dropout diperbesar, menggunakan data augmentation yang lebih kompleks, dan sebagainya.</p>
<p>Sekadar mengecek pengaruh mode <em>mixed precision</em> terhadap kecepatan waktu pelatihan, berikut perbandingannya dengan mode full FP32 dan dengan menggunakan mesin komputasi yang berbeda (Apple M4 vs A100 GPU di Google Colab)</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th></th>
<th><strong>Apple M4</strong></th>
<th><strong>A100 GPU (Colab)</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>FP32</strong></td>
<td>~1300 ms</td>
<td>~ 293 ms</td>
</tr>
<tr class="even">
<td><strong>Mixed Precision</strong></td>
<td>~1000 ms</td>
<td>~187 ms</td>
</tr>
</tbody>
</table>
<p>Jelas terlihat komputasi di Colab dengan mesin A100 GPU jauh lebih cepat dibandingkan Apple M4, dan juga terlihat peningkatan kecepatan dengan memanfaatkan <em>mixed precision</em>.</p>
<p>Kode sumber lengkap untuk eksperimen di atas dapat ditemukan di <a href="https://github.com/ghif/vit-keras3/blob/main/train_cifar100.py">https://github.com/ghif/vit-keras3/blob/main/train_cifar100.py</a>.</p>
</section>
</section>
<section id="finetuning-dengan-menggunakan-vit" class="level2">
<h2 class="anchored" data-anchor-id="finetuning-dengan-menggunakan-vit">Finetuning dengan Menggunakan ViT</h2>
<p>Kita dapat memanfaatkan model ViT yang sudah melalui tahap pralatih (<em>pretraining</em>) pada dataset berukuran besar, yang selanjutnya dapat dilakukan <em>finetuning</em> untuk menyelesaikan problem klasifikasi yang lain. Pendekatan ini dikenal dengan istilah <em>transfer learning</em>, dimana model yang sudah dilatih untuk suatu pekerjaan tertentu di awal (<em>upstream task</em>) akan ditransfer kemampuannya untuk pekerjaan lain yang terkait (<em>downstream task</em>).</p>
<p>Sebagai contoh, model ViT dapat dipralatih pada dataset <a href="https://www.image-net.org/">ImageNet</a>, lalu akan digunakan untuk klasifikasi objek pada CIFAR100. ViT hasil pralatih sudah tersedia secara publik di Internet dan dapat dengan mudah diakses melalui <a href="https://keras.io/keras_hub/">Keras Hub</a>.</p>
<p>Secara umum, langkah-langkah yang perlu dipersiapkan untuk finetuning adalah sebagai berikut:</p>
<p><strong>1. Memuat (<em>load</em>) model ViT pralatih</strong></p>
<p>Dengan menggunakan <code>keras-hub</code> , model backbone ViT yang sudah dipralatih dapat diakses via 1 baris kode — contoh di bawah memuat model versi “ViT-Base” yang dipralatih dari dataset ImageNet.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> keras_hub</span>
<span id="cb10-2"></span>
<span id="cb10-3">backbone <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> keras_hub.models.Backbone.from_preset(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"vit_base_patch16_224_imagenet"</span>)</span></code></pre></div></div>
<p><strong>2. Memodifikasi <em>Classification Head</em></strong></p>
<p>ViT hasil pralatih dari ImageNet memiliki jumlah kategori/kelas sebanyak 1000. Sedangkan untuk diaplikasikan pada CIFAR100, terdapat jumlah kategori sebesar 100 dengan semantik yang berbeda.</p>
<p>Oleh karena itu, kita perlu mengganti layer terakhir dari ViT yang terhubung ke kategori. Melalui Keras Hub kita dapat memanfaatkan API <code>keras_hub.models.ViTImageClassifier</code>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Get the preprocessor</span></span>
<span id="cb11-2">preprocessor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> keras_hub.models.ViTImageClassifierPreprocessor.from_preset(</span>
<span id="cb11-3">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"vit_base_patch16_224_imagenet"</span></span>
<span id="cb11-4">)</span>
<span id="cb11-5"></span>
<span id="cb11-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Setup ViT model with the new Classification Head</span></span>
<span id="cb11-7">image_classifier <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> keras_hub.models.ViTImageClassifier(</span>
<span id="cb11-8">    backbone<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>backbone,</span>
<span id="cb11-9">    num_classes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>num_classes,</span>
<span id="cb11-10">    preprocessor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>preprocessor,</span>
<span id="cb11-11">)</span>
<span id="cb11-12"></span>
<span id="cb11-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># visualize the architecture</span></span>
<span id="cb11-14">image_classifier.summary(expand_nested<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>) </span></code></pre></div></div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-03-21-training-vision-transformer-keras/media/Screenshot_2025-03-20_at_17.42.36.png" class="img-fluid figure-img"></p>
<figcaption>Screenshot 2025-03-20 at 17.42.36.png</figcaption>
</figure>
</div>
<p>Dari tangkapan layar di atas, terlihat lapisan terakhir pada ViT, <code>predictions (Dense)</code>, sudah memiliki dimensi 100, sama dengan jumlah kategori pada CIFAR100.</p>
<p><strong>3. Melatih model dengan Classification Head yang baru</strong></p>
<p>Model ViT dengan Classification Head yang baru siap dilatih dengan mekanisme <em>finetuning</em> pada dataset CIFAR100. Berbeda dengan yang digunakan pada percobaan pelatihan dari nol sebelumnya, metode optimisasi yang digunakan adalah Stochastic Gradient Descent (<code>SGD</code>) dengan <code>momentum=0.9</code>.</p>
<p>Perbedaan lainnya adalah nilai <code>learning_rate</code> dibuat dinamis, perlahan-lahan berkurang dengan mengikuti penjadwalan (<em>scheduling</em>) dengan mekanisme <em><strong>cosine decay</strong> —</em> kecepatan pembaruan parameter semakin berkurang seiring bertambahnya langkah pelatihan mengikuti pola fungsi <em>cosine</em>. Hal ini untuk menjaga kestabilan atau menghindari osilasi yang tinggi pada proses pelatihan bobot model di fase tengah ke akhir. Grafik di bawah ini mengilustrasikan pola <em>cosine decay</em>:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://ghif.github.io/posts/2025-03-21-training-vision-transformer-keras/media/vit_base_224_finetuned_all_lr_schedule.png" class="img-fluid figure-img"></p>
<figcaption>vit_base_224_finetuned_all_lr_schedule.png</figcaption>
</figure>
</div>
<p>Perhatikan pada grafik tersebut, bahwa efek <em>cosine decay</em> baru dimulai setelah langkah &gt; 10000. Dari langkah awal hingga langkah ke ~2000an, pola<code>learning_rate</code> naik secara linear, lalu dilanjutkan dengan stagnasi pada nilai tertentu hingga langkah ke ~11000an. Efek ini dinamakan dengan <strong><em>warm up</em></strong>, dalam rangka menjaga kestabilan pelatihan di fase awal untuk mengimbangi efek nilai bobot yang diinisialisasi secara acak.</p>
<p>Berikut implementasi finetuning model ViT dengan penjadwalan <em>cosine decay + warmup</em> terhadap <code>learning_rate</code>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> lr_warmup_cosine_decay(</span>
<span id="cb12-2">    global_step,</span>
<span id="cb12-3">    warmup_steps,</span>
<span id="cb12-4">    hold<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,</span>
<span id="cb12-5">    total_steps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,</span>
<span id="cb12-6">    target_lr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-3</span>,</span>
<span id="cb12-7">):</span>
<span id="cb12-8">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Cosine decay</span></span>
<span id="cb12-9">    learning_rate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb12-10">        <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span></span>
<span id="cb12-11">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> target_lr</span>
<span id="cb12-12">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (</span>
<span id="cb12-13">            <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb12-14">            <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> ops.cos(</span>
<span id="cb12-15">                math.pi</span>
<span id="cb12-16">                <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> ops.convert_to_tensor(</span>
<span id="cb12-17">                    global_step <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> warmup_steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> hold, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"float32"</span></span>
<span id="cb12-18">                )</span>
<span id="cb12-19">                <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> ops.convert_to_tensor(</span>
<span id="cb12-20">                    total_steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> warmup_steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> hold, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"float32"</span></span>
<span id="cb12-21">                )</span>
<span id="cb12-22">            )</span>
<span id="cb12-23">        )</span>
<span id="cb12-24">    )</span>
<span id="cb12-25"></span>
<span id="cb12-26">    warmup_lr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> target_lr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (global_step <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> warmup_steps)</span>
<span id="cb12-27"></span>
<span id="cb12-28">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> hold <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb12-29">        learning_rate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.where(</span>
<span id="cb12-30">            global_step <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> warmup_steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> hold, learning_rate, target_lr</span>
<span id="cb12-31">        )</span>
<span id="cb12-32"></span>
<span id="cb12-33">    learning_rate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ops.where(global_step <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> warmup_steps, warmup_lr, learning_rate)</span>
<span id="cb12-34">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> learning_rate</span>
<span id="cb12-35"></span>
<span id="cb12-36"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> WarmUpCosineDecay(schedules.LearningRateSchedule):</span>
<span id="cb12-37">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, warmup_steps, total_steps, hold, target_lr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-2</span>):</span>
<span id="cb12-38">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb12-39">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.target_lr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> target_lr</span>
<span id="cb12-40">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.warmup_steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> warmup_steps</span>
<span id="cb12-41">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.total_steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> total_steps</span>
<span id="cb12-42">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.hold <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hold</span>
<span id="cb12-43"></span>
<span id="cb12-44">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__call__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, step):</span>
<span id="cb12-45">        lr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lr_warmup_cosine_decay(</span>
<span id="cb12-46">            global_step<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>step,</span>
<span id="cb12-47">            total_steps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.total_steps,</span>
<span id="cb12-48">            warmup_steps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.warmup_steps,</span>
<span id="cb12-49">            target_lr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.target_lr,</span>
<span id="cb12-50">            hold<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.hold,</span>
<span id="cb12-51">        )</span>
<span id="cb12-52">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ops.where(step <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.total_steps, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, lr)</span>
<span id="cb12-53">    </span>
<span id="cb12-54">lr_schedule <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> WarmUpCosineDecay(</span>
<span id="cb12-55">    target_lr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>conf.LEARNING_RATE,</span>
<span id="cb12-56">    warmup_steps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> total_steps),</span>
<span id="cb12-57">    total_steps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>total_steps,</span>
<span id="cb12-58">    hold<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.45</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> total_steps)</span>
<span id="cb12-59">)</span>
<span id="cb12-60"></span>
<span id="cb12-61"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Finetune the classifier with SGD optimizer</span></span>
<span id="cb12-62">optimizer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> keras.optimizers.SGD(</span>
<span id="cb12-63">    learning_rate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>lr_schedule,</span>
<span id="cb12-64">    momentum<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>,</span>
<span id="cb12-65">    global_clipnorm<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb12-66"></span>
<span id="cb12-67">)</span>
<span id="cb12-68"></span>
<span id="cb12-69">image_classifier.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>(</span>
<span id="cb12-70">    optimizer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>optimizer,</span>
<span id="cb12-71">    loss<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>keras.losses.SparseCategoricalCrossentropy(from_logits<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>),</span>
<span id="cb12-72">    metrics<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[</span>
<span id="cb12-73">        keras.metrics.SparseCategoricalAccuracy(name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"accuracy"</span>),</span>
<span id="cb12-74">        keras.metrics.SparseTopKCategoricalAccuracy(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top-5-accuracy"</span>),</span>
<span id="cb12-75">    ],</span>
<span id="cb12-76">)</span>
<span id="cb12-77"></span>
<span id="cb12-78"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Checkpoint callback</span></span>
<span id="cb12-79">checkpoint_filepath <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"models/</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>MODEL_PREFIX<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">_cifar100.weights.h5"</span></span>
<span id="cb12-80">checkpoint_callback <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> keras.callbacks.ModelCheckpoint(</span>
<span id="cb12-81">    checkpoint_filepath,</span>
<span id="cb12-82">    monitor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"val_accuracy"</span>,</span>
<span id="cb12-83">    save_best_only<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>,</span>
<span id="cb12-84">    save_weights_only<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>,</span>
<span id="cb12-85">)</span>
<span id="cb12-86"></span>
<span id="cb12-87"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Finetune the classifier</span></span>
<span id="cb12-88">history <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> image_classifier.fit(</span>
<span id="cb12-89">    train_dataset,</span>
<span id="cb12-90">    epochs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>conf.EPOCHS,</span>
<span id="cb12-91">    validation_data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>test_dataset,</span>
<span id="cb12-92">    callbacks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[checkpoint_callback],</span>
<span id="cb12-93">)</span>
<span id="cb12-94"></span>
<span id="cb12-95">loss, accuracy, top_5_accuracy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> image_classifier.evaluate(train_dataset)</span>
<span id="cb12-96"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Train loss: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>loss<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb12-97"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Train accuracy: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(accuracy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%"</span>)</span>
<span id="cb12-98"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Train top 5 accuracy: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(top_5_accuracy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%"</span>)</span>
<span id="cb12-99"></span>
<span id="cb12-100">loss, accuracy, top_5_accuracy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> image_classifier.evaluate(test_dataset)</span>
<span id="cb12-101"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Test loss: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>loss<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb12-102"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Test accuracy: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(accuracy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%"</span>)</span>
<span id="cb12-103"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Test top 5 accuracy: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(top_5_accuracy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%"</span>)</span></code></pre></div></div>
<p>Sedikit catatan tambahan, proses <em>finetuning</em> di sini dilakukan dengan membekukan (<em>freeze</em>) bobot dari lapisan <em>backbone</em>. Artinya, bobot yang diperbarui hanya pada lapisan Classification Head.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1">backbone.trainable <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span></span></code></pre></div></div>
<p>Hal ini dilakukan untuk menyederhanakan komputasi. Proses <em>finetuning</em> pada dasarnya bisa diberlakukan untuk memperbarui seluruh bobot pada model.</p>
<section id="hasil-percobaan-finetuning" class="level3">
<h3 class="anchored" data-anchor-id="hasil-percobaan-finetuning">Hasil Percobaan Finetuning</h3>
<p>Berikut performa yang didapatkan oleh model ViT hasil finetuning dengan data CIFAR100 setelah melalui <code>50 epochs</code>.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th></th>
<th><strong>Top-1 Accuracy (%)</strong></th>
<th><strong>Top-5 Accuracy (%)</strong></th>
<th><strong>Xent Loss</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Train</strong></td>
<td>89.04</td>
<td>98.23</td>
<td>0.39</td>
</tr>
<tr class="even">
<td><strong>Test</strong></td>
<td>85.20</td>
<td>96.85</td>
<td>0.97</td>
</tr>
</tbody>
</table>
<p>Top-1 Accuracy berhasil mencapai angka 85.20%, jauh di atas angka hasil pelatihan model ViT dari nol sebelumnya (49.30%).</p>
<p>Implementasi secara lengkap dapat dilihat di <a href="https://github.com/ghif/vit-keras3/blob/main/finetune_cifar100.py">https://github.com/ghif/vit-keras3/blob/main/finetune_cifar100.py</a>.</p>
</section>
</section>
<section id="penutup" class="level2">
<h2 class="anchored" data-anchor-id="penutup">Penutup</h2>
<p>Dalam artikel ini, telah dibahas secara komprehensif tentang implementasi Vision Transformer (ViT) dengan Keras 3, mulai dari pemrosesan awal berupa ekstraksi patch, encoding patch dengan penambahan position embedding, hingga encoder block yang mengintegrasikan mekanisme Multi Head Attention dan MLP. Pembahasannya juga meliputi teknik penyederhanaan komputasi seperti mixed precision dan juga strategi fine-tuning untuk memanfaatkan model yang telah dipralatih pada dataset besar. Dari hasil eksperimen klasifikasi objek pada dataset CIFAR100, strategi finetuning mampu menghasilkan performa ViT yang kompetitif dibandingkan pelatihan dari nol.</p>


</section>

 ]]></description>
  <category>deep-learning</category>
  <category>computer-vision</category>
  <category>implementation</category>
  <guid>https://ghif.github.io/posts/2025-03-21-training-vision-transformer-keras/</guid>
  <pubDate>Fri, 21 Mar 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Generative AI-Powered Software Engineering: Latest Trends and DORA Impact</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2025-03-06-generative-ai-software-engineering-dora/</link>
  <description><![CDATA[ 




<aside>
<p>💡</p>
<p>This article was written with the help of <a href="https://openai.com/index/introducing-deep-research/">OpenAI’s deep research</a>, an agentic AI tool that employs reasoning to synthesize extensive online information and execute complex multi-step research tasks.</p>
</aside>
<section id="overview-of-generative-ai-in-software-engineering" class="level2">
<h2 class="anchored" data-anchor-id="overview-of-generative-ai-in-software-engineering"><strong>1. Overview of Generative AI in Software Engineering</strong></h2>
<p>Generative AI is increasingly transforming software development by automating and assisting in coding, testing, and operations. Modern AI coding assistants like <strong>GitHub Copilot</strong>, <strong>Codeium</strong>, <strong>Tabnine</strong>, and <strong>Amazon CodeWhisperer</strong> leverage large language models trained on vast codebases to suggest code or even generate entire functions. These tools can dramatically boost developer productivity and reduce time-to-market . For example, developers currently spend an estimated 17 hours per week on routine maintenance, but AI can now handle many repetitive tasks (like boilerplate code generation) and assist with code reviews, testing, and design . As a result, teams can focus more on complex creative work while the AI tackles the grunt work.</p>
<p>Recent advancements have expanded the capabilities of these AI assistants beyond basic autocompletion. GitHub Copilot’s underlying model (OpenAI’s Codex) and its successors can interpret natural-language comments to generate code, suggest entire code blocks, and even create test cases. Competing tools like Codeium and Tabnine offer similar AI-driven completions within IDEs, supporting multiple languages and frameworks. The integration of AI into IDEs is well underway – these tools not only complete code but also perform semantic searches in codebases and flag potential issues before runtime . This represents a leap from earlier generation “intelligent code completion” toward a true AI pair programmer experience. The popularity of such tools has surged: the Stack Overflow 2023 developer survey found GitHub Copilot to be the most-used AI dev tool, with about <strong>55%</strong> of developers reporting using it, far outpacing alternatives like Tabnine (13%) . The global market for AI coding tools exceeded$4 billion in 2023 and is expected to <strong>triple by 2028</strong>, reflecting how rapidly this technology is being adopted across the industry.</p>
<p>Beyond coding assistance, AI is being woven into other stages of the software engineering lifecycle. In <strong>CI/CD pipelines</strong>, AI-driven tools can optimize build and test workflows. For instance, predictive models can identify which test suites are likely to catch a particular code change’s bugs, reducing unnecessary test runs. Some DevOps platforms even use AI to analyze code changes and recommend deployment strategies, or to automate canary releases and rollbacks based on predicted risk. In <strong>automated testing</strong>, generative AI can create unit tests or end-to-end test scripts by analyzing code or user stories. This leads to higher test coverage with less manual effort. Researchers and companies have begun using LLMs to generate test cases from requirements in plain language . Early tools (e.g.&nbsp;Diffblue Cover for Java or Amazon’s CodeWhisperer test suggestions) illustrate how AI can quickly produce a suite of tests that would take humans substantial time.</p>
<p>Another emerging application is <strong>AI-driven Infrastructure as Code (IaC)</strong> and configuration management. Generative AI can translate high-level descriptions of infrastructure needs into code for provisioning cloud resources. For instance, given a request like “set up a VPC with two subnets and an EC2 behind a load balancer,” an AI could output Terraform or CloudFormation scripts to implement it. Tools like Brainboard provide AI suggestions and modules for Terraform configurations , and researchers have explored using LLMs to generate Kubernetes YAML manifests and other config files. This has the potential to speed up cloud deployments and help less-experienced engineers by providing boilerplate IaC code. Similarly, AI can assist with configuration management by analyzing system states and recommending optimal settings or by automatically correcting configuration drift. While still nascent, these AI capabilities hint at a future where much of the setup and management of infrastructure can be automated through natural language interfaces.</p>
<p>In summary, generative AI is now touching all parts of the software development lifecycle: <strong>coding</strong>, <strong>testing</strong>, <strong>CI/CD, monitoring</strong>, and <strong>infrastructure</strong>. These advancements are enabling development teams to ship software faster and with potentially higher quality. In the next sections, we will examine how these AI-driven changes are impacting DevOps performance metrics – particularly the four key DORA metrics – and explore real-world cases, challenges, and future outlook.</p>
</section>
<section id="impact-on-dora-metrics" class="level2">
<h2 class="anchored" data-anchor-id="impact-on-dora-metrics"><strong>2. Impact on DORA Metrics</strong></h2>
<p>DevOps Research and Assessment (DORA) identifies four key metrics that indicate software delivery performance: <strong>Deployment Frequency, Lead Time for Changes, Change Failure Rate, and Time to Restore Service</strong>. Generative AI is poised to influence all four. Below, we analyze each metric in turn, citing recent data and case studies:</p>
<ul>
<li><strong>Deployment Frequency (DF)</strong> – This measures how often an organization deploys code to production. AI assistance can significantly boost deployment frequency by enabling developers to implement and integrate changes faster. With AI-generated code suggestions and automated tasks, teams complete feature work and bug fixes more rapidly, allowing more frequent releases. GitHub’s internal studies, for example, found that developers using Copilot <strong>completed tasks 55% faster</strong> on average . Faster coding and automation of tedious work mean that code can move from idea to ready-for-deploy in less time, naturally increasing how often deployments occur. AI can also automate parts of the deployment process itself – from writing deployment scripts to coordinating rollouts – further raising throughput. One blog noted that modern AI tools can improve deployment frequency by streamlining code reviews and infrastructure changes . In practice, some teams leveraging AI report moving towards <strong>daily or on-demand deployments</strong> rather than weekly sprints. However, it’s worth noting that AI can also encourage larger batch changes if developers rely on it to generate big chunks of code at once. DORA’s 2024 report observed that when some teams adopted AI without adjusting processes, their overall software <strong>throughput actually dipped ~1.5%</strong> . This was attributed to developers bundling bigger changesets (since AI made coding easier, they attempted more in a single go), which can slow the pipeline. The lesson is that to truly improve deployment frequency, AI should be used to deliver small, incremental changes faster, rather than to create giant releases.</li>
<li><strong>Lead Time for Changes</strong> – Lead time measures how long it takes from code being committed to running in production (or from ticket start to deployment). Generative AI shortens multiple segments of this timeline. Code-writing is faster as noted above, and AI can also speed up code review and testing. For instance, AI-generated tests catch issues earlier, reducing back-and-forth in QA. GitHub reports not only faster coding with Copilot but also a <strong>50% faster time-to-merge for pull requests</strong> in pilot studies . Quicker PR approvals mean features flow through the pipeline with less delay. Moreover, AI bots can automatically validate code (linting, static analysis) and even fix simple errors, cutting waiting time between development and integration. In a fintech case study, introducing generative AI substantially reduced lead times for specific tasks. One team saw an <strong>API schema generation task drop from 2 hours to 45 minutes</strong> with AI assistance – a <strong>62.5% reduction in development time</strong> . Across eight standard development activities in that study, AI tools improved productivity in each, suggesting much shorter turnaround for code changes to go live . In short, by accelerating coding and automating validation, AI compresses the cycle time of software delivery. High-performing DevOps teams strive for lead times of hours or even minutes, and AI is helping approach that realm. In fact, an optimistic scenario posed by researchers is that advanced AI could eventually reduce the time from idea conception to deployment to <em>“mere minutes”</em> for certain tasks . We are already seeing early signs of drastically faster turnaround in organizations effectively leveraging these tools.</li>
<li><strong>Change Failure Rate (CFR)</strong> – This is the percentage of deployments that result in a failure in production (e.g.&nbsp;require a hotfix or rollback). AI’s impact on change failure rate is nuanced. On one hand, AI can <strong>improve code quality</strong> by catching bugs or suggesting well-tested patterns, thereby reducing the chance that a deployment fails. Developers using AI assistants often report writing cleaner code – GitHub noted quality improvements on multiple dimensions (readability, maintainability, etc.) when Copilot was used . More comprehensive automated testing (augmented by AI-generated tests) and AI-based static analysis can also <strong>prevent defects</strong> from ever reaching production. All these should lower the change failure rate, meaning fewer incidents per release. On the other hand, there is a risk that inexperienced devs might trust AI-generated code too much. If AI suggestions introduce unseen vulnerabilities or logical errors, they could <em>increase</em> the failure rate unless proper review is in place. A study in <strong>Communications of the ACM</strong> found about <strong>40% of programs generated by GitHub Copilot had security vulnerabilities</strong> in scenarios targeting common weakness patterns . Likewise, security researchers at Snyk demonstrated that AI coding assistants can <strong>amplify existing bugs and insecure code</strong> by mindlessly imitating flawed patterns from training data . These findings underscore that AI is not infallible – if it produces a subtle bug and developers deploy without catching it, a failure can occur. Early data from DORA’s research hints at this trade-off: organizations that adopted AI saw a <strong>7.2% drop in stability</strong> metrics on average, suggesting slightly higher failure rates or longer outages in some cases . It’s suspected that low trust and improper use of AI led to some buggy code slipping through. However, many high-performing teams avoid this pitfall by treating AI as a junior developer whose code <strong>must be reviewed and tested</strong>. When used with best practices, AI can actually <strong>reduce change failures</strong> by producing code with fewer human errors (offloading tedious aspects that humans often get wrong). In summary, AI has the potential to <em>lower</em> change failure rates via better quality and testing, but only if its outputs are vetted – otherwise it may introduce new failure modes.</li>
<li><strong>Time to Restore Service (MTTR)</strong> – This metric tracks how quickly a team can recover from an incident or outage (i.e.&nbsp;restore a service to normal operation). Generative AI and AI-Ops capabilities are proving very effective at cutting MTTR by accelerating incident diagnosis and response. As mentioned, AI-driven monitoring tools can instantly detect anomalies and even pinpoint likely causes by correlating tons of signals (logs, metrics, recent deploys) faster than any human on-call could. For example, ScienceLogic reports that AI-based root cause analysis software helps <strong>diagnose issues 10 times faster</strong>, directly shrinking the time it takes to identify what needs to be fixed . Faster triage naturally leads to faster recovery. AI can also suggest remediation steps – for instance, if a certain error is detected, an AI system might automatically recommend rolling back to the last good version or restarting a specific service. Some incident bots can execute predefined self-healing actions (like clearing a queue, scaling up resources) autonomously if the situation matches a known pattern. This reduces the need to page humans for every minor incident. Furthermore, generative AI can assist during the incident by summarizing system status or querying knowledge bases (imagine a ChatGPT-like assistant that an SRE can ask, “What changed in the last 30 minutes that could cause this outage?” and it answers with probable causes). By speeding up each phase – detection, diagnosis, and resolution – AI can drastically lower MTTR. In high-stakes industries, companies are starting to boast of near-zero downtime with the help of such AI-powered resiliency. It’s important to note that while AI can <em>facilitate</em> faster restores, organizations still need solid incident management processes. AI is an aid, not a replacement for on-call engineers, but it can give those engineers superhuman speed. As these tools mature, we expect MTTR to continue to drop, moving closer to <strong>real-time automated recovery</strong> in many cases.</li>
</ul>
<p>Overall, the <strong>impact of AI on DORA metrics</strong> is very promising, though not uniformly positive in all contexts. Early adopters of AI in development have seen improvements in <strong>throughput and stability</strong> when they integrate these tools properly. According to the 2024 Accelerate State of DevOps Report, teams that effectively embraced AI noted gains in developer flow, productivity, and even job satisfaction – translating into better overall performance . In other words, AI can help teams deploy faster and more reliably, which is the essence of DORA metrics. However, the same research also urges caution: about 39% of professionals in the study voiced low or no trust in AI-generated code, and hasty AI adoption was correlated with slight dips in performance metrics in some cases . The key takeaway is that <strong>AI is a powerful accelerator</strong>, but organizations must implement it with proper guardrails (e.g.&nbsp;keep humans in the loop, provide training and time to adjust) to actually see DORA metrics improve. When done right, generative AI can be a catalyst toward elite performance (multiple deploys per day, very low failure rates, instant recoveries); done poorly, it could temporarily set back your DevOps outcomes. The quantitative evidence so far – faster coding cycles, sizable time reductions, and real-world quality improvements – suggests that, despite challenges, AI is generally an enabler of better software delivery performance.</p>
</section>
<section id="industry-use-cases" class="level2">
<h2 class="anchored" data-anchor-id="industry-use-cases"><strong>3. Industry Use Cases</strong></h2>
<p>Generative AI-powered software engineering is not a theoretical concept – it’s being applied across industries such as <strong>fintech</strong>, <strong>healthcare</strong>, <strong>SaaS</strong>, and more, with noteworthy results. Below we explore how AI is reshaping engineering practices in different sectors, along with real implementations and success stories:</p>
<p><strong>Fintech</strong> – The finance and banking sector, known for its stringent requirements and legacy systems, is leveraging AI to accelerate software modernization and innovation. Fintech companies often deal with <strong>complex, mission-critical codebases</strong> (e.g.&nbsp;core banking systems) where productivity gains can translate directly into competitive advantage. A striking example comes from a study by Grid Dynamics on using generative AI for enterprise software development (including fintech scenarios). The results showed dramatic improvements in development tasks when assisted by AI.</p>
<p>Productivity gains from generative AI across typical software development tasks, as measured in an enterprise study . For instance, code translation (e.g.&nbsp;converting legacy code to a modern language) saw a 15–90% reduction in effort with AI, and writing documentation – often a mundane task – showed gains of up to 90%. Even complex tasks like creating unit tests, data access layers, or API schemas showed substantial productivity boosts. Such efficiency improvements are particularly valuable in fintech, where legacy code modernization and rigorous testing are continuous challenges.</p>
<p>In real fintech use cases, these kinds of gains have enabled faster release cycles <strong>without compromising quality</strong>. For example, a fintech team modernizing a payment processing platform used an AI assistant to help translate portions of a legacy Java codebase to Kotlin. The AI suggested code mappings and handled repetitive translation patterns, speeding up the project significantly. In another case, a large bank applied generative AI to generate configuration-as-code for its cloud infrastructure, freeing engineers from manually writing hundreds of lines of YAML and Terraform. The bank reported that what used to take developers days of scripting could be achieved in a few hours with the AI’s help, after which engineers only needed to do minor tweaking and reviews. Moreover, fintech companies are using AI to <strong>bolster compliance and security</strong> in development. AI code analysis tools can automatically flag code that doesn’t meet PCI-DSS security standards or that fails to handle edge cases in financial transactions, acting as an ever-vigilant code reviewer. J.P. Morgan, for instance, developed an internal ML tool called <em>Senatus AI</em> to analyze and improve code quality across its software portfolio, aiming to “supercharge the software development lifecycle” in a heavily regulated environment . These real-world implementations show that, when paired with domain experts, AI can help fintech engineering teams ship updates faster (important for rapidly evolving fintech products) while maintaining the high bar for security and accuracy that the industry demands.</p>
<p><strong>Healthcare</strong> – In healthcare and life sciences, software is often tied to critical outcomes and strict regulations (such as HIPAA, FDA rules for medical devices, etc.). Here, generative AI is proving useful in enhancing software engineering productivity <strong>while supporting compliance and documentation needs</strong>. One case study published in <em>JMIR Human Factors (2024)</em> examined using ChatGPT to assist the development of a digital health intervention app for diabetes prevention. The researchers found that AI could help in multiple stages: generating software requirements, suggesting design approaches, and even producing code snippets for the app . Notably, a panel of evaluators (with backgrounds in medicine and computer science) reviewed ChatGPT’s outputs and gave positive scores on metrics like <strong>understandability, relevance, and completeness</strong> . The study concluded that <em>“ChatGPT can serve as a usable facilitator…supporting developers to achieve high-quality products faster”</em> and even aiding communication between technical and non-technical team members . This highlights a unique benefit in healthcare: AI can bridge gaps between clinicians and engineers by translating requirements or explaining code in plain language, thereby reducing misunderstandings in software that directly affects patient care.</p>
<p>In practice, healthcare software teams are cautiously adopting AI coding tools to speed up projects like electronic health record (EHR) system extensions, health data analytics platforms, or telemedicine apps. For instance, developers at a health-tech startup used an AI assistant to generate portions of boilerplate code for interfacing with HL7/FHIR healthcare data standards. This automation saved the team considerable time and ensured that the code adhered to the complex FHIR schemas (since the AI was trained on the standard). Hospitals’ IT departments are also experimenting with AI to help manage infrastructure and configuration for their clinical software – similar to other industries, writing “infrastructure as code” with AI guidance to quickly set up secure cloud environments for health applications. The <strong>biggest gains in healthcare</strong> seem to be where AI helps handle the extensive documentation and validation processes: generating user documentation, API docs, and even drafting regulatory submission forms based on the code. By automating these laborious tasks, AI lets healthcare developers focus on core functionality and safety. A key success story often cited is how an AI tool was used to run through thousands of lines of code in a medical imaging application to identify potential concurrency issues that could have caused rare failures – something that would have been like finding a needle in a haystack for humans, but the AI pattern recognition flagged the problematic code path in minutes. This prevented a costly bug in a live clinical setting, underlining how AI can contribute to <strong>higher reliability</strong> in healthcare software.</p>
<p><strong>SaaS and Cloud Software</strong> – Software-as-a-Service companies, especially startups and cloud-native firms, have been among the fastest adopters of generative AI in development. Their engineering cultures tend to embrace new productivity tools, and the competitive pressure to ship features continuously makes AI assistance very appealing. Many SaaS teams now treat AI coding tools as a standard part of the developer toolkit. For example, engineers at <strong>GitLab</strong> and <strong>Salesforce</strong> have spoken publicly about using AI to improve their development process – GitLab is building AI features into its platform (Code Suggestions) and allowing its own developers to offload mundane coding to AI, and Salesforce’s dev teams use their proprietary AI (CodeT5-based) to automate writing test classes for the Salesforce platform. Even smaller SaaS startups report significant boosts: anecdotally, one CTO of a SaaS product noted that after enabling AI coding assistant access for the team, their <strong>development velocity increased such that they went from bi-weekly releases to multiple deployments per week</strong>. The developers felt more confident tackling new languages and frameworks because the AI could assist them through unfamiliar territory, effectively lowering the learning curve for adopting new tech in their stack.</p>
<p>Surveys back this trend: by late 2023, <strong>over 70% of developers</strong> said they were using AI tools in their workflow, although many only partially trust the accuracy of the outputs . In the context of SaaS, AI helps ensure that continuous delivery pipelines keep moving smoothly. For instance, SaaS companies often maintain extensive regression test suites; AI tools can analyze code changes and prioritize which tests to run or even auto-generate additional tests when a new feature is added, catching issues before production. Incident management in cloud services is another area – AIOps tools are commonly used by SaaS providers to maintain high uptime SLAs. Companies like Netflix and Uber (which operate large-scale SaaS-like platforms) have internal AI-driven systems that predict outages or automatically tune system parameters (like cache sizes, thread pools) based on learned patterns, reducing manual ops work and downtime. A <strong>success story</strong> here is Netflix’s AI-powered alerting system (part of their platform called <em>Vizceral</em> and <em>Telltale</em>): it learned normal vs.&nbsp;abnormal microservice call patterns and could alert engineers to a problem in a specific service <em>before</em> that service actually crashed, sometimes preventing user-visible incidents entirely. While not “generative” AI in the sense of code, it shows how intelligent automation in DevOps tangibly improves reliability for SaaS offerings.</p>
<p>Across industries, a common theme is emerging: AI is becoming a collaborative teammate in software engineering. Whether it’s helping a financial institution refactor an old COBOL system, assisting a healthcare startup in ensuring their app meets privacy requirements, or enabling a SaaS company to iterate features faster, <strong>AI is driving notable productivity and quality gains</strong>. Organizations that have embraced these tools often report not just faster delivery, but also improved developer morale – engineers spend more time on creative tasks and less on tedious debugging or configuration. A GitHub survey of 2,000 developers found <strong>88% felt more productive</strong> and <strong>74% felt able to focus on more satisfying work</strong> when using AI coding tools . These qualitative outcomes are as important as the quantitative ones. Of course, each industry also faces specific constraints (for example, fintech and healthcare must be extra careful about security/compliance with AI), which leads us to examine the challenges and risks in the next section.</p>
</section>
<section id="challenges-and-risks" class="level2">
<h2 class="anchored" data-anchor-id="challenges-and-risks"><strong>4. Challenges and Risks</strong></h2>
<p>Despite its exciting benefits, generative AI in software engineering brings along a set of <strong>challenges, risks, and concerns</strong> that organizations must address. These span security issues, potential technical debt, over-reliance pitfalls, and ethical or compliance questions:</p>
<ul>
<li><strong>Security Vulnerabilities</strong>: One of the most prominent concerns is that AI-generated code may introduce security flaws. By design, an AI coding assistant learns from existing code – some of which is insecure or outdated. As a result, it might suggest solutions that are functionally correct but not security-hardened. Studies have quantified this risk. In a controlled experiment, roughly <strong>40%</strong> of code completions from GitHub Copilot contained vulnerabilities under certain conditions . These were not trivial mistakes, but issues aligned with dangerous CWE patterns (like SQL injection, hard-coded credentials, buffer overruns, etc.). The AI, having seen many instances of such flawed code in training, can inadvertently reproduce them. Additionally, tools like Copilot lack true understanding of code logic and thus cannot reliably judge security implications . Snyk’s research noted that AI suggestions often <strong>imitate and even amplify existing bugs</strong> in a codebase . For example, if a codebase has a subtle input validation bug and the developer asks the AI for a similar function elsewhere, the AI might propagate that same bug into the new code. This “ignorant cloning” of vulnerabilities can expand the attack surface. The risk is not purely hypothetical: there have been reports of AI tools recommending use of deprecated or insecure functions (like using md5 for hashing passwords, or using older cryptographic libraries with known exploits). To mitigate this, teams must implement AI-assisted <strong>code review and testing</strong> with security in mind. It’s advisable to pair AI code generation with robust static analysis and linting. Some AI tools are now being augmented with security scanners (OpenAI’s own Codex model now has a filter for known insecure patterns, and products like Amazon CodeWhisperer include security issue flagging for generated code). Ultimately, developers should treat AI suggestions as they would a junior developer’s code: <strong>trust but verify</strong>. Security training for developers now increasingly includes awareness of AI-specific failure modes – e.g., instructing engineers on how to prompt AI for more secure outputs and not to accept code blindly.</li>
<li><strong>Software Supply Chain Risks</strong>: Generative AI effectively becomes part of your software supply chain. It introduces a <em>new link</em> in that chain – the AI itself and its training data. This raises questions about provenance and trust. The training datasets for code AIs include millions of open-source repositories, some containing known vulnerabilities, malicious code, or unlicensed content. According to a Synopsys report, the repositories used to train generative coding tools <em>“contain vulnerabilities, copyrighted code, and code with restrictive licensing”</em> . So when the AI produces code, there’s a chance (even if small) it might regurgitate a snippet that has a known CVE or one that is copy-pasted from a GPL-licensed project. This is a supply chain issue because you are essentially pulling in code from an external source (the model) without the normal vetting you might do for a third-party library. Indeed, a phenomenon dubbed “package hallucination” has been observed, where an AI suggests importing a software package that doesn’t exist or is outdated – if a developer doesn’t catch that, they might waste time or introduce a sketchy dependency unknowingly . From a governance perspective, companies need to apply the same diligence to AI-generated code as they do to open-source components. That includes scanning AI contributions for vulnerabilities and licenses. The <strong>licensing risk</strong> is non-trivial: GitHub Copilot’s model was trained on public GitHub code that included copyleft licenses (GPL, AGPL, etc.), which legally require derivatives to be open-sourced. There is an ongoing <strong>class-action lawsuit</strong> against Microsoft/GitHub/OpenAI alleging that Copilot violated open-source licenses by allowing verbatim output of licensed code . GitHub has stated that direct copies from training data are rare (approximately 1% of Copilot’s outputs were found to match chunks of training set code verbatim) , and they’ve since introduced filters to reduce this. But the fact remains – without transparency into which training repo an AI suggestion came from, you can’t be certain of the license or integrity of that code. Some organizations (like those in defense or healthcare) have responded by using <strong>self-hosted AI models trained on their own code only</strong>, to avoid these supply chain uncertainties. Others use tools that restrict suggestions to only what could be derived from permissible licenses. This is an evolving area of risk management, but it’s clear that AI-generated code must be treated as third-party code: scanned, reviewed, and vetted for security &amp; compliance before being trusted in production .</li>
<li><strong>Technical Debt and Maintainability</strong>: While AI can generate code quickly, that code is not guaranteed to be optimal or easily maintainable. There’s a concern that indiscriminate use of AI could lead to a buildup of <strong>technical debt</strong> – code that works in the short term but is poor in structure or clarity, making future changes harder. Developers may be tempted to accept AI suggestions without fully understanding them, especially when under pressure. Over time, a codebase infused with chunks of AI-written code might become a patchwork that lacks a consistent style or architecture. If the team doesn’t thoroughly refactor or at least review AI contributions, they might end up “paying interest” on that technical debt later in the form of more bugs or slower modifications. Another aspect is that AI can produce very verbose solutions if not guided well – it might write 50 lines of code where a seasoned engineer would use 5 lines of a library call. Such bloat is harder to maintain. There’s also the scenario of <strong>over-reliance on AI</strong> causing skill atrophy: if developers start relying on AI for every small task, they might lose the sharpness in fundamentals needed to debug or improve that code. Experts have warned of a “use it or lose it” effect on coding skills . In a devil’s advocate view, if a generation of programmers grows too accustomed to AI doing the heavy lifting, they may struggle when the AI fails or when they need to implement something truly novel that isn’t in the training data. Moreover, certain complex bug fixes or performance optimizations often require deep understanding – a human who hasn’t been in the weeds of the code because the AI handled most of it could find it difficult to step in during a crisis. To combat these issues, some teams enforce a rule that any AI-generated code must be reviewed and explained by the human who integrates it, ensuring understanding. Code maintainability can also be addressed by having the AI itself generate documentation or comments for the code it writes (and indeed many use Copilot to also generate docstrings and comments). Still, the risk of accumulating subpar code remains. The industry may respond with new linters or quality gates specifically targeting AI-generated patterns (for example, tools to detect if code looks copy-pasted or overly verbose). In summary, AI can produce a lot of code very fast – teams need to be disciplined to avoid creating a <em>debt spiral</em> of quick-and-dirty AI code that will cost more to fix later.</li>
<li><strong>Ethical and Compliance Concerns</strong>: Beyond the technical facets, there are ethical issues and regulatory compliance matters when using AI in software engineering. <strong>Data privacy</strong> is a major one – many AI coding tools (like cloud-based ones) require sending your code context to a third-party server to generate suggestions. This can conflict with privacy policies or regulations if the code contains sensitive information (such as customer data, proprietary algorithms, or security keys). Companies in sectors like finance and healthcare have had to be cautious: for instance, some banks initially banned tools like Copilot until on-premises or anonymized solutions became available, because uploading internal source code to an external AI service posed too high a risk. Even if the AI provider claims not to store the code, organizations subject to strict compliance (GDPR, HIPAA, etc.) prefer not to take chances. <strong>Regulatory compliance</strong> extends to ensuring that AI usage doesn’t lead to violations – e.g., if an AI generated a piece of code that inadvertently bypasses an accessibility requirement or a financial audit control, the company is still fully accountable for that. A vivid example is in algorithmic trading software (fintech): if an AI suggests code that doesn’t properly implement a compliance rule (say, missing a check on trading limits), it could lead to illegal behavior by the system. Thus, oversight and testing are paramount. There’s also an <strong>accountability and ethics</strong> question: if an AI contributes to code that fails and causes harm (imagine a medical device software glitch or a self-driving car error), who is responsible? The developers, the company, or the tool maker? Legally, today the responsibility lies with the human operators and companies deploying the software. Ethically, this means companies must treat AI outputs with the same rigor as human outputs. Some industry guidelines suggest maintaining logs of AI suggestions accepted, so there’s traceability of why a certain code was written (in case auditors or investigators need to know). Another concern is <strong>bias</strong> – while not as discussed in coding as in AI decision-making systems, it’s possible that AI could systematically favor certain implementations or languages that reflect bias in training data. For instance, it might not suggest solutions that use newer frameworks (if trained mostly on older code), thereby subtly stifling innovation or perpetuating old practices. Finally, as mentioned earlier, the ethical use of open-source code is a heated debate. The fact that generative AI might regurgitate someone’s GPL-licensed code without credit is seen by some as an ethical violation of the open-source community norms. This has sparked movements like the Free Software Foundation’s call to boycott tools like Copilot until these issues are resolved . In regulated industries, we also see a push for <strong>AI governance</strong> – ensuring AI tools themselves meet certain standards (transparency, bias mitigation, auditability). Although coding assistants are just tools, enterprises are beginning to include them in governance frameworks: for example, requiring that any AI used in the SDLC must be approved by a security and compliance review, just as one would vet a new third-party software component.</li>
</ul>
<p>In light of these challenges, organizations are adopting strategies to reap AI’s benefits while managing risks. <strong>Human oversight</strong> is a recurring theme: treat AI as an assistant, not an autonomous developer. Many companies institute mandatory code reviews for AI-generated code (no blind commits from AI). <strong>Policy guardrails</strong> and lint rules can automatically reject code that looks suspiciously like an insecure pattern. Education is also key – training developers on how to effectively use prompts and how to double-check AI outputs. Tool vendors are responding too: expect to see “responsible AI” features, such as enterprise AI coding assistants that allow setting rules (e.g., “never suggest code that uses eval() or other dangerous functions”). Despite the risks, the consensus is that with proper controls, the advantages of AI-assisted development far outweigh the downsides. We are effectively learning how to code with a powerful but sometimes unreliable apprentice; the onus is on us to mentor and verify the apprentice’s work. In the next section, we’ll look at how this dynamic might evolve, and what the future holds for AI in DevOps – including how these challenges might be addressed by next-generation solutions.</p>
</section>
<section id="future-outlook" class="level2">
<h2 class="anchored" data-anchor-id="future-outlook"><strong>5. Future Outlook</strong></h2>
<p>The rapid evolution of generative AI in software engineering suggests that we are only at the <strong>early stages</strong> of a major transformation in how software is built and delivered. Looking ahead, we can expect AI-driven development and DevOps to become even more capable and deeply integrated. Here are some key predictions and possibilities for the future, along with their potential impact on DORA metrics and the software industry at large:</p>
<ul>
<li><strong>More Powerful and Specialized AI Models</strong>: Future AI coding assistants will likely be far more powerful than today’s. With companies like OpenAI, Google, and others continuously improving model size and training techniques, we may see models that understand not just coding syntax but also higher-level design and architecture intentions. This could enable AI to handle larger scopes of tasks. For instance, instead of one function or file at a time, you might ask an AI to implement an entire microservice given an interface specification – and it could do so, including writing the documentation and tests. We also anticipate more <strong>domain-specific AI models</strong>: models fine-tuned for particular tech stacks or industries. Imagine an AI assistant that is an expert in <strong>Android app development</strong> (well-versed in Android SDK idiosyncrasies), or one specialized in <strong>financial software</strong> (aware of common formulas, compliance checks, and patterns in fintech). These specialized AIs will produce more relevant and safer code in their niche. As model quality improves, developer trust in AI will grow, addressing one current barrier. In fact, Gartner forecasts that by around 2028, <strong>75% of enterprise developers will be using AI coding assistants regularly</strong> – effectively making it a standard tool like an IDE or version control. Such ubiquity will push organizations to optimize their entire development workflow around AI, potentially leading to substantial efficiency gains. We might even see a shift in required skills – future DevOps engineers could need to be as adept at <strong>prompt engineering</strong> (communicating intent to AI) as they are at programming itself.</li>
<li><strong>Deeper Integration into DevOps Pipelines</strong>: In the coming years, AI is expected to be embedded at every stage of the DevOps pipeline, creating a kind of <strong>self-driving CI/CD</strong>. For example, continuous integration systems might use AI to intelligently decide which subset of tests to run for a given change (cutting CI time dramatically) and to auto-tune pipeline parameters (like build container sizes, cache usage) for optimal speed. Continuous delivery could benefit from AI by automatically generating deployment manifests or Helm charts from high-level descriptors and by using predictive analytics to decide the safest time to deploy (for instance, learning from past incidents that deploying certain services at peak traffic times causes issues). Incident management will become increasingly proactive: AI might predict incidents before they happen by recognizing precursor signals – effectively achieving <strong>preventative healing</strong>. In terms of DORA metrics, these advances could push elite performance to new heights: deployment frequency that currently might max out at multiple times per day could evolve to <strong>on-demand deployment for each commit</strong> (since the AI ensures everything is tested and correct instantaneously). Lead time for changes might shrink to minutes for many routine updates. One can envision a scenario where a developer simply writes a high-level description of a feature, and the AI handles coding, testing, and deployment, all in a continuous flow. There is an optimistic view in research that a fully AI-driven SDLC could <em>“drastically reduce time and costs…potentially reducing [time] to mere minutes”</em> for delivering software . While minutes might be too optimistic for general cases, the direction is toward <strong>hyper-automation</strong>, where the bottlenecks in software delivery are removed one by one by AI interventions.</li>
<li><strong>Partial to Full Automation of Software Development</strong>: Experts have outlined scenarios of how AI involvement might increase over time . In the near future, we expect <strong>incremental assistance</strong> to continue – AI helping with code suggestions, test generation, etc., under human guidance (this is already happening). The next stage is <strong>partial automation</strong>: certain tasks could be wholly handed over to AI. We’re already seeing beginnings of this with documentation generation and simple unit test writing – tasks that might be 90% AI and 10% human oversight. As confidence and capability grow, more tasks will shift. Some bold predictions suggest that within a decade, we could reach a stage of <strong>full automation for specific types of projects</strong>. For example, given a well-understood problem domain, an AI might feasibly generate an entire application end-to-end. In research labs, multi-agent systems like <em>ChatDev, MetaGPT, and GPT-Pilot</em> have demonstrated AI agents collaborating to mimic a software team – with roles like product manager, coder, tester – to build a software project from scratch . Currently, these are experimental and the software produced is relatively simple, but the concept is profound. It hints at a future where you could spin up a “virtual software team” of AI agents to implement features overnight. In practice, full automation will likely be gradual and domain-specific (we might see it in well-bounded domains like generating a standard CRUD app or a website). If and when full automation becomes viable, it could revolutionize DevOps: deployments could be triggered directly by high-level business requests without a developer typing code. DORA metrics in such a world would be on the extreme end – lead time measured in hours or less, deployment frequency potentially innumerable (every change auto-deployed), failure rates theoretically very low if AI and testing are exhaustive, and MTTR approaching zero due to self-healing. However, this utopia comes with the caveat of <strong>trust and verification</strong>. Even in a fully automated pipeline, organizations will need strong validation and oversight (perhaps AI validators) to ensure software correctness. As one expert quipped, even if coding becomes fully automated, <strong>debugging and guiding the AI</strong> will remain a crucial human task – somewhat akin to how autopilot in planes works under a pilot’s supervision.</li>
<li><strong>Changes in Developer Roles and Skills</strong>: As AI takes over routine parts of development, the role of human developers and operators will shift more towards <strong>strategic and creative work</strong>. Developers might spend less time writing boilerplate code and more time on defining architecture, protocols, and ensuring ethical and correct outcomes. The skill of formulating proper problem specifications (so that an AI can implement them) will be at a premium. We may also see new roles like an “AI DevOps Engineer” whose job is to manage and tune the AI tools themselves – feeding them data, customizing models for the company’s codebase, and handling the interface between human teams and AI output. The nature of code reviews might change: instead of reviewing colleagues’ code, developers might review AI-generated code or even the prompts/constraints given to AI. It’s likely that pair programming with an AI will become a standard workflow – every developer essentially has a “pair” who is an AI agent. This could improve onboarding (junior devs ramp up faster with AI guidance) and overall team throughput. On the flip side, organizations will have to manage the <strong>cultural impact</strong> – developers must maintain their sense of ownership and expertise, rather than blindly deferring to AI. There could be resistance or disruption as some traditional workflows get replaced; proactive training and a focus on upskilling developers to work <em>with</em> AI will be important. Encouragingly, the DORA 2024 report found that as <strong>trust in AI tools increases, developers become more willing to integrate them</strong> into daily work . We can infer that in the future, high-performing teams will be those that have built a strong trust+verify relationship with their AI assistants.</li>
<li><strong>Improved DORA Metrics and New Metrics</strong>: In terms of DevOps performance, the continued adoption of AI should lead to more teams achieving <strong>elite DORA metrics</strong> (as defined by DORA’s State of DevOps research). We expect the bar for “elite” to keep moving upward as AI enables greater efficiency. It’s conceivable that deployment frequency measured in multiple times per day (today’s elite) will become the norm, and elite teams might deploy hundreds or thousands of times per day in micro-deployments. Lead time for standard changes might shrink to hours or minutes. Change failure rates could drop further if AI helps eliminate common errors – some have envisioned the possibility of <strong>zero-touch deployments</strong> that are auto-verified to such a degree that failures are almost nonexistent (though unexpected problems can always occur). Time to restore could also approach zero downtime through predictive healing. However, as AI takes over more, organizations might introduce <strong>new metrics</strong> to track what really matters. For example, if deployment becomes instantaneous, “deployment frequency” may be less useful; instead, metrics around <em>value delivery</em> or <em>customer feedback loop time</em> might gain prominence. DORA might evolve to include metrics like <strong>Automated Change Percentage</strong> (the proportion of changes fully handled by AI vs human) or <strong>AI Suggestion Acceptance Rate</strong> as indicators of how well teams are leveraging automation. Also, human-centric metrics like developer satisfaction and learning might become key – ensuring that the human side of DevOps isn’t neglected in an AI-heavy process. The future will require balancing speed with <strong>governance</strong>. We anticipate more tooling around AI auditability: for instance, maintaining an audit trail of every code change an AI made, and perhaps metrics on how often those needed human correction (a measure of AI reliability). Long term, the hope is that AI will enable a level of software delivery performance that we currently can barely imagine, while freeing developers to concentrate on innovation.</li>
<li><strong>Evolution of Tools and Platforms</strong>: The next generation of DevOps platforms will likely come with AI capabilities out-of-the-box. We’re already seeing CI/CD services integrating AI for test optimization and error analysis. In the future, platforms like GitHub, GitLab, Azure DevOps, etc., will have AI woven throughout: AI-assisted commit message writing, AI-generated release notes, automated ticket creation from monitoring alerts, and so on. Version control might include AI that can summarize diffs or suggest which team members should review a particular PR based on its content. Incident management systems will have AI incident commanders that handle coordination. We may also see <strong>AI-driven project management</strong> – AI analyzing velocity and suggesting adjustments in sprint scope, or even drafting user stories based on stakeholder conversations. Essentially, DevOps loops (Plan -&gt; Code -&gt; Build -&gt; Test -&gt; Release -&gt; Monitor -&gt; Plan) could each have AI augmentations. Companies like Microsoft (with its Copilot suite) are actively working on this “AI everywhere” vision. As these tools mature, smaller organizations will get access to capabilities that previously only tech giants with custom AI could afford. This democratization could raise the overall performance of the software industry – even a 5-person startup can achieve automation and reliability on par with a big tech firm, by renting powerful AI dev tools.</li>
</ul>
<p>In conclusion, the future of AI-powered software engineering is bright but will require careful navigation. Generative AI is poised to become an <strong>indispensable partner</strong> in development and DevOps, driving efficiency and innovation to levels previously unattainable. If the current trajectory holds, we will see faster delivery cycles, more reliable releases, and perhaps entirely new ways of conceiving software (with AI as a design partner). Importantly, the <strong>human element</strong> remains vital. The organizations that succeed will be those that blend human creativity and oversight with AI’s speed and scale. As one expert noted, AI won’t replace developers, but developers who use AI <strong>will likely replace those who do not</strong> – echoing the competitive edge it provides. DevOps teams should thus prepare for this future by embracing AI tools in a controlled, educated manner, updating their practices and metrics as needed. With sound strategy, generative AI can help achieve the DevOps holy grail: deploying <strong>better software, faster and more reliably</strong> than ever before, while freeing engineers to focus on the next big problem to solve.</p>
</section>
<section id="references" class="level2">
<h2 class="anchored" data-anchor-id="references"><strong>References</strong></h2>
<ol type="1">
<li><p><a href="https://www.iese.fraunhofer.de/blog/generative-ai-in-software-engineering-scenarios-and-challenges/">Siebert, J. &amp; Jedlitschka, A.</a> <a href="https://www.iese.fraunhofer.de/blog/generative-ai-in-software-engineering-scenarios-and-challenges/#:~:text=The%20integration%20of%20generative%20AI,code%20review%2C%20testing%2C%20and%20design"><em>Fraunhofer IESE Blog</em></a> – <em>Generative AI in Software Engineering: Scenarios and Challenges Ahead</em> (Nov 2024) – Overview of generative AI’s potential in coding, testing, DevOps, and future scenarios .</p></li>
<li><p><a href="https://www.techtarget.com/searchenterpriseai/tip/Compare-top-AI-coding-tools#:~:text=algorithms,visibility%20of%20AI%20coding%20tools">Craig, L. &amp; Kelly, W.</a> <a href="https://www.techtarget.com/searchenterpriseai/tip/Compare-top-AI-coding-tools"><em>TechTarget (SearchEnterpriseAI)</em></a> – <em>Compare 7 of the best AI coding tools for 2025</em> (Nov 2024) – Market size and growth of AI coding tools, integration of LLMs into coding .</p></li>
<li><p><a href="https://www.infoq.com/news/2024/11/2024-dora-report/">InfoQ News</a> – <em>2024 Accelerate State of DevOps Report Shows Pros and Cons of AI</em> (Nov 2024) – DORA survey insights on AI adoption, trust, and performance impacts (throughput ↓1.5%, stability ↓7.2% with AI; early adopter benefits) .</p></li>
<li><p><a href="https://resources.github.com/learn/pathways/copilot/essentials/measuring-the-impact-of-github-copilot/">GitHub Product Blog</a> – <em>Quantifying GitHub Copilot’s impact on developer productivity</em> (2023) – Reports 55% faster task completion and improved code quality with AI pair programming .</p></li>
<li><p><a href="https://www.techtarget.com/searchenterpriseai/tip/Compare-top-AI-coding-tools#:~:text=algorithms,visibility%20of%20AI%20coding%20tools">GitHub Resources</a> – <em>Measuring the impact of GitHub Copilot</em> (Ryan Salva, 2023) – Enterprise study findings: 55% faster coding, 50% faster pull request merge time using Copilot .</p></li>
<li><p><a href="https://sciencelogic.com/blog/reducing-mttr-and-the-hidden-costs-of-downtime-through-ai-automation#:~:text=monitoring%20%2C%20IT%20workflow%20automation%2C,time">ScienceLogic Blog</a> – <em>Reducing MTTR and the Hidden Costs of Downtime Through AI &amp; Automation</em> (2025) – Example of AIOps: automated root cause analysis diagnosing issues 10× faster, improving incident response .</p></li>
<li><p><a href="https://www.darkreading.com/vulnerabilities-threats/ai-generated-code-a-new-link-in-the-software-supply-chain">Dark Reading (Synopsys)</a> – <em>AI-Generated Code: A New Link in the Software Supply Chain</em> (May 2024) – Gartner prediction of 75% of developers using AI by ~2028; warns training data contains vulnerabilities and restrictive licenses .</p></li>
<li><p><a href="https://cacm.acm.org/research-highlights/asleep-at-the-keyboard-assessing-the-security-of-github-copilots-code-contributions/">Pearce, H. et al.&nbsp;(NYU)</a> – <em>“Asleep at the Keyboard? Assessing the Security of GitHub Copilot’s Code Contributions,”</em> <strong>CACM Research Highlight</strong> (Jan 2025) – Security analysis finding ~40% of Copilot-generated programs vulnerable .</p></li>
<li><p><a href="https://www.techtarget.com/searchsecurity/news/366571117/GitHub-Copilot-replicating-vulnerabilities-insecure-code">TechTarget (SearchSecurity)</a> – <em>“GitHub Copilot replicating vulnerabilities, insecure code”</em> (Feb 2024) – Snyk research finding Copilot can amplify existing bugs and security issues due to lack of code understanding.</p></li>
<li><p><a href="https://www.styra.com/blog/ai-generated-infrastructure-as-code-the-good-the-bad-and-the-ugly/">Styra Blog</a> – <em>AI-Generated Infrastructure-as-Code: The Good, the Bad and the Ugly</em> (2023) – Discussion of IaC with AI; notes that AI-generated infra code is often invalid or insecure without human review, and highlights license concerns (Copilot training on GPL code, etc.).</p></li>
<li><p>Stack Overflow Developer Survey 2023 (key insights via <a href="http://devops.com/">DevOps - The Web’s Largest Collection of DevOps Content</a> , July 2023) – Found 70% of developers using AI tools, Copilot the most popular at 55% usage.</p>
<p><img src="https://devops.com/wp-content/uploads/2021/10/android-chrome-256x256-1-130x130.png" class="img-fluid"></p></li>
<li><p><a href="https://www.griddynamics.com/blog/genai-fintech-software-development">Grid Dynamics Blog</a> – <em>Generative AI’s impact on fintech software development</em> (Oct 2023) – Case study with quantitative metrics: code translation productivity +15–90%, API task time –62%, documentation writing +70-90% efficiency.</p></li>
<li><p><a href="https://pubmed.ncbi.nlm.nih.gov/38446539/">Rodriguez, D.V. <em>et al.</em>, <em>JMIR Human Factors</em> (Mar 2024)</a> – <em>Case Study: Leveraging Generative AI (ChatGPT) in Health Care Software Development</em> – Found AI improved coding efficiency and helped achieve high-quality results faster in a digital health app.</p></li>
<li><p><a href="http://devops.com/">DevOps - The Web’s Largest Collection of DevOps Content</a> – <em>Measuring GitHub Copilot’s Impact on Engineering Productivity</em> (Dec 2023) – Discusses metrics to evaluate AI in development; mentions 88% developers felt more productive with Copilot (GitHub survey).</p>
<p><img src="https://devops.com/wp-content/uploads/2021/10/android-chrome-256x256-1-130x130.png" class="img-fluid"></p></li>
</ol>


</section>

 ]]></description>
  <category>generative-ai</category>
  <category>opinions</category>
  <guid>https://ghif.github.io/posts/2025-03-06-generative-ai-software-engineering-dora/</guid>
  <pubDate>Thu, 06 Mar 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Recent Advancements in Deep Learning Optimization</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2025-03-01-deep-learning-optimization-advancements/</link>
  <description><![CDATA[ 




<aside>
<p>💡</p>
<p>This article was written with the help of <a href="https://openai.com/index/introducing-deep-research/">OpenAI’s deep research</a>, an agentic AI tool that employs reasoning to synthesize extensive online information and execute complex multi-step research tasks.</p>
</aside>
<p>Deep learning continues to advance, with optimization algorithms playing a crucial role in enhancing model efficiency. Recent developments in optimization techniques have introduced new methods that improve convergence rates, reduce computational costs, and enhance model generalization.</p>
<p>This article discusses the latest advancements in deep learning optimization, including novel optimizers such as Lion and Sophia, as well as second-order techniques that contribute to more efficient training processes.</p>
<section id="new-and-improved-optimization-algorithms" class="level2">
<h2 class="anchored" data-anchor-id="new-and-improved-optimization-algorithms">New and Improved Optimization Algorithms</h2>
<section id="lion-evolved-sign-momentum" class="level3">
<h3 class="anchored" data-anchor-id="lion-evolved-sign-momentum">1. Lion (Evolved Sign Momentum)</h3>
<p>Lion is a recently developed optimizer identified through AutoML (<a href="https://arxiv.org/pdf/2302.06675">Chen et al., 2023</a>). It updates weights based on the sign of the gradient rather than its magnitude, reducing computational overhead while maintaining effective training performance. Lion has been shown to achieve improved accuracy in image classification tasks, particularly in Vision Transformer (ViT) models (<a href="https://arxiv.org/abs/2010.11929">Dosovitskiy et al., 2020</a>), with reduced computational requirements compared to AdamW (<a href="https://arxiv.org/pdf/1711.05101v3">Loshchilov &amp; Hutter, 2019</a>).</p>
</section>
<section id="lamb-layer-wise-adaptive-moments-for-batch-training" class="level3">
<h3 class="anchored" data-anchor-id="lamb-layer-wise-adaptive-moments-for-batch-training">2. LAMB (Layer-wise Adaptive Moments for Batch Training)</h3>
<p>LAMB is designed for large-batch training and has been particularly useful for transformer-based models such as BERT (<a href="https://arxiv.org/pdf/1904.00962">You et al., 2020</a>). It incorporates layer-wise normalization to maintain stable learning dynamics in large-scale training scenarios, enabling efficient training with significantly larger batch sizes compared to traditional optimizers.</p>
</section>
<section id="adafactor-memory-efficient-optimization" class="level3">
<h3 class="anchored" data-anchor-id="adafactor-memory-efficient-optimization"><strong>3. AdaFactor: Memory-Efficient Optimization</strong></h3>
<p>AdaFactor, a variant of Adam, reduces memory overhead by storing only row- and column-wise squared gradient sums rather than full matrices (<a href="https://proceedings.mlr.press/v80/shazeer18a/shazeer18a.pdf">Shazeer &amp; Stern, 2018</a>). It reduces memory usage to sublinear in model size while delivering similar convergence as Adam. This makes it particularly beneficial for training large-scale models, such as T5, on resource-limited hardware.</p>
</section>
<section id="adabelief" class="level3">
<h3 class="anchored" data-anchor-id="adabelief">4. AdaBelief</h3>
<p>AdaBelief, another variant of Adam, adapts step sizes by how much gradient deviates from an expected trend (<a href="https://arxiv.org/pdf/2010.07468">Zhuang et al.&nbsp;2020</a>). It treats the exponential moving average of past gradients as a prediction of the next gradient. This approach merges the benefits of adaptive methods and SGD: AdaBelief attains faster convergence like Adam but with SGD-like generalization. On ImageNet, AdaBelief achieved accuracy on par with SGD (unusual for an adaptive optimizer). It’s also noted for stability in GAN training, outperforming a well-tuned Adam on CIFAR-10 GANs.</p>
<p><strong>Other Adam improvements:</strong> AdamW (<a href="https://arxiv.org/pdf/1711.05101v3">Loshchilov &amp; Hutter, 2019</a>) decoupled weight decay from the gradient update, improving regularization and becoming a default in vision transformers. AMSGrad (<a href="https://openreview.net/pdf?id=ryQu7f-RZ">Reddi et al.&nbsp;2018</a>) was introduced to handle the convergence failure by Adam in some simple cases; AMSGrad fixes this by enforcing a non-increasing second-moment term, provably restoring convergence in theory. RAdam (Rectified Adam) addressed Adam’s reliance on learning-rate warmup by analytically adjusting the adaptive learning rate variance, leading to more stable training without manual warmup (<a href="https://arxiv.org/pdf/1908.03265">Liu et al., 2020</a>). Lookahead is another innovation where one optimizer’s updates are periodically “averaged” into a slow-moving set of weights improving stability and often final accuracy. Each of these refinements targets specific weaknesses in Adam (e.g., instability, generalization, or need for hyperparameter tricks) to make optimization more robust.</p>
</section>
</section>
<section id="advancements-in-learning-rate-adaptation-and-stability" class="level2">
<h2 class="anchored" data-anchor-id="advancements-in-learning-rate-adaptation-and-stability">Advancements in Learning Rate Adaptation and Stability</h2>
<section id="momentum-based-optimizers" class="level3">
<h3 class="anchored" data-anchor-id="momentum-based-optimizers">Momentum-based Optimizers</h3>
<p>Momentum-based approaches, including Nesterov acceleration and sign-based methods such Lion and signSGD, contribute to improve training stability and convergence speed. These techniques help mitigate the effects of noisy gradients.</p>
</section>
<section id="gradient-clipping-for-stability" class="level3">
<h3 class="anchored" data-anchor-id="gradient-clipping-for-stability">Gradient Clipping for Stability</h3>
<p>Gradient clipping techniques, including per-layer norm clipping and adaptive clipping, have been widely adopted to prevent instability caused by exploding gradients in deep networks.</p>
</section>
<section id="dynamic-learning-rate-schedules" class="level3">
<h3 class="anchored" data-anchor-id="dynamic-learning-rate-schedules">Dynamic Learning Rate Schedules</h3>
<p>Recent research has introduced advanced learning rate scheduling strategies, such as cosine annealing, cyclical decay, and adaptive step size adjustments. These approaches improve convergence rates and enhance overall training stability.</p>
</section>
</section>
<section id="developments-in-second-order-optimization" class="level2">
<h2 class="anchored" data-anchor-id="developments-in-second-order-optimization">Developments in Second-Order Optimization</h2>
<p>Second-order optimization methods, which utilize curvature information, have traditionally been computationally expensive. However, recent advancements have made them more practical:</p>
<section id="shampoo-scalable-second-order-optimization" class="level3">
<h3 class="anchored" data-anchor-id="shampoo-scalable-second-order-optimization">1. Shampoo: Scalable Second-Order Optimization</h3>
<p>Shampoo employs a Kronecker factorization to approximate second-order gradients, enabling improved optimization efficiency in large-scale deep learning models (<a href="https://arxiv.org/pdf/2002.09018">Anil et al., 2021</a>).</p>
</section>
<section id="k-fac-kronecker-factored-approximated-curvature" class="level3">
<h3 class="anchored" data-anchor-id="k-fac-kronecker-factored-approximated-curvature">2. K-FAC (Kronecker-Factored Approximated Curvature)</h3>
<p>K-FAC is designed to approximate the Fisher information matrix, reducing the number of required training iterations, particularly in convolutional neural networks (<a href="https://arxiv.org/pdf/1503.05671">Martens &amp; Grosse et al., 2015</a>, <a href="https://arxiv.org/pdf/2311.00636">Eschenhagen et al., 2024</a>).</p>
</section>
<section id="sophia-second-order-clipped-optimization-for-large-models" class="level3">
<h3 class="anchored" data-anchor-id="sophia-second-order-clipped-optimization-for-large-models"><strong>3. Sophia (Second-Order Clipped Optimization for Large Models)</strong></h3>
<p>Sophia is an optimizer designed for training large language models (<a href="https://arxiv.org/pdf/2305.14342">Liu et al., 2023</a>). By approximating the Hessian matrix and applying element-wise clipping, it reduces the number of required training steps compared to Adam, leading to improved computational efficiency in large-scale settings.</p>
</section>
<section id="hybrid-approaches" class="level3">
<h3 class="anchored" data-anchor-id="hybrid-approaches">4. Hybrid Approaches</h3>
<p>Recent research has explored combining first- and second-order methods, such as SOAP (Scalable Optimizer for Adaptive Preconditioning), which integrates Shampoo’s curvature-based updates with Adam’s adaptive momentum (<a href="https://arxiv.org/pdf/2409.11321">Vyas et al., 2024</a>). These hybrid approaches have demonstrated improved convergence efficiency in large-batch training scenarios.</p>
</section>
</section>
<section id="empirical-evaluations-and-real-world-applications" class="level2">
<h2 class="anchored" data-anchor-id="empirical-evaluations-and-real-world-applications">Empirical Evaluations and Real-World Applications</h2>
<section id="computer-vision" class="level3">
<h3 class="anchored" data-anchor-id="computer-vision">Computer Vision</h3>
<p>Studies indicate that Lion has achieved improved accuracy in image classification tasks, particularly in training Vision Transformer models, while reducing computational resource requirements.</p>
</section>
<section id="natural-language-processing" class="level3">
<h3 class="anchored" data-anchor-id="natural-language-processing">Natural Language Processing</h3>
<p>Sophia has demonstrated improved efficiency in training large-scale language models, reducing training time and computational costs in comparison to Adam.</p>
</section>
<section id="generative-models" class="level3">
<h3 class="anchored" data-anchor-id="generative-models">Generative Models</h3>
<p>AdaBelief and Lion have been evaluated in generative adversarial network (GAN) training, where they have contributed to improved stability and enhanced output quality.</p>
</section>
<section id="reinforcement-learning" class="level3">
<h3 class="anchored" data-anchor-id="reinforcement-learning">Reinforcement Learning</h3>
<p>Preliminary evaluations suggest that sign-based optimizers, such as Lion, may offer advantages in reinforcement learning tasks by handling noisy gradient estimates more effectively.</p>
</section>
</section>
<section id="considerations-for-optimizer-selection" class="level2">
<h2 class="anchored" data-anchor-id="considerations-for-optimizer-selection">Considerations for Optimizer Selection</h2>
<p>Selecting an appropriate optimizer depends on the specific task and computational constraints:</p>
<ul>
<li><strong>For Natural Language Processing (NLP) Models</strong>: AdamW is a widely used default optimizer. AdaFactor is suitable for large-scale models where memory efficiency is a priority. Sophia may be considered for reducing training time in large-scale pretraining.</li>
<li><strong>For Computer Vision Models</strong>: SGD with momentum remains a preferred choice for moderate-sized datasets. Lion has demonstrated potential improvements in Vision Transformer training.</li>
<li><strong>For Reinforcement Learning</strong>: Adam and RMSprop are commonly used, through recent studies indicate potential benefits of alternative optimizers such as AdaBelief and Lion.</li>
<li><strong>For Large-Scale Distributed Training</strong>: LAMB and Distributed Shampoo are designed for efficient training with large batch sizes on multi-GPU or TPU systems.</li>
</ul>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>Advancements in deep learning optimization continue to improve training efficiency, model performance, and computational resource utilization. New optimizers such as Lion and Sophia, along with second-order methods like Shampoo and K-FAC, provide promising alternatives to traditional approaches. Future research is likely to further refine these methods and develop more adaptive optimization strategies suited to emerging deep learning techniques.</p>
<p>Practitioners and researchers should evaluate these optimization techniques in the context of their specific applications to achieve optimal performance outcomes.</p>


</section>

 ]]></description>
  <category>deep-learning</category>
  <category>mathematics</category>
  <guid>https://ghif.github.io/posts/2025-03-01-deep-learning-optimization-advancements/</guid>
  <pubDate>Sat, 01 Mar 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Activate PGVector Extension in PostgreSQL for RAG</title>
  <dc:creator>MUHAMMAD GHIFARY</dc:creator>
  <link>https://ghif.github.io/posts/2025-02-25-activate-pgvector-postgresql-rag/</link>
  <description><![CDATA[ 




<p>Last time we have seen how to setup PostgreSQL in mac OS. The following article is to enable an extension called PGVector, which is used to</p>
<section id="step-1-install-pgvector-from-the-source" class="level3">
<h3 class="anchored" data-anchor-id="step-1-install-pgvector-from-the-source">Step 1: Install pgvector from the source</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> /tmp</span>
<span id="cb1-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> clone <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--branch</span> v0.8.0 https://github.com/pgvector/pgvector.git</span>
<span id="cb1-3"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> pgvector</span>
<span id="cb1-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">make</span></span>
<span id="cb1-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">make</span> install <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># may need sudo</span></span></code></pre></div></div>
</section>
<section id="step-2-restart-postgresql-service" class="level3">
<h3 class="anchored" data-anchor-id="step-2-restart-postgresql-service">Step 2: Restart PostgreSQL service</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">brew</span> services restart postgresql@15</span></code></pre></div></div>
</section>
<section id="step-4-create-extension" class="level3">
<h3 class="anchored" data-anchor-id="step-4-create-extension">Step 4: Create extension</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Start PostgreSQL service</span></span>
<span id="cb3-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">brew</span> services start postgresql@15</span>
<span id="cb3-3"></span>
<span id="cb3-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Connect to your database</span></span>
<span id="cb3-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">psql</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-d</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>db-name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> -U <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>username<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb3-6"></span>
<span id="cb3-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Create the vector extension</span></span>
<span id="cb3-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">CREATE</span> EXTENSION vector<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">;</span></span>
<span id="cb3-9"></span>
<span id="cb3-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Verify installation</span></span>
<span id="cb3-11"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">\dx</span> vector</span></code></pre></div></div>
<p>If the installation succeed, we should see the following output after executing <code>\dx vector</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb4-1">                           <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">List</span> of installed extensions</span>
<span id="cb4-2">  <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Name</span>  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Version</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Schema</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span>                     <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Description</span>                      </span>
<span id="cb4-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">--------+---------+--------+------------------------------------------------------</span></span>
<span id="cb4-4"> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">vector</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">0.8.0</span>   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">public</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">vector</span> data type and ivfflat and hnsw access methods</span></code></pre></div></div>
<p>Check Postgres log:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb5-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tail</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-f</span> /opt/homebrew/var/log/postgresql@15.log</span></code></pre></div></div>


</section>

 ]]></description>
  <category>generative-ai</category>
  <category>implementation</category>
  <guid>https://ghif.github.io/posts/2025-02-25-activate-pgvector-postgresql-rag/</guid>
  <pubDate>Tue, 25 Feb 2025 00:00:00 GMT</pubDate>
</item>
</channel>
</rss>
