cd /news/artificial-intelligence/heygen-x-google-cloud-bringing-avata… · home topics artificial-intelligence article
[ARTICLE · art-95512] src=developers.googleblog.com ↗ pub= topic=artificial-intelligence verified=true sentiment=↑ positive

HeyGen x Google Cloud: Bringing Avatar IV to TPUs

HeyGen, an AI video generation platform, announced that its Avatar IV diffusion model, which runs on more than 18B parameters, is now 1.86× faster on Google Cloud's eight-chip Trillium (v6e) TPU host compared to its first working version, achieving performance comparable to its 8×H100 production setup while being up to 25% more cost efficient per minute of generated video. The optimization, done in collaboration with Google Cloud's specialized AI infrastructure performance team, involved porting the model via torchax and custom Pallas kernels, and the pipeline now streams 720p or 1080p video at 25 frames per second.

read10 min views1 publishedAug 13, 2026
HeyGen x Google Cloud: Bringing Avatar IV to TPUs
Image: Developers (auto-discovered)

HeyGen is an AI video generation platform featuring avatar models. Avatar IV, the diffusion stack behind our talking-head videos, runs on more than 18B parameters and is available via Web and API. Working with Google Cloud's specialized AI infrastructure performance optimization team*, we brought Avatar IV to an eight-chip Trillium (v6e) host and made it 1.86× faster than our first working version.*

A stream doesn't wait. Avatar IV renders talking-head video chunk by chunk, and if a chunk is late, the video stalls. Every optimization here was made against that deadline. Working with the Google Cloud team, we moved the pipeline onto an eight-chip Trillium (v6e) host and then made it nearly twice as fast as our first working version, with every change passing the same output-quality gates. Three walls set the shape of the work: exposed all-to-all collectives in the mesh, partial blocks in the sparse attention grid, and a serial dependency in the softmax inner loop. This post covers all three, plus the compiler contracts and quality gates behind them.

Avatar IV turns a single photo and an audio track into a talking, moving person. Behind the product, three models take turns on every chunk of video: a diffusion transformer that renders motion conditioned on the audio, a second transformer that super-resolves it, and a VAE decoder that turns latents into pixels. The output is 720p or 1080p video at 25 frames per second, streamed as chunks complete, so playback starts while later chunks are still rendering.

Avatar IV was written for GPUs, like the ecosystem around it. The port ran through torchax, a PyTorch frontend on JAX: the production model code runs unmodified, dispatched onto JAX arrays and compiled by the XLA compiler. The same model code now targets both stacks, and the TPU-specific engineering concentrates where the hardware differs. Each of the pipeline's attention variants dispatches to a Pallas kernel built for its shape. We also checked what a full native JAX rewrite would buy over this approach. The answer was roughly nothing: XLA compiles the whole pipeline end to end either way, so the frontend's cost is paid once, at trace time.

The parallelism was forced by arithmetic. The two transformers total more than 36 GB of bf16 weights against 32 GB of HBM per Trillium chip, so the weights are FSDP-sharded across the eight chips. Trillium's SparseCore, a co-processor that runs alongside the main core, carries each layer's weight gathers asynchronously. In production traces, those gathers hide behind compute: running them on the co-processor physically frees the matrix unit from weight movement, so a sharding forced by memory arithmetic costs nothing on the critical path. Ulysses sequence parallelism combines with the weight sharding over the same mesh, splitting the video sequence itself.

The sharding strategy was finalized during the first working version. Everything that followed was kernel and compiler work, and we tracked time per chunk as each change landed. The chart below shows that record as six milestones:

Moving from left to right across the graph, time per chunk falls to just over half its starting value—a 1.86× speedup—with the same model and quality gates throughout. The first milestone is the largest drop, representing the known playbook executed in full: custom attention kernels in place of stock ones, the sequence-parallel layout locked in, the XLA flag set tuned against this workload rather than left at defaults, and kernel tile sizes matched to its shapes. The three walls that follow, and the compiler contracts after them, are what that playbook could not reach.

The result is a pipeline that streams at performance comparable to what we see from our 8×H100 production setup while being up to 25% more cost efficient per minute of generated video.

Ulysses sequence parallelism puts a pair of all-to-alls inside every self-attention: one to trade sequence shards for head shards, and one to trade back. On our traces those collectives sat fully exposed, already moving bytes at 85–90% of the mesh's bisection bandwidth. The wire had no headroom left, which pointed at the program itself: a monolithic chain of all-to-all, then attention, then all-to-all gives XLA nothing to schedule the transfer behind, so it correctly keeps it synchronous.

The fix, worked out with the Google Cloud team, was the standard remedy: pipeline the collective. The attention heads split into a few independent groups, each running its own all-to-all, attention, all-to-all pattern, and each group's transfer now has sibling attention to hide behind, so XLA switches to asynchronous start/done pairs. The work was carrying that through the production pipeline and the quality gates. The hard case was the super-resolution stage's sparse attention: its mask is defined over a specific token ordering, and the head grouping has to leave that ordering untouched, so that every group still sees exactly the mask it would have seen unsplit. In the traces, the collective's footprint on the compute stream collapsed roughly 5×, with attention time itself unchanged. The wire time didn't shrink. It moved off the critical path, which is all the deadline cares about. The group count has a sweet spot: over-split, and the per-launch overhead eats what the overlap buys. More than once, a collective change that measured faster in isolation failed to hold at full pipeline depth, where the transfers interleave with each other and with everything else on the wire. Nothing here counted until it won end to end.

The largest single kernel in the pipeline is the super-resolution stage's sparse attention. It runs over tens of thousands of tokens with a windowed sparsity pattern: each frame attends to a window of nearby frames plus one global reference frame. That mask is frame-aligned by nature, meaning its live regions snap exactly to frame boundaries. A general-purpose block-sparse kernel has no reason to know about frames. The Pallas splash-attention family we started from tiles the sequence in multiples of 128, matching the hardware's vector lanes, and a frame's token span isn't a multiple of 128. So live blocks straddle frame boundaries, and roughly one in five comes out partial. The misaligned tiling forces the expensive machinery around it: mask predicates in the inner loop wherever a block straddles a boundary, and a padded sequence so the block grid tiles the full length. On top of that, the kernel handled the two parts of the mask, the frame window and the reference frame, as two separate attention passes, writing full-precision intermediates that a merge pass re-read to combine them.

The fix was not to make the masked path faster. It was to delete the mask. We relaxed the kernel's block-size constraint from multiples of 128 down to multiples of 16, the finest bf16 tiling the hardware supports along the sequence dimension: fine enough that block sizes can divide the per-frame token span exactly, and still large enough to keep the matrix unit fed. Now every block is either entirely inside the mask or entirely outside it. Live blocks are full by construction, and the reference frame's blocks are simply more full blocks in the same online softmax, so a single pass covers the whole mask. With that, the mask predicates, the second pass, and the padding all disappear.

A second round rebuilt the kernel body around the same grid: the faster dense-attention inner loop running over the frame-aligned sparse layout, with softmax reductions restructured to cut register traffic in the hot loop. The alignment round lifted the kernel from about half of the ceiling this attention shape can reach on the hardware to nearly three-quarters of it, and the rebuilt body closed to about 86%. Together the two rounds took more than ten percent off the super-resolution stage.

Alignment removed the work between blocks. Inside them, one serial dependency remained: the online softmax.

Flash-style attention carries a running maximum per query row, rescaling its accumulator whenever a new block of keys raises it. That bookkeeping is a serial dependency in the hottest inner loop of the pipeline's attention kernels.

Together with the Google Cloud team, we replaced the running max with a precomputed upper bound. By the Cauchy–Schwarz inequality, a query's largest possible logit is bounded by its norm times the largest key norm. A tiny array of precomputed norms, fed to the kernel through scalar prefetch, lets each row derive its bound as it starts, and the online max is no longer needed. Mean-centering the keys first tightens the bound, since keys share a large common component, and it's softmax-invariant: the outputs are unchanged in exact arithmetic, and the quality gates described below catch what rounding does in practice. With the max fixed up front, the rescaling and its serial chain drop out of the inner loop entirely.

Not every head is a good fit: a bound far above the true maximum pushes the exponentials toward underflow. Eligibility is checked per attention head, and heads whose bound is too loose fall back to the standard online path inside the same kernel. On our production data, 98–99% of heads qualify. Before anything shipped, a joint audit of the math extended the bound's guarantees to our windowed sparse masks, where each query row sees a different slice of the keys. Once the serial dependency is gone, the kernel's optimal block geometry moves, so we re-tuned block shapes together with the new softmax rather than inheriting them from the old one.

On the chart, this is one of the steepest drops after the opening playbook.

The walls shared a quieter lever: explicit contracts with the compiler.

The clearest example is layout. Attention's inputs are produced by a chain of small operations: normalization, rotary embeddings, projections, head packing. The compiler assigns a specific physical layout to the operand of the all-to-all that follows, and any mismatch gets patched with copies. We fused that chain into a single Pallas kernel that writes its output in exactly the layout the collective wants. The kernel's output buffer is the collective's input buffer. In one case, a five-stage repack chain between the projections and the collective disappeared outright.

Flags are contracts too. The heavy self-attention kernels only hit their tuned speed under an alternative instruction scheduler that better overlaps the softmax's vector work with the matrix unit. So each kernel requests it by name (XLA_TPU_FORCE_LP_LLO_SCHEDULER

) rather than trusting the defaults.

Two more contracts paid off the same way. XLA prices a custom kernel at zero FLOPs and zero bytes, since it can't see inside, so its latency-hiding scheduler misprices everything around it. Attaching honest cost estimates to our in-house kernels bought back time with no kernel or graph change at all: the scheduler simply repriced what it could hide.

And because the unit of shipment on TPU is a compiled program, our executables are release artifacts: cached, versioned, and promoted between environments like model weights, gated on zero recompiles and bit-exact output.

Every optimization here passed a two-tier output gate before it shipped.

Tier one is byte-identical. The delivered video must hash equal to baseline, frame for frame. Re-tilings and scheduling changes that leave every reduction order intact are held to this bar, and they meet it. One change you might expect to show up in pixels met the same bar: we moved attention's final matrix product to bf16 on the matrix unit, and the delivered video still hashed identical at both resolutions. Whatever numeric difference it introduced vanished when the frames were quantized to output pixels, before the video was ever encoded.

Tier two covers changes that alter the compiled program's reduction order. Those are held to the narrow similarity band that bf16 reassociation itself produces, a band we measured independently. Anything below that band goes to the model's owners for a blind, frame-by-frame review before it ships. And each lever was measured one at a time against a dual baseline: two runs of the same build that must hash identical before a delta is trusted. One candidate moved the diffusion transformers' residual stream down to bf16 and measured faster end to end, but its output fell below the band. The speedup was coming out of quality, so we discarded it and shipped the slower version.

Our thanks to the Google Cloud team for their help throughout this work. The team is Google Cloud's engineering group for optimizing large-scale AI workloads on its accelerators, working with customers from early proof of concept through full production.

This work was a collaboration between Google Cloud and HeyGen. We'd like to thank everyone involved for their support throughout this project.

── more in #artificial-intelligence 4 stories · sorted by recency
── more on @heygen 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/heygen-x-google-clou…] indexed:0 read:10min 2026-08-13 ·