<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Thinking Machines Lab</title>
    <link>https://thinkingmachines.ai/</link>
    <description>Recent content on Thinking Machines Lab</description>
    <language>en</language>
    <lastBuildDate>Fri, 10 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://thinkingmachines.ai/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>The Future Worth Building Is Human</title>
      <link>https://thinkingmachines.ai/blog/the-future-worth-building-is-human/</link>
      <pubDate>Fri, 10 Jul 2026 00:00:00 +0000</pubDate>
      
      <guid>https://thinkingmachines.ai/blog/the-future-worth-building-is-human/</guid>
      <description>&lt;p&gt;The mission of Thinking Machines is to build AI that extends human will and judgment.&lt;/p&gt;
&lt;p&gt;Artificial intelligence can do more every day, but deciding what it should do is up to us: individuals, organizations, humanity as a whole. These decisions require knowledge and judgment that people acquire through continuous contact with the work, increasingly done alongside AI. Shaping the goals of advanced intelligence is also a continuous process of feedback, learning, and realignment.&lt;/p&gt;
&lt;p&gt;Most AI in use today is trained in a handful of places and then frozen. It isn’t shaped by the people it serves, and doesn’t learn much from the work they do together. Extending human will and judgment calls for AIs as diverse and distributed as people themselves are. This is the path we have chosen.&lt;/p&gt;
&lt;p&gt;To progress on that path, we are pursuing these technical directions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;We train strong models&lt;/strong&gt;, advancing capabilities such as multimodal interaction and customizability. Sharp instruments extend human will, and human judgment needs to shape models that compete on the frontier.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://thinkingmachines.ai/tinker/&#34;&gt;We build tools&lt;/a&gt;&lt;/strong&gt; that enable people to make AI their own, customizing models to serve their unique needs. This includes the ability to train model weights.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://thinkingmachines.ai/blog/interaction-models/&#34;&gt;We develop interfaces&lt;/a&gt;&lt;/strong&gt; that broaden the communication channel between human and machine, allowing personal judgment to continuously influence the work of AI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://thinkingmachines.ai/blog/&#34;&gt;We publish research&lt;/a&gt;&lt;/strong&gt; for the scientific community, because the power to shape AI requires deep understanding of how it’s made.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We believe the future worth building is human — shaped by human knowledge, guided by human will, and decided by human judgment. What follows is the case for that future, and the work we’re doing to bring it about.&lt;/p&gt;
&lt;h2 id=&#34;bringing-intelligence-to-knowledge&#34;&gt;Bringing intelligence to knowledge&lt;/h2&gt;
&lt;p&gt;AI exists to serve the work that we do. This work runs on knowledge of how things are done and what is worth doing, knowledge that is generated continuously by people engaged in the work.&lt;/p&gt;
&lt;p&gt;Think of a chef crafting a new recipe or a shopkeeper rearranging the items and prices on display. They are pursuing a complex set of goals and applying know-how that isn’t immediately legible to outsiders. This knowledge is constantly updated through feedback; it’s not a static repository that can be written into a database. It’s local — a different restaurant or shop pursues different outcomes by different means. The collective knowledge of shops and kitchens is scattered across every shopkeeper and chef.&lt;label for=&#34;sn-mission-1&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-mission-1&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Michael Polanyi, &lt;a href=&#34;https://press.uchicago.edu/ucp/books/book/chicago/T/bo6035368.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;The Tacit Dimension&lt;/a&gt; (1966)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The dispersion of knowledge is a collective strength; it’s the source of variety, adaptability, and resilience of the overall system. It’s the reason that free markets outperform planned economies. Central planning fails not because of insufficient intelligence, but because of the nature of productive knowledge: tacit, local, fleeting, and held privately by those who acquired it through their work.&lt;label for=&#34;sn-mission-2&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-mission-2&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Friedrich Hayek, &lt;a href=&#34;https://www.econlib.org/library/Essays/hykKnw.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;The Use of Knowledge in Society&lt;/a&gt; (1945)&lt;/span&gt; Attempting to aggregate knowledge for the use of a centralized intelligence faces the same challenge.&lt;/p&gt;
&lt;p&gt;There are domains where intelligence alone is sufficient, and where autonomous AI doesn’t require human participation to race ahead. Two examples are chess, where the strongest engines are trained purely on self-play, and math, where frontier models are solving long-standing problems on their own. These examples share two traits. First, the goal given to AI is static and expressible: to win a chess match, to prove a theorem. Second, these domains don’t contain hidden knowledge. The rules of chess and math are universal; the board is visible to all. Outside the board, intelligence alone is not enough.&lt;/p&gt;
&lt;p&gt;For artificial intelligence to benefit from distributed knowledge, it must itself be distributed. Every organization is powered by the expert knowledge of its people, gained and expressed through their work. We believe in AI that helps the organization cultivate that unique knowledge, not AI that extracts a snapshot of it and replaces it with a standard offering. This cultivation is an ongoing process that requires AI to work with people, not in their stead.&lt;/p&gt;
&lt;p&gt;In 2014, Toyota, long a master of the automated plant, brought its expert craftsmen back onto the line with the explicit goal of growing craftsmanship and knowledge. The man who led this, Mitsuru Kawai, put the reason this way: “To be the master of the machine, you have to have the knowledge and the skills to teach the machine.”&lt;label for=&#34;sn-mission-3&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-mission-3&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Craig Trudell, Yuki Hagiwara and Ma Jie, &lt;a href=&#34;https://www.bloomberg.com/news/articles/2014-04-06/humans-replacing-robots-herald-toyota-s-vision-of-future&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Humans Replacing Robots Herald Toyota’s Vision of Future&lt;/a&gt; (2014)&lt;/span&gt; The production of knowledge and application of intelligence lift each other; they are not substitutes.&lt;/p&gt;
&lt;p&gt;The work people do may change, and turn toward more of what only people bring, but the best organizations will make the fullest use of both. AI should enable each organization to be excellent in its own way, not to erase the differences between them.&lt;/p&gt;
&lt;p&gt;We aim to bring intelligence to where knowledge is made and used. &lt;a href=&#34;https://thinkingmachines.ai/tinker/&#34;&gt;We build tools&lt;/a&gt; that enable everyone to fine-tune models with their unique knowledge, and to keep adapting the models as their knowledge evolves. &lt;a href=&#34;https://thinkingmachines.ai/blog/&#34;&gt;We publish research&lt;/a&gt; and &lt;a href=&#34;https://tinker-docs.thinkingmachines.ai/cookbook/&#34;&gt;recipes&lt;/a&gt; that put this capability within reach of more people. We envision frontier AI as a collective, as diverse as the people it serves because it was shaped by them in each unique location.&lt;/p&gt;
&lt;h2 id=&#34;human-participation-is-a-technical-challenge&#34;&gt;Human participation is a technical challenge&lt;/h2&gt;
&lt;p&gt;Keeping people engaged in setting goals and sharing knowledge with AI doesn’t mean resisting automation for its own sake. What a machine does reliably on its own, it should do. But it should also know when to act alone and when to invite oversight and feedback, as people themselves do when working in teams. The best collaborators anticipate: they learn what someone is reaching for and bring it before being asked, earning over time the right to act on their behalf. These are technical challenges, requiring a new approach to how AI is designed and evaluated.&lt;/p&gt;
&lt;p&gt;A major bottleneck for bringing human knowledge and judgment to work with LLMs is the communication channel between human and AI — a small text box and a long wait. This is too narrow to carry the richness of human wisdom and intent, and too slow for ongoing feedback. People collaborate best when they collaborate live. We interrupt and correct, take second looks and make gestures, change our minds aloud. This is why we’re making a long-term bet on &lt;a href=&#34;https://thinkingmachines.ai/blog/interaction-models/&#34;&gt;interaction models&lt;/a&gt;: models that handle live, multimodal interaction natively, in the model itself rather than in scaffolding bolted around it. Built this way, interactivity scales with intelligence; the same training that makes the model smarter makes it a better collaborator. The right interface doesn’t just allow human participation, it invites and rewards it.&lt;/p&gt;
&lt;p&gt;Another challenge is setting the right target for evaluation and optimization. The common measure of AI intelligence today is the time horizon of software tasks models can execute autonomously, tracked on charts like METR’s.&lt;label for=&#34;sn-mission-4&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-mission-4&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Thomas Kwa and Ben West et al., &lt;a href=&#34;https://metr.org/time-horizons/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Task-Completion Time Horizons of Frontier AI Models&lt;/a&gt; (2025)&lt;/span&gt; We expect progress on this benchmark to continue, but it ultimately measures only what AI is capable of on its own, not what people and machines can accomplish together.&lt;/p&gt;
&lt;p&gt;Measuring the latter is more complex, and can’t be done by a lab on its own. Every organization evaluates for itself whether AI helps it sharpen its judgment, develop new knowledge, and achieve its objectives.&lt;/p&gt;
&lt;p&gt;Building AI that makes its users stronger in the long run also aligns incentives well. An AI lab offering a single model for every customer benefits by absorbing what makes each user distinct and devaluing the cultivation of specialized knowledge. By optimizing AI to be customized and collaborated with, we benefit when our customers leverage their unique advantages. These advantages are maximized not by renting an AI and outsourcing to it, but by organizations owning it and tailoring it to their goals.&lt;/p&gt;
&lt;h2 id=&#34;decentralized-alignment&#34;&gt;Decentralized alignment&lt;/h2&gt;
&lt;p&gt;Human values, just like human knowledge, reside in the heads of individual people and resist consolidation. But today, the values and voice of AI are decided in a handful of places. A single locus of value alignment, however well run, becomes a locus of power to be captured.&lt;/p&gt;
&lt;p&gt;This creates danger, especially if most valuable work is done by AI on its own with little need for human input. The social contract between corporations, governments, and citizens relies on individuals’ productive capabilities on which the government’s sovereignty and corporations’ profits ultimately depend. Power that needs nothing from people loses the incentive to care for their needs and values, caring instead for its own preservation.&lt;label for=&#34;sn-mission-5&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-mission-5&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Luke Drago and Rudolf Laine, &lt;a href=&#34;https://intelligence-curse.ai/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;The Intelligence Curse&lt;/a&gt; (2025)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Even with the best intentions, a model shaped in one place inevitably encodes the values of its owner, not the individual users it serves.&lt;label for=&#34;sn-mission-6&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-mission-6&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;“A more moral AI is not enough if that morality is determined by a few.” Leo XIV, &lt;a href=&#34;https://www.vatican.va/content/leo-xiv/en/encyclicals/documents/20260515-magnifica-humanitas.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Magnifica Humanitas&lt;/a&gt; (2026)&lt;/span&gt; Today each lab trains its next flagship model by using its previous flagship model to generate training data and a reward signal. Whatever character emerges from that loop, everyone gets the same one, and each generation inherits the traits of the last, raised on its parent’s outputs and judged by its parent’s tastes. A single alignment spec suppresses creativity and diversity and stultifies progress. Free speech and free markets let new ideas, goods, and services emerge and compete, rather than averaging out the preferences that exist at a point in time.&lt;/p&gt;
&lt;p&gt;For organizations and individuals to align AI to their own values, these values must be encoded in the model weights. If the user’s values and desires only impact the model through a prompt, the user finds that surface properties change while the deeper habits remain. Allowing core model behavior to change significantly with prompts sacrifices safety, making a malleable centralized model vulnerable to repeated attacks.&lt;label for=&#34;sn-mission-7&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-mission-7&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Gwern Branwen, &lt;a href=&#34;https://gwern.net/guardian-angel&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Guardian Angels: LLM Personalization for Productivity and Security&lt;/a&gt; (2026)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The power to shape a model profoundly is also the power to shape it for ill. John von Neumann remarked on this problem in 1955,&lt;label for=&#34;sn-mission-8&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-mission-8&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;John von Neumann, &lt;a href=&#34;https://sseh.uchicago.edu/doc/von_Neumann_1955.pdf&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Can We Survive Technology?&lt;/a&gt; (1955)&lt;/span&gt; writing that the useful and the harmful aspects of technology “lie everywhere so close together that it is never possible to separate the lions from the lambs.” Keeping the lambs safe is an ongoing process, the result of judgment exercised and choices made continuously. We aim to give the people making these choices stronger tools, pursuing research that enables safer models without taking away ownership.&lt;/p&gt;
&lt;p&gt;Humanity has flourished through individual weirdness and creative tension. We envision alignment as a feature not of a single model but of an ecosystem of AIs raised in different places, disagreeing, competing, and learning from each other. We believe in keeping the weirdness alive.&lt;/p&gt;
&lt;h2 id=&#34;the-future-worth-building&#34;&gt;The future worth building&lt;/h2&gt;
&lt;p&gt;The technology industry has made incredible progress in teaching machines to think; what they should think about must remain with us. What is worth wanting, what is worth making, what’s the right use of the time we have.&lt;label for=&#34;sn-mission-9&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-mission-9&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;“The only way out of the dilemma of meaninglessness in all strictly utilitarian philosophy is to turn away from the objective world of use things and fall back upon the subjectivity of use itself. Only in a strictly anthropocentric world, where the user, that is, man himself, becomes the ultimate end which puts a stop to the unending chain of ends and means, can utility as such acquire the dignity of meaningfulness…The anthropocentric utilitarianism of &lt;em&gt;homo faber&lt;/em&gt; has found its greatest expression in the Kantian formula that no man must ever become a means to an end, that every human being is an end in himself.” Hannah Arendt, &lt;a href=&#34;https://press.uchicago.edu/ucp/books/book/chicago/H/bo29137972.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;The Human Condition&lt;/a&gt; (1958)&lt;/span&gt; We are not looking to hand down a single answer to this, but to give every person the ability to make their own answer part of the development of frontier AI.&lt;/p&gt;
&lt;p&gt;The current path of AI development, pushing towards centralization and autonomy, frames human involvement as a trade-off: participation vs. capability, ownership vs. safe alignment. We see these as technical challenges to solve: AI that is more capable because it encourages human participation, organizations that benefit in the long run from tailoring AI to their advantages, alignment that arises from diverse AIs shaped by the people who own them. Solving these challenges is what our mission requires.&lt;/p&gt;
&lt;p&gt;The future is not a choice between human dominance and rapid obsolescence in the face of AI. Different roads lead to many different futures, and we get to choose which one to take. We are building technology that lets the born and the made walk the road together.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Interaction Models: A Scalable Approach to Human-AI Collaboration</title>
      <link>https://thinkingmachines.ai/blog/interaction-models/</link>
      <pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate>
      
      <guid>https://thinkingmachines.ai/blog/interaction-models/</guid>
      <description>&lt;p&gt;Today, we’re announcing a research preview of interaction models: models that handle interaction natively rather than through external scaffolding. We think interactivity should scale alongside intelligence; the way we work with AI should not be treated as an afterthought. Interaction models let people collaborate with AI the way we naturally collaborate with each other—they continuously take in audio, video, and text, and think, respond, and act in real time.&lt;/p&gt;
&lt;p&gt;We train an interaction model from scratch. To ensure real-time responsiveness, we adopt a multi-stream, micro-turn design. Our research preview demonstrates qualitatively new interaction capabilities, as well as state-of-the-art combined performance in intelligence and responsiveness.&lt;/p&gt;
&lt;h2 id=&#34;the-collaboration-bottleneck&#34;&gt;The collaboration bottleneck&lt;/h2&gt;
&lt;p&gt;AI labs often treat the ability for AI to work autonomously as the model’s most important capability.&lt;label for=&#34;sn-im-2&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-2&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Kwa, T., West, B., Becker, J., et al. Measuring AI Ability to Complete Long Tasks. &lt;a href=&#34;https://metr.org/blog/2025-03-19-measuring-ai-ability-to-complete-long-tasks/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;METR&lt;/a&gt;, 2025.&lt;/span&gt; As a result, today&amp;rsquo;s models and interfaces aren&amp;rsquo;t optimized for humans to remain in the loop.&lt;label for=&#34;sn-im-3&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-3&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;A &lt;a href=&#34;https://www-cdn.anthropic.com/8b8380204f74670be75e81c820ca8dda846ab289.pdf&#34; target=&#34;_blank&#34;&gt;recent frontier model card&lt;/a&gt; states: “Importantly, we find that when used in an interactive, synchronous, “hands-on-keyboard” pattern, the benefits of the model were less clear. When used in this fashion, some users perceived [our model] as too slow and did not realize as much value. Autonomous, long-running agent harnesses better elicited the model’s coding capabilities.”&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Autonomous interfaces are valuable, but in most real work, users can&amp;rsquo;t fully specify their requirements upfront and walk away—good results benefit from a collaborative process where the human stays in the loop, clarifying and giving feedback along the way. However, humans increasingly get pushed out not because the work doesn&amp;rsquo;t need them, but because the interface has no room for them. Instead, people are most effective when they can collaborate with AI the same way we do with other people: messaging, talking, listening, seeing, showing, and interjecting as needed—and for the model to do the same.&lt;label for=&#34;sn-im-4&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-4&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Communication gets better with: (a) Copresence: people can interact with what others are interacting with; (b) Contemporality: people receive information as it’s produced by others with instant feedback; (c) Simultaneity: people receive and produce information at the same time. Clark H. and Brennan S., &amp;ldquo;Grounding in Communication,&amp;rdquo; in Perspectives on Socially Shared Cognition, 1991.&lt;/span&gt;&lt;span class=&#34;sidenote-citation-separator&#34;&gt;, &lt;/span&gt;&lt;label for=&#34;sn-im-5&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-5&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;The evanescence of orality for its participatory (cf. objectively distanced) nature. Today’s computers and mediums of knowledge work have similar interactive properties. Ong, W. J.. In &lt;em&gt;Orality and Literacy: The technologizing of the word&lt;/em&gt;, 1982.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In order to resolve this, we need to move beyond the current turn-based interface for the models. Today&amp;rsquo;s models experience reality in a single thread.&lt;label for=&#34;sn-im-6&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-6&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;We are referring to commercial general-purpose frontier models—there are smaller-scale or specialized models like Moshi, PersonaPlex, Nemotron VoiceChat, or GPT-Realtime-Translate.&lt;/span&gt; Until the user finishes typing or speaking, the model waits with no perception of what the user is doing or how the user is doing it. Until the model finishes generating, its perception freezes, receiving no new information until it finishes or is interrupted. This creates a narrow channel for human-AI collaboration that limits how much of a person’s knowledge,&lt;label for=&#34;sn-im-7&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-7&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;“Metis, with the premium it places on practical knowledge, experience, and stochastic reasoning…is the mode of reasoning most appropriate to complex material and social tasks where the uncertainties are so daunting that we must trust our (experienced) intuition and feel our way.” Scott, J. C: Métis. In &lt;em&gt;Seeing like a State: How certain schemes to improve the human condition have failed&lt;/em&gt;, 1998.&lt;/span&gt;&lt;span class=&#34;sidenote-citation-separator&#34;&gt;, &lt;/span&gt;&lt;label for=&#34;sn-im-8&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-8&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;“A little reflection will show that there is…a body of very important but unorganized knowledge…: the knowledge of the particular circumstances of time and place.” Hayek, F. A. “The use of knowledge in society.” &lt;em&gt;The American Economic Review&lt;/em&gt;, 1945.&lt;/span&gt; intent, and judgement can reach the model, and how much of the model’s work can be understood. Picture trying to resolve a crucial disagreement over email rather than in person.&lt;/p&gt;
&lt;p&gt;At Thinking Machines, we believe we can solve this bandwidth bottleneck by making &lt;strong&gt;AI interactive in real time across any modality&lt;/strong&gt;. This enables AI interfaces to meet humans where they are, rather than forcing humans to contort themselves to AI interfaces.&lt;/p&gt;
&lt;p&gt;Most existing AI models bolt on interactivity with a harness: stitching components together to emulate interruptions, multimodality, or concurrency.&lt;label for=&#34;sn-im-9&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-9&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Most real-time commercials speech systems use voice-activity-detection components to detect turn boundaries.&lt;/span&gt; However, “the bitter lesson”&lt;label for=&#34;sn-im-10&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-10&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Sutton R. &lt;a href=&#34;http://www.incompleteideas.net/IncIdeas/BitterLesson.html&#34; target=&#34;_blank&#34;&gt;The Bitter Lesson&lt;/a&gt;, 2019.&lt;/span&gt; suggests that these hand-crafted systems will be outpaced by the advance of general capabilities. &lt;strong&gt;For interactivity to scale with intelligence, it must be part of the model itself.&lt;/strong&gt; With this approach, scaling a model makes it smarter &lt;em&gt;and&lt;/em&gt; a better collaborator.&lt;/p&gt;
&lt;h2 id=&#34;capabilities&#34;&gt;Capabilities&lt;/h2&gt;
&lt;p&gt;Having interactivity be part of the model unlocks a variety of capabilities that would otherwise need to be implemented in the harness.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Seamless dialog management.&lt;/strong&gt; The model tracks implicitly whether the speaker is thinking, yielding, self-correcting, or inviting a response. There is no separate dialog management component.&lt;/p&gt;
&lt;div class=&#34;post-inline-video&#34; data-youtube-video&gt;
    &lt;button class=&#34;youtube-video-poster&#34; type=&#34;button&#34; data-youtube-video-poster data-youtube-id=&#34;Ys6i_MGnjUA&#34; data-video-title=&#34;Seamless dialog management demonstration&#34; aria-label=&#34;Play Seamless dialog management demonstration&#34;&gt;
      &lt;img src=&#34;thumbnails/ANIMAL_STORY_THUMB.jpg&#34; alt=&#34;&#34; class=&#34;youtube-video-poster-image&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; width=&#34;1080&#34; height=&#34;608&#34;&gt;
      &lt;span class=&#34;youtube-video-play&#34; aria-hidden=&#34;true&#34;&gt;&lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Verbal and visual interjections.&lt;/strong&gt; The model jumps in as needed depending on the context, not only when the user finishes speaking.&lt;/p&gt;
&lt;div class=&#34;post-inline-video&#34; data-youtube-video&gt;
    &lt;button class=&#34;youtube-video-poster&#34; type=&#34;button&#34; data-youtube-video-poster data-youtube-id=&#34;n2GXGjy41HQ&#34; data-video-title=&#34;Visual interjection demonstration&#34; aria-label=&#34;Play Visual interjection demonstration&#34;&gt;
      &lt;img src=&#34;thumbnails/SLOUCHING_THUMB.jpg&#34; alt=&#34;&#34; class=&#34;youtube-video-poster-image&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; width=&#34;1080&#34; height=&#34;608&#34;&gt;
      &lt;span class=&#34;youtube-video-play&#34; aria-hidden=&#34;true&#34;&gt;&lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;div class=&#34;post-inline-video&#34; data-youtube-video&gt;
    &lt;button class=&#34;youtube-video-poster&#34; type=&#34;button&#34; data-youtube-video-poster data-youtube-id=&#34;_SsogUUZP2o&#34; data-video-title=&#34;Verbal interjection demonstration&#34; aria-label=&#34;Play Verbal interjection demonstration&#34;&gt;
      &lt;img src=&#34;thumbnails/DANGER_THUMB.jpg&#34; alt=&#34;&#34; class=&#34;youtube-video-poster-image&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; width=&#34;1080&#34; height=&#34;608&#34;&gt;
      &lt;span class=&#34;youtube-video-play&#34; aria-hidden=&#34;true&#34;&gt;&lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Simultaneous speech.&lt;/strong&gt; The user and the model can speak concurrently (e.g. live translation)&lt;/p&gt;
&lt;div class=&#34;post-inline-video&#34; data-youtube-video&gt;
    &lt;button class=&#34;youtube-video-poster&#34; type=&#34;button&#34; data-youtube-video-poster data-youtube-id=&#34;2ky5MXBvZP8&#34; data-video-title=&#34;Simultaneous speech demonstration&#34; aria-label=&#34;Play Simultaneous speech demonstration&#34;&gt;
      &lt;img src=&#34;thumbnails/ANGER_THUMB.jpg&#34; alt=&#34;&#34; class=&#34;youtube-video-poster-image&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; width=&#34;1080&#34; height=&#34;608&#34;&gt;
      &lt;span class=&#34;youtube-video-play&#34; aria-hidden=&#34;true&#34;&gt;&lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Time-awareness.&lt;/strong&gt; The model has a direct sense of elapsed time.&lt;/p&gt;
&lt;div class=&#34;post-inline-video&#34; data-youtube-video&gt;
    &lt;button class=&#34;youtube-video-poster&#34; type=&#34;button&#34; data-youtube-video-poster data-youtube-id=&#34;sqJNfze19jA&#34; data-video-title=&#34;Time awareness demonstration&#34; aria-label=&#34;Play Time awareness demonstration&#34;&gt;
      &lt;img src=&#34;thumbnails/QA_THUMB.jpg&#34; alt=&#34;&#34; class=&#34;youtube-video-poster-image&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; width=&#34;1080&#34; height=&#34;608&#34;&gt;
      &lt;span class=&#34;youtube-video-play&#34; aria-hidden=&#34;true&#34;&gt;&lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Simultaneous tools calls, search, and generative UI.&lt;/strong&gt; While speaking and listening to the user, the model can concurrently search, browse the web, or generate UI—weaving back results into the conversation as needed.&lt;/p&gt;
&lt;div class=&#34;post-inline-video&#34; data-youtube-video&gt;
    &lt;button class=&#34;youtube-video-poster&#34; type=&#34;button&#34; data-youtube-video-poster data-youtube-id=&#34;ly3GtaiRFyo&#34; data-video-title=&#34;Simultaneous tool calls and search demonstration&#34; aria-label=&#34;Play Simultaneous tool calls and search demonstration&#34;&gt;
      &lt;img src=&#34;thumbnails/UBER_THUMB.jpg&#34; alt=&#34;&#34; class=&#34;youtube-video-poster-image&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; width=&#34;1080&#34; height=&#34;608&#34;&gt;
      &lt;span class=&#34;youtube-video-play&#34; aria-hidden=&#34;true&#34;&gt;&lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;div class=&#34;post-inline-video&#34; data-youtube-video&gt;
    &lt;button class=&#34;youtube-video-poster&#34; type=&#34;button&#34; data-youtube-video-poster data-youtube-id=&#34;GL1waQJsV9c&#34; data-video-title=&#34;Generative UI demonstration&#34; aria-label=&#34;Play Generative UI demonstration&#34;&gt;
      &lt;img src=&#34;thumbnails/SEARCH_THUMB.jpg&#34; alt=&#34;&#34; class=&#34;youtube-video-poster-image&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; width=&#34;1080&#34; height=&#34;608&#34;&gt;
      &lt;span class=&#34;youtube-video-play&#34; aria-hidden=&#34;true&#34;&gt;&lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a longer real session, all of this happens continuously, creating an experience that feels more like collaborating and less like prompting.&lt;/p&gt;
&lt;div class=&#34;post-inline-video&#34; data-youtube-video&gt;
  &lt;button class=&#34;youtube-video-poster&#34; type=&#34;button&#34; data-youtube-video-poster data-youtube-id=&#34;qXdYDUqxSxA&#34; data-video-title=&#34;Longer real session demonstration&#34; aria-label=&#34;Play Longer real session demonstration&#34;&gt;
    &lt;img src=&#34;thumbnails/DIET_THUMB.jpg&#34; alt=&#34;&#34; class=&#34;youtube-video-poster-image&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; width=&#34;1080&#34; height=&#34;608&#34;&gt;
    &lt;span class=&#34;youtube-video-play&#34; aria-hidden=&#34;true&#34;&gt;&lt;/span&gt;
  &lt;/button&gt;
&lt;/div&gt;
&lt;div class=&#34;post-inline-video&#34; data-youtube-video&gt;
  &lt;button class=&#34;youtube-video-poster&#34; type=&#34;button&#34; data-youtube-video-poster data-youtube-id=&#34;iVDJ8O89ERg&#34; data-video-title=&#34;Continuous collaboration demonstration&#34; aria-label=&#34;Play Continuous collaboration demonstration&#34;&gt;
    &lt;img src=&#34;thumbnails/IN_EAR_THUMB.jpg&#34; alt=&#34;&#34; class=&#34;youtube-video-poster-image&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; width=&#34;1080&#34; height=&#34;608&#34;&gt;
    &lt;span class=&#34;youtube-video-play&#34; aria-hidden=&#34;true&#34;&gt;&lt;/span&gt;
  &lt;/button&gt;
&lt;/div&gt;
&lt;p class=&#34;post-inline-footnote&#34;&gt;None of the brands or products appearing in these videos are associated with Thinking Machines Labs. These videos are to demonstrate the model&#39;s capabilities and do not indicate sponsorship or partnership.&lt;/p&gt;
&lt;h2 id=&#34;our-approach&#34;&gt;Our approach&lt;/h2&gt;
&lt;figure class=&#34;grounded-reality-figure&#34;&gt;
  &lt;div class=&#34;grounded-reality-diagram&#34; role=&#34;group&#34;
    aria-label=&#34;Comparison between a turn-based model timeline and a time-aligned micro-turn timeline.&#34;&gt;
    &lt;section class=&#34;grounded-reality-panel grounded-reality-panel-turns&#34; aria-hidden=&#34;true&#34;&gt;
      &lt;div class=&#34;grounded-reality-panel-copy&#34;&gt;
        &lt;div class=&#34;grounded-reality-panel-title&#34;&gt;Turn based&lt;/div&gt;
        &lt;p&gt;Inputs and outputs are flattened into one ordered token sequence&lt;/p&gt;
      &lt;/div&gt;

      &lt;div class=&#34;grounded-reality-scroll&#34;&gt;
        &lt;div class=&#34;turn-world-grid&#34;&gt;
          &lt;div class=&#34;turn-world-step&#34;&gt;
            &lt;div class=&#34;turn-world-box turn-world-human&#34; aria-label=&#34;human input 1&#34;&gt;
              &lt;span class=&#34;turn-world-token&#34; data-mobile-label=&#34;I 1&#34;&gt;input 1&lt;/span&gt;
            &lt;/div&gt;
            &lt;span class=&#34;turn-world-role&#34;&gt;human&lt;/span&gt;
          &lt;/div&gt;
          &lt;div class=&#34;turn-world-step&#34;&gt;
            &lt;div class=&#34;turn-world-box turn-world-model&#34; aria-label=&#34;model output 1&#34;&gt;
              &lt;span class=&#34;turn-world-token&#34; data-mobile-label=&#34;O 1&#34;&gt;output 1&lt;/span&gt;
            &lt;/div&gt;
            &lt;span class=&#34;turn-world-role&#34;&gt;model&lt;/span&gt;
          &lt;/div&gt;
          &lt;div class=&#34;turn-world-step&#34;&gt;
            &lt;div class=&#34;turn-world-box turn-world-human&#34; aria-label=&#34;human input 2&#34;&gt;
              &lt;span class=&#34;turn-world-token&#34; data-mobile-label=&#34;I 2&#34;&gt;input 2&lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;
          &lt;div class=&#34;turn-world-step&#34;&gt;
            &lt;div class=&#34;turn-world-box turn-world-model&#34; aria-label=&#34;model output 2&#34;&gt;
              &lt;span class=&#34;turn-world-token&#34; data-mobile-label=&#34;O 2&#34;&gt;output 2&lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;
          &lt;div class=&#34;turn-world-step&#34;&gt;
            &lt;div class=&#34;turn-world-box turn-world-human&#34; aria-label=&#34;human input 3&#34;&gt;
              &lt;span class=&#34;turn-world-token&#34; data-mobile-label=&#34;I 3&#34;&gt;input 3&lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;
          &lt;div class=&#34;turn-world-step&#34;&gt;
            &lt;div class=&#34;turn-world-box turn-world-model&#34; aria-label=&#34;model output 3&#34;&gt;
              &lt;span class=&#34;turn-world-token&#34; data-mobile-label=&#34;O 3&#34;&gt;output 3&lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;
          &lt;svg class=&#34;turn-world-arrows&#34; viewBox=&#34;0 0 600 24&#34; preserveAspectRatio=&#34;none&#34; aria-hidden=&#34;true&#34;
            focusable=&#34;false&#34;&gt;
            &lt;g class=&#34;turn-world-arrow&#34; style=&#34;--turn-world-arrow-delay: 0s;&#34;&gt;
              &lt;path class=&#34;turn-world-arrow-stem&#34; d=&#34;M 84 8 C 94 17 118 17 128 8&#34;&gt;&lt;/path&gt;
              &lt;path class=&#34;turn-world-arrow-tip&#34; d=&#34;M 119 10 L 128 8 L 124 15&#34;&gt;&lt;/path&gt;
            &lt;/g&gt;
            &lt;g class=&#34;turn-world-arrow&#34; style=&#34;--turn-world-arrow-delay: 1.05s;&#34;&gt;
              &lt;path class=&#34;turn-world-arrow-stem&#34; d=&#34;M 184 8 C 194 17 218 17 228 8&#34;&gt;&lt;/path&gt;
              &lt;path class=&#34;turn-world-arrow-tip&#34; d=&#34;M 219 10 L 228 8 L 224 15&#34;&gt;&lt;/path&gt;
            &lt;/g&gt;
            &lt;g class=&#34;turn-world-arrow&#34; style=&#34;--turn-world-arrow-delay: 2.1s;&#34;&gt;
              &lt;path class=&#34;turn-world-arrow-stem&#34; d=&#34;M 284 8 C 294 17 318 17 328 8&#34;&gt;&lt;/path&gt;
              &lt;path class=&#34;turn-world-arrow-tip&#34; d=&#34;M 319 10 L 328 8 L 324 15&#34;&gt;&lt;/path&gt;
            &lt;/g&gt;
            &lt;g class=&#34;turn-world-arrow&#34; style=&#34;--turn-world-arrow-delay: 3.15s;&#34;&gt;
              &lt;path class=&#34;turn-world-arrow-stem&#34; d=&#34;M 384 8 C 394 17 418 17 428 8&#34;&gt;&lt;/path&gt;
              &lt;path class=&#34;turn-world-arrow-tip&#34; d=&#34;M 419 10 L 428 8 L 424 15&#34;&gt;&lt;/path&gt;
            &lt;/g&gt;
            &lt;g class=&#34;turn-world-arrow&#34; style=&#34;--turn-world-arrow-delay: 4.2s;&#34;&gt;
              &lt;path class=&#34;turn-world-arrow-stem&#34; d=&#34;M 484 8 C 494 17 518 17 528 8&#34;&gt;&lt;/path&gt;
              &lt;path class=&#34;turn-world-arrow-tip&#34; d=&#34;M 519 10 L 528 8 L 524 15&#34;&gt;&lt;/path&gt;
            &lt;/g&gt;
          &lt;/svg&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/section&gt;

    &lt;section class=&#34;grounded-reality-panel grounded-reality-panel-time&#34; data-time-world-mode=&#34;continuous&#34;
      data-time-world-state=&#34;playing&#34;&gt;
      &lt;div class=&#34;grounded-reality-panel-copy&#34;&gt;
        &lt;div class=&#34;grounded-reality-panel-copy-text&#34;&gt;
          &lt;div class=&#34;grounded-reality-panel-title&#34;&gt;Time-aligned micro-turn based&lt;/div&gt;
          &lt;p&gt;Interaction is grounded in time with continuous input and output streams split into micro-turns&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class=&#34;grounded-reality-scroll&#34;&gt;
        &lt;div class=&#34;time-world-grid&#34;&gt;
          &lt;div class=&#34;time-world-frame-column-layer&#34; aria-hidden=&#34;true&#34;&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 1&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 2&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 3&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 4&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 5&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 6&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 7&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 8&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 9&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 10&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 11&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 12&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 13&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 14&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 15&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34; data-tooltip=&#34;Micro-turn 16&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-indicator&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-tooltip&#34;&gt;
              &lt;span class=&#34;time-world-frame-tooltip-card&#34;&gt;
                &lt;span class=&#34;time-world-frame-tooltip-label&#34;&gt;Micro-turn 1&lt;/span&gt;
                &lt;span class=&#34;time-world-frame-tooltip-time&#34;&gt;200ms&lt;/span&gt;
              &lt;/span&gt;
            &lt;/span&gt;
            &lt;span class=&#34;time-world-loading-counter&#34;&gt;&lt;span class=&#34;time-world-loading-counter-number&#34;&gt;200&lt;/span&gt;&lt;span
                class=&#34;time-world-loading-counter-unit&#34;&gt;ms&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-hover-cue&#34; aria-hidden=&#34;true&#34;&gt;
              &lt;span class=&#34;time-world-hover-cue-icon&#34;&gt;&lt;/span&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;div class=&#34;time-world-row time-world-video-row&#34;&gt;
            &lt;div class=&#34;time-world-label&#34;&gt;video&lt;/div&gt;
            &lt;div class=&#34;time-world-track time-world-frame-track time-world-video-track&#34;&gt;
              &lt;div class=&#34;time-world-video-strip&#34;&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/1.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/2.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/3.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/4.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/5.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/6.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/7.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/8.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/9.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/10.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/11.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/12.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/13.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/14.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/15.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-video-frame&#34;&gt;&lt;img src=&#34;frames/16.jpg&#34; width=&#34;500&#34; height=&#34;360&#34; alt=&#34;&#34;
                    loading=&#34;lazy&#34; decoding=&#34;async&#34; draggable=&#34;false&#34;&gt;&lt;/span&gt;
              &lt;/div&gt;
              &lt;span class=&#34;time-world-block time-world-human&#34; data-turn=&#34;i1&#34;
                style=&#34;grid-column: 9 / span 11; grid-row: 1;&#34; aria-label=&#34;video input 1&#34;&gt;&lt;/span&gt;
              &lt;span class=&#34;time-world-block time-world-human&#34; data-turn=&#34;i2&#34;
                style=&#34;grid-column: 35 / span 14; grid-row: 1;&#34; aria-label=&#34;video input 2&#34;&gt;&lt;/span&gt;
              &lt;span class=&#34;time-world-block time-world-human&#34; data-turn=&#34;i3&#34;
                style=&#34;grid-column: 59 / span 20; grid-row: 1;&#34; aria-label=&#34;video input 3&#34;&gt;&lt;/span&gt;
              &lt;span class=&#34;time-world-block time-world-human&#34; data-turn=&#34;i4&#34;
                style=&#34;grid-column: 88 / span 9; grid-row: 1;&#34; aria-label=&#34;video input 4&#34;&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;

          &lt;div class=&#34;time-world-row time-world-audio-row&#34;&gt;
            &lt;div class=&#34;time-world-label&#34;&gt;audio&lt;/div&gt;
            &lt;div class=&#34;time-world-track time-world-frame-track time-world-audio-track&#34;&gt;
              &lt;span class=&#34;time-world-block time-world-human&#34; data-turn=&#34;i1&#34;
                style=&#34;grid-column: 1 / span 17; grid-row: 1;&#34; aria-label=&#34;audio input 1&#34;&gt;&lt;/span&gt;
              &lt;span class=&#34;time-world-block time-world-human&#34; data-turn=&#34;i2&#34;
                style=&#34;grid-column: 20 / span 15; grid-row: 1;&#34; aria-label=&#34;audio input 2&#34;&gt;&lt;/span&gt;
              &lt;span class=&#34;time-world-block time-world-human&#34; data-turn=&#34;i3&#34;
                style=&#34;grid-column: 37 / span 18; grid-row: 1;&#34; aria-label=&#34;audio input 3&#34;&gt;&lt;/span&gt;
              &lt;span class=&#34;time-world-block time-world-human&#34; data-turn=&#34;i4&#34;
                style=&#34;grid-column: 57 / span 16; grid-row: 1;&#34; aria-label=&#34;audio input 4&#34;&gt;&lt;/span&gt;
              &lt;span class=&#34;time-world-block time-world-human&#34; data-turn=&#34;i5&#34;
                style=&#34;grid-column: 74 / span 11; grid-row: 1;&#34; aria-label=&#34;audio input 5&#34;&gt;&lt;/span&gt;
              &lt;span class=&#34;time-world-block time-world-human&#34; data-turn=&#34;i6&#34;
                style=&#34;grid-column: 86 / span 11; grid-row: 1;&#34; aria-label=&#34;audio input 6&#34;&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;

          &lt;div class=&#34;time-world-row time-world-model-row&#34;&gt;
            &lt;div class=&#34;time-world-label&#34;&gt;model&lt;/div&gt;
            &lt;div class=&#34;time-world-track time-world-frame-track time-world-model-track&#34;&gt;
              &lt;span class=&#34;time-world-block time-world-model&#34; data-turn=&#34;o1&#34;
                style=&#34;grid-column: 10 / span 18; grid-row: 1;&#34; aria-label=&#34;model audio output 1&#34;&gt;&lt;/span&gt;
              &lt;span class=&#34;time-world-block time-world-model&#34; data-turn=&#34;o2&#34;
                style=&#34;grid-column: 28 / span 21; grid-row: 1;&#34; aria-label=&#34;model audio output 2&#34;&gt;&lt;/span&gt;
              &lt;span class=&#34;time-world-block time-world-model&#34; data-turn=&#34;o3&#34;
                style=&#34;grid-column: 50 / span 18; grid-row: 1;&#34; aria-label=&#34;model audio output 3&#34;&gt;&lt;/span&gt;
              &lt;span class=&#34;time-world-block time-world-model&#34; data-turn=&#34;o4&#34;
                style=&#34;grid-column: 68 / span 16; grid-row: 1;&#34; aria-label=&#34;model audio output 4&#34;&gt;&lt;/span&gt;
              &lt;span class=&#34;time-world-block time-world-model&#34; data-turn=&#34;o5&#34;
                style=&#34;grid-column: 82 / span 15; grid-row: 1;&#34; aria-label=&#34;model audio output 5&#34;&gt;&lt;/span&gt;
              &lt;span class=&#34;time-world-model-marker-layer&#34; aria-hidden=&#34;true&#34;&gt;
                &lt;span class=&#34;time-world-model-marker&#34; style=&#34;grid-column: 3;&#34; data-microturn-index=&#34;2&#34;
                  data-marker-tooltip=&#34;Model interrupts and responds immediately&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-model-marker&#34; style=&#34;grid-column: 7;&#34; data-microturn-index=&#34;6&#34;
                  data-marker-tooltip=&#34;Model and user both remain silent&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-model-marker&#34; style=&#34;grid-column: 10;&#34; data-microturn-index=&#34;9&#34;
                  data-marker-tooltip=&#34;Model backchannels while the user talks&#34;&gt;&lt;/span&gt;
                &lt;span class=&#34;time-world-model-marker&#34; style=&#34;grid-column: 13;&#34; data-microturn-index=&#34;12&#34;
                  data-marker-tooltip=&#34;Model reacts to visual clues without explicit prompting&#34;&gt;&lt;/span&gt;
              &lt;/span&gt;
              &lt;span class=&#34;time-world-model-note-layer&#34; aria-hidden=&#34;true&#34;&gt;
                &lt;span class=&#34;time-world-model-note&#34; style=&#34;grid-column: 3;&#34; data-microturn-index=&#34;2&#34;&gt;Model interrupts
                  and responds immediately&lt;/span&gt;
                &lt;span class=&#34;time-world-model-note&#34; style=&#34;grid-column: 7;&#34; data-microturn-index=&#34;6&#34;&gt;Model and user both
                  remain silent&lt;/span&gt;
                &lt;span class=&#34;time-world-model-note&#34; style=&#34;grid-column: 10;&#34; data-microturn-index=&#34;9&#34;&gt;Model backchannels
                  while the user talks&lt;/span&gt;
                &lt;span class=&#34;time-world-model-note&#34; style=&#34;grid-column: 13;&#34; data-microturn-index=&#34;12&#34;&gt;Model reacts to
                  visual clues without explicit prompting&lt;/span&gt;
              &lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;

        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&#34;token-timeline-controls time-world-controls&#34; aria-label=&#34;Timeline controls&#34;&gt;
        &lt;button class=&#34;token-timeline-control&#34; type=&#34;button&#34; data-time-world-action=&#34;skip&#34;&gt;skip&lt;/button&gt;
        &lt;button class=&#34;token-timeline-control&#34; type=&#34;button&#34; data-time-world-action=&#34;replay&#34;&gt;
          &lt;svg class=&#34;token-timeline-control-icon&#34; width=&#34;14&#34; height=&#34;14&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34;
            aria-hidden=&#34;true&#34; focusable=&#34;false&#34;&gt;
            &lt;path d=&#34;M9 14 4 9l5-5&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34;
              stroke-linejoin=&#34;round&#34;&gt;&lt;/path&gt;
            &lt;path d=&#34;M4 9h10.5a5.5 5.5 0 0 1 0 11H11&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34;
              stroke-linejoin=&#34;round&#34;&gt;&lt;/path&gt;
          &lt;/svg&gt;
          &lt;span&gt;play again&lt;/span&gt;
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/section&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;Turn-based models see an alternating token sequence. Time-aware interaction models see a continuous stream
    of micro-turns, so silence, overlap, and interruption remain part of the model&#39;s context.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;script&gt;
  (() =&gt; {
    const figures = Array.from(document.querySelectorAll(&#39;.grounded-reality-figure:not(.audio-comparison-figure)&#39;));
    if (figures.length === 0) return;

    const loadScript = () =&gt; {
      if (window.__groundedRealityFigureScriptLoading) return;
      window.__groundedRealityFigureScriptLoading = true;
      const script = document.createElement(&#39;script&#39;);
      script.src = &#39;/blog/interaction-models/js/figure-grounded-reality.js?v=20260514&#39;;
      script.async = true;
      document.head.appendChild(script);
    };

    figures.forEach((figure) =&gt; {
      figure.addEventListener(&#39;pointerenter&#39;, loadScript, { once: true });
      figure.addEventListener(&#39;focusin&#39;, loadScript, { once: true });
    });

    if (&#39;IntersectionObserver&#39; in window) {
      const observer = new IntersectionObserver((entries) =&gt; {
        if (entries.some((entry) =&gt; entry.isIntersecting)) {
          loadScript();
          observer.disconnect();
        }
      }, { rootMargin: &#39;900px 0px&#39;, threshold: 0 });

      figures.forEach((figure) =&gt; observer.observe(figure));
      return;
    }

    const schedule = window.requestIdleCallback || ((callback) =&gt; window.setTimeout(callback, 1));
    schedule(loadScript, { timeout: 1600 });
  })();
&lt;/script&gt;

&lt;p&gt;An interaction model is in constant two-way exchange with the user—perceiving and responding at the same time. Some domains take such interactivity as a given—the physical world demands that robotics and autonomous vehicles operate in real time. Audio full-duplex models&lt;label for=&#34;sn-im-11&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-11&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Moshi, PersonaPlex, nemotron-voicechat, Seeduplex.&lt;/span&gt; are another example where interaction is bidirectional and continuous.&lt;/p&gt;
&lt;p&gt;Applying the same principle, we set out to build an interaction model native to this regime—one that perceives and responds in the same continuous loop, across audio, video, and text. The result is a system architected around two ideas: a time-aware interaction model that maintains real-time presence, and an asynchronous background model that handles sustained reasoning, tool use, and longer-horizon work.&lt;/p&gt;
&lt;h3 id=&#34;system-overview&#34;&gt;System overview&lt;/h3&gt;
&lt;p&gt;The interaction model is in constant exchange with the user. When a task requires deeper reasoning than can be produced instantaneously, the interaction model delegates to a background model that runs asynchronously.&lt;label for=&#34;sn-im-12&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-12&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;This approach builds upon prior work like Qwen-omni, KAME, MoshiRAG.&lt;/span&gt; The interaction model remains present throughout — answering follow-ups, taking new input, holding the thread — and integrates background results into the conversation as they arrive.&lt;/p&gt;
&lt;figure class=&#34;interaction-model-figure&#34;&gt;
  &lt;div class=&#34;interaction-model-panel&#34;&gt;
    &lt;div class=&#34;interaction-model-diagram&#34; role=&#34;img&#34;
      aria-label=&#34;Diagram showing a user connected to an interaction model, which exchanges context and responses with a background model that can call tools and browse.&#34;&gt;
      &lt;svg class=&#34;interaction-model-arrows&#34; viewBox=&#34;0 0 1600 1015&#34; preserveAspectRatio=&#34;none&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34;&gt;
        &lt;path id=&#34;interaction-model-flow-user&#34; class=&#34;interaction-model-arrow interaction-model-desktop-route&#34;
          d=&#34;M 300 700 V 575 Q 300 543 332 543 H 445&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;interaction-model-arrowhead interaction-model-desktop-route&#34; d=&#34;M 421 527 L 445 543 L 421 559&#34;&gt;&lt;/path&gt;
        &lt;path id=&#34;interaction-model-flow-interfacer&#34; class=&#34;interaction-model-arrow interaction-model-desktop-route&#34;
          d=&#34;M 606 620 V 733 Q 606 765 574 765 H 460&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;interaction-model-arrowhead interaction-model-desktop-route&#34; d=&#34;M 484 749 L 460 765 L 484 781&#34;&gt;&lt;/path&gt;
        &lt;path id=&#34;interaction-model-flow-context&#34; class=&#34;interaction-model-arrow interaction-model-spark-delay interaction-model-desktop-route&#34;
          d=&#34;M 606 467 V 289 Q 606 257 638 257 H 878&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;interaction-model-arrowhead interaction-model-spark-delay interaction-model-desktop-route&#34; d=&#34;M 854 241 L 878 257 L 854 273&#34;&gt;
        &lt;/path&gt;
        &lt;path id=&#34;interaction-model-flow-response&#34; class=&#34;interaction-model-arrow interaction-model-spark-soft interaction-model-desktop-route&#34;
          d=&#34;M 1040 333 V 511 Q 1040 543 1008 543 H 766&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;interaction-model-arrowhead interaction-model-spark-soft interaction-model-desktop-route&#34; d=&#34;M 790 527 L 766 543 L 790 559&#34;&gt;&lt;/path&gt;
        &lt;path id=&#34;interaction-model-flow-tools&#34; class=&#34;interaction-model-arrow interaction-model-spark-brain interaction-model-desktop-route&#34;
          d=&#34;M 1040 180 V 110 Q 1040 78 1072 78 H 1251 Q 1283 78 1283 110 V 225 Q 1283 257 1251 257 H 1200&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;interaction-model-arrowhead interaction-model-spark-brain interaction-model-desktop-route&#34; d=&#34;M 1224 241 L 1200 257 L 1224 273&#34;&gt;
        &lt;/path&gt;
        &lt;path id=&#34;interaction-model-route-realtime&#34; class=&#34;interaction-model-motion-route interaction-model-desktop-route&#34;
          d=&#34;M 300 700 V 575 Q 300 543 332 543 H 445 L 606 543 V 620 V 733 Q 606 765 574 765 H 460 L 300 765 V 700&#34;&gt;
        &lt;/path&gt;
        &lt;path id=&#34;interaction-model-route-brain-cycle&#34; class=&#34;interaction-model-motion-route interaction-model-desktop-route&#34;
          d=&#34;M 606 467 V 289 Q 606 257 638 257 H 878 L 956 257 H 1040 V 180 V 110 Q 1040 78 1072 78 H 1251 Q 1283 78 1283 110 V 225 Q 1283 257 1251 257 H 1200 L 1110 257 H 1040 V 333 V 511 Q 1040 543 1008 543 H 766 L 606 543 V 467&#34;&gt;
        &lt;/path&gt;
        &lt;path id=&#34;interaction-model-flow-user-mobile&#34; class=&#34;interaction-model-arrow interaction-model-mobile-route&#34;
          d=&#34;M 300 676 V 573 Q 300 541 332 541 H 394&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;interaction-model-arrowhead interaction-model-mobile-route&#34; d=&#34;M 370 525 L 394 541 L 370 557&#34;&gt;&lt;/path&gt;
        &lt;path id=&#34;interaction-model-flow-interfacer-mobile&#34; class=&#34;interaction-model-arrow interaction-model-mobile-route&#34;
          d=&#34;M 610 666 V 744 Q 610 776 578 776 H 558&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;interaction-model-arrowhead interaction-model-mobile-route&#34; d=&#34;M 582 760 L 558 776 L 582 792&#34;&gt;&lt;/path&gt;
        &lt;path id=&#34;interaction-model-flow-context-mobile&#34; class=&#34;interaction-model-arrow interaction-model-spark-delay interaction-model-mobile-route&#34;
          d=&#34;M 610 442 V 310 Q 610 278 642 278 H 838&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;interaction-model-arrowhead interaction-model-spark-delay interaction-model-mobile-route&#34; d=&#34;M 814 262 L 838 278 L 814 294&#34;&gt;
        &lt;/path&gt;
        &lt;path id=&#34;interaction-model-flow-response-mobile&#34; class=&#34;interaction-model-arrow interaction-model-spark-soft interaction-model-mobile-route&#34;
          d=&#34;M 1082 386 V 526 Q 1082 558 1050 558 H 860&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;interaction-model-arrowhead interaction-model-spark-soft interaction-model-mobile-route&#34; d=&#34;M 884 542 L 860 558 L 884 574&#34;&gt;&lt;/path&gt;
        &lt;path id=&#34;interaction-model-flow-tools-mobile&#34; class=&#34;interaction-model-arrow interaction-model-spark-brain interaction-model-mobile-route&#34;
          d=&#34;M 1082 170 V 150 Q 1082 132 1100 132 H 1350 Q 1390 132 1390 172 V 300 Q 1390 332 1358 332 H 1320&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;interaction-model-arrowhead interaction-model-spark-brain interaction-model-mobile-route&#34; d=&#34;M 1344 316 L 1320 332 L 1344 348&#34;&gt;
        &lt;/path&gt;
        &lt;path id=&#34;interaction-model-route-realtime-mobile&#34; class=&#34;interaction-model-motion-route interaction-model-mobile-route&#34;
          d=&#34;M 300 676 V 573 Q 300 541 332 541 H 394 L 610 541 V 666 V 744 Q 610 776 578 776 H 558 L 300 776 V 676&#34;&gt;
        &lt;/path&gt;
        &lt;path id=&#34;interaction-model-route-brain-cycle-mobile&#34; class=&#34;interaction-model-motion-route interaction-model-mobile-route&#34;
          d=&#34;M 610 442 V 310 Q 610 278 642 278 H 838 L 956 278 H 1082 V 170 V 150 Q 1082 132 1100 132 H 1350 Q 1390 132 1390 172 V 300 Q 1390 332 1358 332 H 1320 L 1082 332 V 386 V 526 Q 1082 558 1050 558 H 860 L 610 558 V 442&#34;&gt;
        &lt;/path&gt;
        &lt;g class=&#34;interaction-model-spark interaction-model-desktop-route&#34;&gt;
          &lt;rect class=&#34;interaction-model-spark-pill&#34; x=&#34;-28&#34; y=&#34;-7&#34; width=&#34;56&#34; height=&#34;14&#34; rx=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;animateMotion dur=&#34;1.55s&#34; repeatCount=&#34;indefinite&#34; rotate=&#34;auto&#34;&gt;
            &lt;mpath href=&#34;#interaction-model-route-realtime&#34;&gt;&lt;/mpath&gt;
          &lt;/animateMotion&gt;
        &lt;/g&gt;
        &lt;g class=&#34;interaction-model-spark interaction-model-desktop-route&#34;&gt;
          &lt;rect class=&#34;interaction-model-spark-pill&#34; x=&#34;-28&#34; y=&#34;-7&#34; width=&#34;56&#34; height=&#34;14&#34; rx=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;animateMotion dur=&#34;1.55s&#34; begin=&#34;-0.775s&#34; repeatCount=&#34;indefinite&#34; rotate=&#34;auto&#34;&gt;
            &lt;mpath href=&#34;#interaction-model-route-realtime&#34;&gt;&lt;/mpath&gt;
          &lt;/animateMotion&gt;
        &lt;/g&gt;
        &lt;g class=&#34;interaction-model-spark interaction-model-spark-delay interaction-model-desktop-route&#34;&gt;
          &lt;rect class=&#34;interaction-model-spark-pill&#34; x=&#34;-28&#34; y=&#34;-7&#34; width=&#34;56&#34; height=&#34;14&#34; rx=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;animateMotion dur=&#34;4.2s&#34; begin=&#34;-1.1s&#34; repeatCount=&#34;indefinite&#34; rotate=&#34;auto&#34;&gt;
            &lt;mpath href=&#34;#interaction-model-route-brain-cycle&#34;&gt;&lt;/mpath&gt;
          &lt;/animateMotion&gt;
        &lt;/g&gt;
        &lt;g class=&#34;interaction-model-spark interaction-model-mobile-route&#34;&gt;
          &lt;rect class=&#34;interaction-model-spark-pill&#34; x=&#34;-28&#34; y=&#34;-7&#34; width=&#34;56&#34; height=&#34;14&#34; rx=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;animateMotion dur=&#34;1.55s&#34; repeatCount=&#34;indefinite&#34; rotate=&#34;auto&#34;&gt;
            &lt;mpath href=&#34;#interaction-model-route-realtime-mobile&#34;&gt;&lt;/mpath&gt;
          &lt;/animateMotion&gt;
        &lt;/g&gt;
        &lt;g class=&#34;interaction-model-spark interaction-model-mobile-route&#34;&gt;
          &lt;rect class=&#34;interaction-model-spark-pill&#34; x=&#34;-28&#34; y=&#34;-7&#34; width=&#34;56&#34; height=&#34;14&#34; rx=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;animateMotion dur=&#34;1.55s&#34; begin=&#34;-0.775s&#34; repeatCount=&#34;indefinite&#34; rotate=&#34;auto&#34;&gt;
            &lt;mpath href=&#34;#interaction-model-route-realtime-mobile&#34;&gt;&lt;/mpath&gt;
          &lt;/animateMotion&gt;
        &lt;/g&gt;
        &lt;g class=&#34;interaction-model-spark interaction-model-spark-delay interaction-model-mobile-route&#34;&gt;
          &lt;rect class=&#34;interaction-model-spark-pill&#34; x=&#34;-28&#34; y=&#34;-7&#34; width=&#34;56&#34; height=&#34;14&#34; rx=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;animateMotion dur=&#34;4.2s&#34; begin=&#34;-1.1s&#34; repeatCount=&#34;indefinite&#34; rotate=&#34;auto&#34;&gt;
            &lt;mpath href=&#34;#interaction-model-route-brain-cycle-mobile&#34;&gt;&lt;/mpath&gt;
          &lt;/animateMotion&gt;
        &lt;/g&gt;
      &lt;/svg&gt;
      &lt;div class=&#34;interaction-model-realtime&#34;&gt;
        &lt;span&gt;real-time&lt;/span&gt;
      &lt;/div&gt;
      &lt;div class=&#34;interaction-model-node interaction-model-user&#34;&gt;user&lt;/div&gt;
      &lt;div class=&#34;interaction-model-node interaction-model-interfacer&#34;&gt;interaction&lt;br&gt;model&lt;/div&gt;
      &lt;div class=&#34;interaction-model-node interaction-model-brain&#34;&gt;background&lt;br&gt;model&lt;/div&gt;
      &lt;div class=&#34;interaction-model-label interaction-model-context&#34;&gt;context&lt;/div&gt;
      &lt;div class=&#34;interaction-model-label interaction-model-response&#34;&gt;response&lt;/div&gt;
      &lt;div class=&#34;interaction-model-label interaction-model-tools&#34;&gt;tool calls&lt;br&gt;browsing&lt;br class=&#34;interaction-model-tools-etc-break&#34;&gt;&lt;span class=&#34;interaction-model-tools-etc&#34;&gt;etc&lt;/span&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;The user continuously interacts with the interaction model, while the background model performs
    asynchronous tasks. Both systems share their context.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;script src=&#34;https://thinkingmachines.ai/blog/interaction-models/js/interaction-model-figure.js?v=20260514&#34; defer&gt;&lt;/script&gt;

&lt;p&gt;This split lets the user benefit from both responsiveness as well as the full extent of intelligence: the planning, tool-use, and agentic workflows of reasoning models at the response latency of non-thinking ones. Note that both the background and interaction models are intelligent — on its own, the interaction model is also competitive on both interactive and intelligence benchmarks.&lt;/p&gt;
&lt;h3 id=&#34;the-interaction-model&#34;&gt;The interaction model&lt;/h3&gt;
&lt;p&gt;Our starting point is continuous audio and video — modalities that are inherently real-time. Text can wait, but a live conversation cannot. By designing around the hardest case first, we arrive at an architecture that is natively multimodal, time-aware, and capable of handling concurrent input and output streams across all modalities. Several design choices make this possible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Time-aligned micro-turns.&lt;/strong&gt; The interaction model works with micro-turns continuously interleaving the processing of 200ms worth of input and generation of 200ms worth of output. Rather than consuming a complete user-turn and generating a complete response, both input and output tokens are treated as streams. Working with 200ms chunks of these streams enables near real-time concurrency of multiple input and output modalities.&lt;/p&gt;
&lt;figure id=&#34;fig-token-timeline&#34; class=&#34;token-timeline-figure&#34;&gt;
  &lt;div class=&#34;token-timeline-panel&#34; role=&#34;img&#34;
    aria-label=&#34;Human perception has separate input and output streams, while the model receives one interleaved token sequence.&#34;&gt;
    &lt;div class=&#34;token-timeline-section token-timeline-human&#34;&gt;
      &lt;div class=&#34;token-timeline-title&#34;&gt;Human perception&lt;/div&gt;
      &lt;div class=&#34;token-timeline-grid&#34;&gt;
        &lt;div class=&#34;token-timeline-time-markers&#34; aria-hidden=&#34;true&#34;&gt;
          &lt;span class=&#34;token-timeline-time-marker token-timeline-time-marker-1&#34;&gt;&lt;span&gt;200ms&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-time-marker token-timeline-time-marker-2&#34;&gt;&lt;span&gt;400ms&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-time-marker token-timeline-time-marker-3&#34;&gt;&lt;span&gt;600ms&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-time-marker token-timeline-time-marker-4&#34;&gt;&lt;span&gt;800ms&lt;/span&gt;&lt;/span&gt;
        &lt;/div&gt;

        &lt;div class=&#34;token-timeline-track token-timeline-track-human-input&#34;&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-input&#34; style=&#34;--token-timeline-token-delay: 0s;&#34;
            data-token-key=&#34;input-0&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;I0&#34;&gt;input
              0&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-input&#34; style=&#34;--token-timeline-token-delay: 1.5s;&#34;
            data-token-key=&#34;input-1&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;I1&#34;&gt;input
              1&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-input&#34; style=&#34;--token-timeline-token-delay: 3s;&#34;
            data-token-key=&#34;input-2&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;I2&#34;&gt;input
              2&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-input&#34; style=&#34;--token-timeline-token-delay: 4.5s;&#34;
            data-token-key=&#34;input-3&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;I3&#34;&gt;input
              3&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-input&#34; style=&#34;--token-timeline-token-delay: 6s;&#34;
            data-token-key=&#34;input-4&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;I4&#34;&gt;input
              4&lt;/span&gt;&lt;/span&gt;
        &lt;/div&gt;

        &lt;div class=&#34;token-timeline-track token-timeline-track-human-output&#34;&gt;
          &lt;span class=&#34;token-timeline-gap&#34; aria-hidden=&#34;true&#34;&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-output&#34; style=&#34;--token-timeline-token-delay: 0.75s;&#34;
            data-token-key=&#34;output-0&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;O0&#34;&gt;output
              0&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-output&#34; style=&#34;--token-timeline-token-delay: 2.25s;&#34;
            data-token-key=&#34;output-1&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;O1&#34;&gt;output
              1&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-output&#34; style=&#34;--token-timeline-token-delay: 3.75s;&#34;
            data-token-key=&#34;output-2&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;O2&#34;&gt;output
              2&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-output&#34; style=&#34;--token-timeline-token-delay: 5.25s;&#34;
            data-token-key=&#34;output-3&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;O3&#34;&gt;output
              3&lt;/span&gt;&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class=&#34;token-timeline-section token-timeline-model&#34;&gt;
      &lt;div class=&#34;token-timeline-title&#34;&gt;Model token sequence&lt;/div&gt;
      &lt;div class=&#34;token-timeline-model-scroll&#34;&gt;
        &lt;div class=&#34;token-timeline-track token-timeline-track-model&#34;&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-input&#34; style=&#34;--token-timeline-token-delay: 0s;&#34;
            data-token-key=&#34;input-0&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;I0&#34;&gt;input
              0&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-output&#34; style=&#34;--token-timeline-token-delay: 0.75s;&#34;
            data-token-key=&#34;output-0&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;O0&#34;&gt;output
              0&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-input&#34; style=&#34;--token-timeline-token-delay: 1.5s;&#34;
            data-token-key=&#34;input-1&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;I1&#34;&gt;input
              1&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-output&#34; style=&#34;--token-timeline-token-delay: 2.25s;&#34;
            data-token-key=&#34;output-1&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;O1&#34;&gt;output
              1&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-input&#34; style=&#34;--token-timeline-token-delay: 3s;&#34;
            data-token-key=&#34;input-2&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;I2&#34;&gt;input
              2&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-output&#34; style=&#34;--token-timeline-token-delay: 3.75s;&#34;
            data-token-key=&#34;output-2&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;O2&#34;&gt;output
              2&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-input&#34; style=&#34;--token-timeline-token-delay: 4.5s;&#34;
            data-token-key=&#34;input-3&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;I3&#34;&gt;input
              3&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-output&#34; style=&#34;--token-timeline-token-delay: 5.25s;&#34;
            data-token-key=&#34;output-3&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;O3&#34;&gt;output
              3&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;token-timeline-token token-timeline-input&#34; style=&#34;--token-timeline-token-delay: 6s;&#34;
            data-token-key=&#34;input-4&#34;&gt;&lt;span class=&#34;token-timeline-token-label&#34; data-mobile-label=&#34;I4&#34;&gt;input
              4&lt;/span&gt;&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&#34;token-timeline-controls&#34; aria-label=&#34;Timeline controls&#34;&gt;
        &lt;button class=&#34;token-timeline-control&#34; type=&#34;button&#34; data-token-timeline-action=&#34;skip&#34;&gt;skip&lt;/button&gt;
        &lt;button class=&#34;token-timeline-control&#34; type=&#34;button&#34; data-token-timeline-action=&#34;replay&#34;&gt;
          &lt;svg class=&#34;token-timeline-control-icon&#34; width=&#34;14&#34; height=&#34;14&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34;
            aria-hidden=&#34;true&#34; focusable=&#34;false&#34;&gt;
            &lt;path d=&#34;M9 14 4 9l5-5&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34;
              stroke-linejoin=&#34;round&#34;&gt;&lt;/path&gt;
            &lt;path d=&#34;M4 9h10.5a5.5 5.5 0 0 1 0 11H11&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34;
              stroke-linejoin=&#34;round&#34;&gt;&lt;/path&gt;
          &lt;/svg&gt;
          &lt;span&gt;play again&lt;/span&gt;
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;Human perception preserves concurrent input and output streams, while the model receives a single
    interleaved token sequence.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;script&gt;
  (() =&gt; {
    const figures = Array.from(document.querySelectorAll(&#39;.token-timeline-figure&#39;));
    if (figures.length === 0) return;

    const loadScript = () =&gt; {
      if (window.__figure3timelineLoading) return;
      window.__figure3timelineLoading = true;
      const script = document.createElement(&#39;script&#39;);
      script.src = &#39;/blog/interaction-models/js/figure-3-timeline.js?v=20260514&#39;;
      script.async = true;
      document.head.appendChild(script);
    };

    figures.forEach((figure) =&gt; {
      figure.addEventListener(&#39;pointerenter&#39;, loadScript, { once: true });
      figure.addEventListener(&#39;focusin&#39;, loadScript, { once: true });
    });

    if (&#39;IntersectionObserver&#39; in window) {
      const observer = new IntersectionObserver((entries) =&gt; {
        if (entries.some((entry) =&gt; entry.isIntersecting)) {
          loadScript();
          observer.disconnect();
        }
      }, { rootMargin: &#39;900px 0px&#39;, threshold: 0 });

      figures.forEach((figure) =&gt; observer.observe(figure));
      return;
    }

    const schedule = window.requestIdleCallback || ((callback) =&gt; window.setTimeout(callback, 1));
    schedule(loadScript, { timeout: 1800 });
  })();
&lt;/script&gt;

&lt;p&gt;With this design, there are no artificial turn boundaries that the model must adhere to. In contrast, most existing real-time systems require a harness that predicts turn boundaries in order for the turn-based models to feel real-time and responsive.&lt;label for=&#34;sn-im-13&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-13&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Moshi, PersonaPlex, and Nemotron Voicechat are examples of full duplex systems that do not use harnesses to detect turns. They are smaller scale models focused on latency rather than intelligence benchmarks.&lt;/span&gt; This harness is made out of components like voice-activity-detection (VAD) that are meaningfully less intelligent than the model itself. This precludes a variety of interaction modes like proactive interjections (“interrupt when I say something wrong”) or reactions to visual cues (“tell me when I’ve written a bug in my code”). Moreover, the model can do things like speak while listening (“translate from spanish to english live”) or watching (“live-commentate this sports game”).&lt;/p&gt;
&lt;p&gt;Thus, all of these different interaction modes that require special harnesses today become special-cases of what the model can do and improve in quality as we scale up model size and training data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Encoder-free early fusion.&lt;/strong&gt; Rather than processing audio and video through large, standalone encoders, we opt for a system with minimal pre-processing. Many omnimodal models require training a separate encoder (e.g. Whisper-like) or decoder (e.g. TTS model-like). We instead take in audio signals as dMel (&lt;a href=&#34;https://arxiv.org/abs/2407.15835&#34; target=&#34;_blank&#34;&gt;Bai, et al. 2024&lt;/a&gt;) and transform it via a light-weighted embedding layer. Images are split into 40x40 patches which are encoded by an hMLP (&lt;a href=&#34;https://arxiv.org/abs/2203.09795&#34; target=&#34;_blank&#34;&gt;Touvron et al. 2022&lt;/a&gt;).  For the audio decoder we use a flow head (&lt;a href=&#34;https://arxiv.org/abs/2210.02747&#34; target=&#34;_blank&#34;&gt;Lipman at al. 2022&lt;/a&gt;). All components are co-trained from scratch together with the transformer.&lt;/p&gt;
&lt;figure class=&#34;interfacer-architecture-figure&#34; data-mode=&#34;runtime&#34;&gt;
  &lt;div class=&#34;interfacer-architecture-panel&#34; data-mode=&#34;runtime&#34;&gt;
    &lt;svg class=&#34;interfacer-architecture-svg&#34; viewBox=&#34;0 52 620 482&#34; width=&#34;620&#34; height=&#34;482&#34; role=&#34;img&#34;
      aria-label=&#34;Diagram of the interaction model architecture for a single 200ms micro-turn. Text, frame, and audio inputs are embedded into a shared Transformer, which produces text and mel outputs. A 200ms span is shown under the input columns.&#34;&gt;
      &lt;g class=&#34;ia-arrows&#34;&gt;
        &lt;g class=&#34;ia-input-token-squares&#34; aria-hidden=&#34;true&#34;&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;37&#34; y=&#34;300&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;59&#34; y=&#34;300&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;81&#34; y=&#34;300&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;103&#34; y=&#34;300&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;125&#34; y=&#34;300&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;147&#34; y=&#34;300&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;169&#34; y=&#34;300&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;191&#34; y=&#34;300&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;213&#34; y=&#34;300&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;235&#34; y=&#34;300&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;257&#34; y=&#34;300&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;279&#34; y=&#34;300&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;301&#34; y=&#34;300&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;323&#34; y=&#34;300&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;345&#34; y=&#34;300&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;

          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;379&#34; y=&#34;222&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;401&#34; y=&#34;222&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;423&#34; y=&#34;222&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;445&#34; y=&#34;222&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;467&#34; y=&#34;222&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;489&#34; y=&#34;222&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;511&#34; y=&#34;222&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;533&#34; y=&#34;222&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;555&#34; y=&#34;222&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
          &lt;rect class=&#34;ia-gap-square&#34; x=&#34;577&#34; y=&#34;222&#34; width=&#34;12&#34; height=&#34;12&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;
        &lt;/g&gt;
        &lt;g class=&#34;ia-arrow-outlines&#34; aria-hidden=&#34;true&#34;&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 82 430 V 422&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 196 430 V 422&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 310 430 V 422&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 82 376 V 368&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 196 376 V 368&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 310 376 V 368&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 82 321 V 291&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 78 297 L 82 291 L 86 297&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 196 321 V 291&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 192 297 L 196 291 L 200 297&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 310 321 V 291&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 306 297 L 310 291 L 314 297&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 424 243 V 213&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 538 243 V 213&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 424 165 V 149&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 420 155 L 424 149 L 428 155&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 538 165 V 149&#34;&gt;&lt;/path&gt;
          &lt;path class=&#34;ia-arrow-outline&#34; d=&#34;M 534 155 L 538 149 L 542 155&#34;&gt;&lt;/path&gt;
        &lt;/g&gt;
        &lt;path class=&#34;ia-arrow-stem&#34; d=&#34;M 82 430 V 422&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrow-stem&#34; d=&#34;M 196 430 V 422&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrow-stem&#34; d=&#34;M 310 430 V 422&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrow-stem&#34; d=&#34;M 82 376 V 368&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrow-stem&#34; d=&#34;M 196 376 V 368&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrow-stem&#34; d=&#34;M 310 376 V 368&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrow-stem&#34; d=&#34;M 82 321 V 291&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrowhead&#34; d=&#34;M 78 297 L 82 291 L 86 297&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrow-stem&#34; d=&#34;M 196 321 V 291&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrowhead&#34; d=&#34;M 192 297 L 196 291 L 200 297&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrow-stem&#34; d=&#34;M 310 321 V 291&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrowhead&#34; d=&#34;M 306 297 L 310 291 L 314 297&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrow-stem&#34; d=&#34;M 424 243 V 213&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrow-stem&#34; d=&#34;M 538 243 V 213&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrow-stem&#34; d=&#34;M 424 165 V 149&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrowhead&#34; d=&#34;M 420 155 L 424 149 L 428 155&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrow-stem&#34; d=&#34;M 538 165 V 149&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-arrowhead&#34; d=&#34;M 534 155 L 538 149 L 542 155&#34;&gt;&lt;/path&gt;
      &lt;/g&gt;

      &lt;g class=&#34;ia-runtime-layer&#34; aria-hidden=&#34;true&#34;&gt;
        &lt;path id=&#34;ia-runtime-input-route&#34; class=&#34;ia-runtime-route ia-runtime-input-route&#34;
          d=&#34;M 82 431 V 421 C 82 406 82 398 82 377 V 367 C 82 348 82 340 82 321 V 291&#34;&gt;&lt;/path&gt;
        &lt;path id=&#34;ia-runtime-frame-route&#34; class=&#34;ia-runtime-route ia-runtime-input-route&#34;
          d=&#34;M 196 431 V 421 C 196 406 196 398 196 377 V 367 C 196 348 196 340 196 321 V 291&#34;&gt;&lt;/path&gt;
        &lt;path id=&#34;ia-runtime-audio-input-route&#34; class=&#34;ia-runtime-route ia-runtime-input-route&#34;
          d=&#34;M 310 431 V 421 C 310 406 310 398 310 377 V 367 C 310 348 310 340 310 321 V 291&#34;&gt;&lt;/path&gt;
        &lt;path id=&#34;ia-runtime-text-route&#34; class=&#34;ia-runtime-route ia-runtime-output-route&#34;
          d=&#34;M 424 243 V 213 C 424 200 424 184 424 165 V 149&#34;&gt;&lt;/path&gt;
        &lt;path id=&#34;ia-runtime-audio-route&#34; class=&#34;ia-runtime-route ia-runtime-output-route&#34; d=&#34;M 538 243 V 149&#34;&gt;&lt;/path&gt;
        &lt;path id=&#34;ia-runtime-text-flow-route&#34; class=&#34;ia-motion-route&#34;
          d=&#34;M 82 431 V 421 C 82 406 82 398 82 377 V 367 C 82 348 82 340 82 321 V 291 V 267 H 424 V 243 V 213 C 424 200 424 184 424 165 V 149&#34;&gt;
        &lt;/path&gt;
        &lt;path id=&#34;ia-runtime-frame-flow-route&#34; class=&#34;ia-motion-route&#34;
          d=&#34;M 196 431 V 421 C 196 406 196 398 196 377 V 367 C 196 348 196 340 196 321 V 291 V 267&#34;&gt;&lt;/path&gt;
        &lt;path id=&#34;ia-runtime-audio-flow-route&#34; class=&#34;ia-motion-route&#34;
          d=&#34;M 310 431 V 421 C 310 406 310 398 310 377 V 367 C 310 348 310 340 310 321 V 291 V 267 H 538 V 243 V 149&#34;&gt;
        &lt;/path&gt;

        &lt;g class=&#34;ia-flows&#34;&gt;
          &lt;g class=&#34;ia-flow ia-flow-input&#34;&gt;
            &lt;rect class=&#34;ia-flow-pill&#34; x=&#34;-12&#34; y=&#34;-2&#34; width=&#34;24&#34; height=&#34;4&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;
              &lt;animate attributeName=&#34;opacity&#34; dur=&#34;5.2s&#34; repeatCount=&#34;indefinite&#34; values=&#34;0;0.42;0.42;0;0&#34;
                keyTimes=&#34;0;0.08;0.48;0.62;1&#34; calcMode=&#34;linear&#34;&gt;&lt;/animate&gt;
            &lt;/rect&gt;
            &lt;animateMotion dur=&#34;5.2s&#34; repeatCount=&#34;indefinite&#34; rotate=&#34;auto&#34; keyPoints=&#34;0;1;1&#34;
              keyTimes=&#34;0;0.62;1&#34; calcMode=&#34;linear&#34;&gt;
              &lt;mpath href=&#34;#ia-runtime-input-route&#34;&gt;&lt;/mpath&gt;
            &lt;/animateMotion&gt;
          &lt;/g&gt;
          &lt;g class=&#34;ia-flow ia-flow-input&#34;&gt;
            &lt;rect class=&#34;ia-flow-pill&#34; x=&#34;-12&#34; y=&#34;-2&#34; width=&#34;24&#34; height=&#34;4&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;
              &lt;animate attributeName=&#34;opacity&#34; dur=&#34;5.6s&#34; begin=&#34;-1.2s&#34; repeatCount=&#34;indefinite&#34;
                values=&#34;0;0.42;0.42;0;0&#34; keyTimes=&#34;0;0.08;0.48;0.62;1&#34; calcMode=&#34;linear&#34;&gt;&lt;/animate&gt;
            &lt;/rect&gt;
            &lt;animateMotion dur=&#34;5.6s&#34; begin=&#34;-1.2s&#34; repeatCount=&#34;indefinite&#34; rotate=&#34;auto&#34; keyPoints=&#34;0;1;1&#34;
              keyTimes=&#34;0;0.62;1&#34; calcMode=&#34;linear&#34;&gt;
              &lt;mpath href=&#34;#ia-runtime-frame-route&#34;&gt;&lt;/mpath&gt;
            &lt;/animateMotion&gt;
          &lt;/g&gt;
          &lt;g class=&#34;ia-flow ia-flow-input&#34;&gt;
            &lt;rect class=&#34;ia-flow-pill&#34; x=&#34;-12&#34; y=&#34;-2&#34; width=&#34;24&#34; height=&#34;4&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;
              &lt;animate attributeName=&#34;opacity&#34; dur=&#34;6s&#34; begin=&#34;-2s&#34; repeatCount=&#34;indefinite&#34;
                values=&#34;0;0.42;0.42;0;0&#34; keyTimes=&#34;0;0.08;0.48;0.62;1&#34; calcMode=&#34;linear&#34;&gt;&lt;/animate&gt;
            &lt;/rect&gt;
            &lt;animateMotion dur=&#34;6s&#34; begin=&#34;-2s&#34; repeatCount=&#34;indefinite&#34; rotate=&#34;auto&#34; keyPoints=&#34;0;1;1&#34;
              keyTimes=&#34;0;0.62;1&#34; calcMode=&#34;linear&#34;&gt;
              &lt;mpath href=&#34;#ia-runtime-audio-input-route&#34;&gt;&lt;/mpath&gt;
            &lt;/animateMotion&gt;
          &lt;/g&gt;
          &lt;g class=&#34;ia-flow ia-flow-output&#34;&gt;
            &lt;rect class=&#34;ia-flow-pill&#34; x=&#34;-12&#34; y=&#34;-2&#34; width=&#34;24&#34; height=&#34;4&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;
              &lt;animate attributeName=&#34;opacity&#34; dur=&#34;5.4s&#34; begin=&#34;-1.4s&#34; repeatCount=&#34;indefinite&#34;
                values=&#34;0;0.42;0.42;0;0&#34; keyTimes=&#34;0;0.08;0.48;0.62;1&#34; calcMode=&#34;linear&#34;&gt;&lt;/animate&gt;
            &lt;/rect&gt;
            &lt;animateMotion dur=&#34;5.4s&#34; begin=&#34;-1.4s&#34; repeatCount=&#34;indefinite&#34; rotate=&#34;auto&#34; keyPoints=&#34;0;1;1&#34;
              keyTimes=&#34;0;0.62;1&#34; calcMode=&#34;linear&#34;&gt;
              &lt;mpath href=&#34;#ia-runtime-text-route&#34;&gt;&lt;/mpath&gt;
            &lt;/animateMotion&gt;
          &lt;/g&gt;
          &lt;g class=&#34;ia-flow ia-flow-output&#34;&gt;
            &lt;rect class=&#34;ia-flow-pill&#34; x=&#34;-12&#34; y=&#34;-2&#34; width=&#34;24&#34; height=&#34;4&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;
              &lt;animate attributeName=&#34;opacity&#34; dur=&#34;5.8s&#34; begin=&#34;-2.4s&#34; repeatCount=&#34;indefinite&#34;
                values=&#34;0;0.42;0.42;0;0&#34; keyTimes=&#34;0;0.08;0.48;0.62;1&#34; calcMode=&#34;linear&#34;&gt;&lt;/animate&gt;
            &lt;/rect&gt;
            &lt;animateMotion dur=&#34;5.8s&#34; begin=&#34;-2.4s&#34; repeatCount=&#34;indefinite&#34; rotate=&#34;auto&#34; keyPoints=&#34;0;1;1&#34;
              keyTimes=&#34;0;0.62;1&#34; calcMode=&#34;linear&#34;&gt;
              &lt;mpath href=&#34;#ia-runtime-audio-route&#34;&gt;&lt;/mpath&gt;
            &lt;/animateMotion&gt;
          &lt;/g&gt;
        &lt;/g&gt;
      &lt;/g&gt;

      &lt;g class=&#34;ia-modality-layer&#34; aria-hidden=&#34;true&#34;&gt;
        &lt;path class=&#34;ia-modality-route ia-modality-route-input ia-modality-route-text&#34; data-ia-route=&#34;text&#34;
          d=&#34;M 82 430 V 422 M 82 376 V 368 M 82 321 V 291 M 78 297 L 82 291 L 86 297&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-modality-route ia-modality-route-output ia-modality-route-text&#34; data-ia-route=&#34;text&#34;
          d=&#34;M 424 243 V 213 M 424 165 V 149 M 420 155 L 424 149 L 428 155&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-modality-route ia-modality-route-input ia-modality-route-image&#34; data-ia-route=&#34;image&#34;
          d=&#34;M 196 430 V 422 M 196 376 V 368 M 196 321 V 291 M 192 297 L 196 291 L 200 297&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-modality-route ia-modality-route-input ia-modality-route-video&#34; data-ia-route=&#34;video&#34;
          d=&#34;M 196 430 V 422 M 196 376 V 368 M 196 321 V 291 M 192 297 L 196 291 L 200 297&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-modality-route ia-modality-route-input ia-modality-route-audio&#34; data-ia-route=&#34;audio&#34;
          d=&#34;M 310 430 V 422 M 310 376 V 368 M 310 321 V 291 M 306 297 L 310 291 L 314 297&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-modality-route ia-modality-route-output ia-modality-route-audio&#34; data-ia-route=&#34;audio&#34;
          d=&#34;M 538 243 V 213 M 538 165 V 149 M 534 155 L 538 149 L 542 155&#34;&gt;&lt;/path&gt;
      &lt;/g&gt;

      &lt;g class=&#34;ia-layer ia-inputs&#34;&gt;
        &lt;g class=&#34;ia-cell&#34; data-ia-modality=&#34;text&#34; tabindex=&#34;0&#34;&gt;
          &lt;rect class=&#34;ia-box&#34; x=&#34;30&#34; y=&#34;433&#34; width=&#34;104&#34; height=&#34;40&#34; rx=&#34;7&#34; ry=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;g class=&#34;ia-input-icon&#34; aria-hidden=&#34;true&#34; transform=&#34;translate(56.5 447.3)&#34;&gt;
            &lt;path
              d=&#34;M13.584 11.3086C14.6973 11.3086 15.752 10.7031 16.25 9.74609L16.2695 9.74609L16.2695 10.5957C16.2695 11.0254 16.5723 11.3184 16.9824 11.3184C17.4121 11.3184 17.7051 11.0254 17.7051 10.5566L17.7051 5.51758C17.7051 3.86719 16.4453 2.80273 14.4824 2.80273C13.0664 2.80273 11.8457 3.4082 11.3965 4.3457C11.2988 4.54102 11.2402 4.72656 11.2402 4.90234C11.2402 5.2832 11.5234 5.53711 11.8945 5.53711C12.168 5.53711 12.373 5.43945 12.5098 5.20508C12.9688 4.39453 13.5547 4.0625 14.4434 4.0625C15.5566 4.0625 16.2207 4.6582 16.2207 5.64453L16.2207 6.2793L13.8672 6.39648C11.9238 6.49414 10.8301 7.40234 10.8301 8.83789C10.8301 10.3223 11.9531 11.3086 13.584 11.3086ZM13.9551 10.0977C12.9785 10.0977 12.3438 9.58008 12.3438 8.80859C12.3438 8.05664 12.959 7.54883 14.043 7.49023L16.2207 7.35352L16.2207 8.04688C16.2207 9.20898 15.2148 10.0977 13.9551 10.0977Z&#34;&gt;
            &lt;/path&gt;
            &lt;path
              d=&#34;M0.742188 11.3086C1.20117 11.3086 1.43555 11.1328 1.60156 10.6445L4.63867 1.94336L4.6875 1.94336L7.73438 10.6445C7.90039 11.1328 8.13477 11.3086 8.59375 11.3086C9.05273 11.3086 9.35547 11.0254 9.35547 10.5859C9.35547 10.4395 9.32617 10.3027 9.25781 10.1172L5.80078 0.810547C5.5957 0.263672 5.23438 0 4.66797 0C4.11133 0 3.75 0.263672 3.55469 0.810547L0.0976562 10.127C0.0292969 10.3125 0 10.4395 0 10.5957C0 11.0352 0.283203 11.3086 0.742188 11.3086ZM2.34375 8.16406L7.01172 8.16406C7.38281 8.16406 7.66602 7.87109 7.66602 7.50977C7.66602 7.13867 7.38281 6.85547 7.01172 6.85547L2.34375 6.85547C1.97266 6.85547 1.68945 7.13867 1.68945 7.50977C1.68945 7.87109 1.97266 8.16406 2.34375 8.16406Z&#34;&gt;
            &lt;/path&gt;
          &lt;/g&gt;
          &lt;text x=&#34;91&#34; y=&#34;453&#34;&gt;Text&lt;/text&gt;
        &lt;/g&gt;
        &lt;g class=&#34;ia-cell ia-shared-cell&#34; data-ia-modality=&#34;image video&#34; tabindex=&#34;0&#34;&gt;
          &lt;rect class=&#34;ia-box&#34; x=&#34;144&#34; y=&#34;433&#34; width=&#34;104&#34; height=&#34;40&#34; rx=&#34;7&#34; ry=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;g class=&#34;ia-input-icon ia-frame-icon&#34; aria-hidden=&#34;true&#34; transform=&#34;translate(167.6 446.8) scale(0.88)&#34;&gt;
            &lt;path
              d=&#34;M2.46094 13.6426L15.0293 13.6426C16.4551 13.6426 17.207 12.8516 17.207 11.2402L12.7246 7.00195C12.3926 6.69922 11.9824 6.54297 11.5918 6.54297C11.1816 6.54297 10.8008 6.68945 10.4492 6.99219L7.01172 10.0684L5.60547 8.80859C5.2832 8.52539 4.93164 8.37891 4.57031 8.37891C4.24805 8.37891 3.90625 8.51562 3.59375 8.79883L0.664062 11.4258C0.673828 12.8711 1.28906 13.6426 2.46094 13.6426ZM2.49023 14.248L15.625 14.248C17.2949 14.248 18.1152 13.4277 18.1152 11.7871L18.1152 2.4707C18.1152 0.830078 17.2949 0.00976562 15.625 0.00976562L2.49023 0.00976562C0.830078 0.00976562 0 0.830078 0 2.4707L0 11.7871C0 13.4277 0.830078 14.248 2.49023 14.248ZM2.50977 12.832C1.80664 12.832 1.40625 12.4512 1.40625 11.7188L1.40625 2.53906C1.40625 1.79688 1.80664 1.42578 2.50977 1.42578L15.6055 1.42578C16.3086 1.42578 16.709 1.79688 16.709 2.53906L16.709 11.7188C16.709 12.4512 16.3086 12.832 15.6055 12.832Z&#34;&gt;
            &lt;/path&gt;
            &lt;path
              d=&#34;M5.74219 7.17773C6.72852 7.17773 7.53906 6.36719 7.53906 5.37109C7.53906 4.38477 6.72852 3.56445 5.74219 3.56445C4.74609 3.56445 3.93555 4.38477 3.93555 5.37109C3.93555 6.36719 4.74609 7.17773 5.74219 7.17773Z&#34;&gt;
            &lt;/path&gt;
          &lt;/g&gt;
          &lt;text x=&#34;206&#34; y=&#34;453&#34;&gt;Frame&lt;/text&gt;
        &lt;/g&gt;
        &lt;g class=&#34;ia-cell&#34; data-ia-modality=&#34;audio&#34; tabindex=&#34;0&#34;&gt;
          &lt;rect class=&#34;ia-box&#34; x=&#34;258&#34; y=&#34;433&#34; width=&#34;104&#34; height=&#34;40&#34; rx=&#34;7&#34; ry=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;g class=&#34;ia-input-icon ia-audio-icon&#34; aria-hidden=&#34;true&#34; transform=&#34;translate(282.5 446.8)&#34;&gt;
            &lt;path
              d=&#34;M13.8086 7.74414C14.209 7.74414 14.5117 7.42188 14.5117 7.04102L14.5117 5.55664C14.5117 5.17578 14.209 4.84375 13.8086 4.84375C13.4082 4.84375 13.1055 5.17578 13.1055 5.55664L13.1055 7.04102C13.1055 7.42188 13.4082 7.74414 13.8086 7.74414Z&#34;&gt;
            &lt;/path&gt;
            &lt;path
              d=&#34;M11.1914 10.8203C11.582 10.8203 11.8945 10.498 11.8945 10.1172L11.8945 2.29492C11.8945 1.91406 11.582 1.58203 11.1914 1.58203C10.7812 1.58203 10.4883 1.91406 10.4883 2.29492L10.4883 10.1172C10.4883 10.498 10.7812 10.8203 11.1914 10.8203Z&#34;&gt;
            &lt;/path&gt;
            &lt;path
              d=&#34;M8.56445 8.59375C8.96484 8.59375 9.26758 8.29102 9.26758 7.90039L9.26758 4.51172C9.26758 4.12109 8.96484 3.80859 8.56445 3.80859C8.16406 3.80859 7.86133 4.12109 7.86133 4.51172L7.86133 7.90039C7.86133 8.29102 8.16406 8.59375 8.56445 8.59375Z&#34;&gt;
            &lt;/path&gt;
            &lt;path
              d=&#34;M5.9375 12.4121C6.33789 12.4121 6.65039 12.0898 6.65039 11.709L6.65039 0.703125C6.65039 0.322266 6.33789 0 5.9375 0C5.54688 0 5.24414 0.322266 5.24414 0.703125L5.24414 11.709C5.24414 12.0898 5.54688 12.4121 5.9375 12.4121Z&#34;&gt;
            &lt;/path&gt;
            &lt;path
              d=&#34;M3.32031 9.60938C3.7207 9.60938 4.0332 9.28711 4.0332 8.90625L4.0332 3.50586C4.0332 3.125 3.7207 2.79297 3.32031 2.79297C2.91992 2.79297 2.61719 3.125 2.61719 3.50586L2.61719 8.90625C2.61719 9.28711 2.91992 9.60938 3.32031 9.60938Z&#34;&gt;
            &lt;/path&gt;
            &lt;path
              d=&#34;M0.693359 7.90039C1.10352 7.90039 1.40625 7.57812 1.40625 7.1875L1.40625 5.22461C1.40625 4.83398 1.10352 4.50195 0.693359 4.50195C0.302734 4.50195 0 4.83398 0 5.22461L0 7.1875C0 7.57812 0.302734 7.90039 0.693359 7.90039Z&#34;&gt;
            &lt;/path&gt;
          &lt;/g&gt;
          &lt;text x=&#34;319&#34; y=&#34;453&#34;&gt;Audio&lt;/text&gt;
        &lt;/g&gt;
      &lt;/g&gt;

      &lt;g class=&#34;ia-layer ia-preprocess&#34;&gt;
        &lt;g class=&#34;ia-cell&#34; data-ia-modality=&#34;text&#34; tabindex=&#34;0&#34;&gt;
          &lt;rect class=&#34;ia-box ia-module&#34; x=&#34;30&#34; y=&#34;325&#34; width=&#34;104&#34; height=&#34;40&#34; rx=&#34;7&#34; ry=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;text x=&#34;82&#34; y=&#34;345&#34;&gt;Embedding&lt;/text&gt;
        &lt;/g&gt;
        &lt;g class=&#34;ia-cell&#34; data-ia-modality=&#34;text&#34; tabindex=&#34;0&#34;&gt;
          &lt;rect class=&#34;ia-box&#34; x=&#34;30&#34; y=&#34;379&#34; width=&#34;104&#34; height=&#34;40&#34; rx=&#34;7&#34; ry=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;text x=&#34;82&#34; y=&#34;399&#34;&gt;Tokens&lt;/text&gt;
        &lt;/g&gt;
        &lt;g class=&#34;ia-cell ia-shared-cell&#34; data-ia-modality=&#34;image video&#34; tabindex=&#34;0&#34;&gt;
          &lt;rect class=&#34;ia-box&#34; x=&#34;144&#34; y=&#34;379&#34; width=&#34;104&#34; height=&#34;40&#34; rx=&#34;7&#34; ry=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;text x=&#34;196&#34; y=&#34;399&#34;&gt;40x40 Patch&lt;/text&gt;
        &lt;/g&gt;
        &lt;g class=&#34;ia-cell ia-shared-cell&#34; data-ia-modality=&#34;image video&#34; tabindex=&#34;0&#34;&gt;
          &lt;rect class=&#34;ia-box ia-module&#34; x=&#34;144&#34; y=&#34;325&#34; width=&#34;104&#34; height=&#34;40&#34; rx=&#34;7&#34; ry=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;text x=&#34;196&#34; y=&#34;345&#34;&gt;hMLP&lt;/text&gt;
        &lt;/g&gt;
        &lt;g class=&#34;ia-cell&#34; data-ia-modality=&#34;audio&#34; tabindex=&#34;0&#34;&gt;
          &lt;rect class=&#34;ia-box&#34; x=&#34;258&#34; y=&#34;379&#34; width=&#34;104&#34; height=&#34;40&#34; rx=&#34;7&#34; ry=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;text x=&#34;310&#34; y=&#34;399&#34;&gt;dMel&lt;/text&gt;
        &lt;/g&gt;
        &lt;g class=&#34;ia-cell&#34; data-ia-modality=&#34;audio&#34; tabindex=&#34;0&#34;&gt;
          &lt;rect class=&#34;ia-box ia-module&#34; x=&#34;258&#34; y=&#34;325&#34; width=&#34;104&#34; height=&#34;40&#34; rx=&#34;7&#34; ry=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;foreignObject class=&#34;ia-foreign-label&#34; x=&#34;258&#34; y=&#34;325&#34; width=&#34;104&#34; height=&#34;40&#34;&gt;
            &lt;div xmlns=&#34;http://www.w3.org/1999/xhtml&#34; class=&#34;ia-two-line-label&#34;&gt;
              &lt;span&gt;Bag of&lt;/span&gt;
              &lt;span&gt;embeddings&lt;/span&gt;
            &lt;/div&gt;
          &lt;/foreignObject&gt;
        &lt;/g&gt;
      &lt;/g&gt;

      &lt;rect class=&#34;ia-box ia-transformer&#34; x=&#34;30&#34; y=&#34;247&#34; width=&#34;560&#34; height=&#34;40&#34; rx=&#34;7&#34; ry=&#34;7&#34;&gt;&lt;/rect&gt;
      &lt;text class=&#34;ia-transformer-label&#34; x=&#34;310&#34; y=&#34;267&#34;&gt;Transformer&lt;/text&gt;

      &lt;g class=&#34;ia-layer ia-outputs&#34;&gt;
        &lt;g class=&#34;ia-cell&#34; data-ia-modality=&#34;text&#34; tabindex=&#34;0&#34;&gt;
          &lt;rect class=&#34;ia-box&#34; x=&#34;372&#34; y=&#34;105&#34; width=&#34;104&#34; height=&#34;40&#34; rx=&#34;7&#34; ry=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;g class=&#34;ia-input-icon&#34; aria-hidden=&#34;true&#34; transform=&#34;translate(398.5 119.3)&#34;&gt;
            &lt;path
              d=&#34;M13.584 11.3086C14.6973 11.3086 15.752 10.7031 16.25 9.74609L16.2695 9.74609L16.2695 10.5957C16.2695 11.0254 16.5723 11.3184 16.9824 11.3184C17.4121 11.3184 17.7051 11.0254 17.7051 10.5566L17.7051 5.51758C17.7051 3.86719 16.4453 2.80273 14.4824 2.80273C13.0664 2.80273 11.8457 3.4082 11.3965 4.3457C11.2988 4.54102 11.2402 4.72656 11.2402 4.90234C11.2402 5.2832 11.5234 5.53711 11.8945 5.53711C12.168 5.53711 12.373 5.43945 12.5098 5.20508C12.9688 4.39453 13.5547 4.0625 14.4434 4.0625C15.5566 4.0625 16.2207 4.6582 16.2207 5.64453L16.2207 6.2793L13.8672 6.39648C11.9238 6.49414 10.8301 7.40234 10.8301 8.83789C10.8301 10.3223 11.9531 11.3086 13.584 11.3086ZM13.9551 10.0977C12.9785 10.0977 12.3438 9.58008 12.3438 8.80859C12.3438 8.05664 12.959 7.54883 14.043 7.49023L16.2207 7.35352L16.2207 8.04688C16.2207 9.20898 15.2148 10.0977 13.9551 10.0977Z&#34;&gt;
            &lt;/path&gt;
            &lt;path
              d=&#34;M0.742188 11.3086C1.20117 11.3086 1.43555 11.1328 1.60156 10.6445L4.63867 1.94336L4.6875 1.94336L7.73438 10.6445C7.90039 11.1328 8.13477 11.3086 8.59375 11.3086C9.05273 11.3086 9.35547 11.0254 9.35547 10.5859C9.35547 10.4395 9.32617 10.3027 9.25781 10.1172L5.80078 0.810547C5.5957 0.263672 5.23438 0 4.66797 0C4.11133 0 3.75 0.263672 3.55469 0.810547L0.0976562 10.127C0.0292969 10.3125 0 10.4395 0 10.5957C0 11.0352 0.283203 11.3086 0.742188 11.3086ZM2.34375 8.16406L7.01172 8.16406C7.38281 8.16406 7.66602 7.87109 7.66602 7.50977C7.66602 7.13867 7.38281 6.85547 7.01172 6.85547L2.34375 6.85547C1.97266 6.85547 1.68945 7.13867 1.68945 7.50977C1.68945 7.87109 1.97266 8.16406 2.34375 8.16406Z&#34;&gt;
            &lt;/path&gt;
          &lt;/g&gt;
          &lt;text x=&#34;433&#34; y=&#34;125&#34;&gt;Text&lt;/text&gt;
        &lt;/g&gt;
        &lt;g class=&#34;ia-cell&#34; data-ia-modality=&#34;text&#34; tabindex=&#34;0&#34;&gt;
          &lt;rect class=&#34;ia-box ia-module&#34; x=&#34;372&#34; y=&#34;169&#34; width=&#34;104&#34; height=&#34;40&#34; rx=&#34;7&#34; ry=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;text x=&#34;424&#34; y=&#34;189&#34;&gt;Unembedding&lt;/text&gt;
        &lt;/g&gt;

        &lt;g class=&#34;ia-cell&#34; data-ia-modality=&#34;audio&#34; tabindex=&#34;0&#34;&gt;
          &lt;rect class=&#34;ia-box&#34; x=&#34;486&#34; y=&#34;105&#34; width=&#34;104&#34; height=&#34;40&#34; rx=&#34;7&#34; ry=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;g class=&#34;ia-input-icon ia-audio-icon&#34; aria-hidden=&#34;true&#34; transform=&#34;translate(512 118.8)&#34;&gt;
            &lt;path
              d=&#34;M13.8086 7.74414C14.209 7.74414 14.5117 7.42188 14.5117 7.04102L14.5117 5.55664C14.5117 5.17578 14.209 4.84375 13.8086 4.84375C13.4082 4.84375 13.1055 5.17578 13.1055 5.55664L13.1055 7.04102C13.1055 7.42188 13.4082 7.74414 13.8086 7.74414Z&#34;&gt;
            &lt;/path&gt;
            &lt;path
              d=&#34;M11.1914 10.8203C11.582 10.8203 11.8945 10.498 11.8945 10.1172L11.8945 2.29492C11.8945 1.91406 11.582 1.58203 11.1914 1.58203C10.7812 1.58203 10.4883 1.91406 10.4883 2.29492L10.4883 10.1172C10.4883 10.498 10.7812 10.8203 11.1914 10.8203Z&#34;&gt;
            &lt;/path&gt;
            &lt;path
              d=&#34;M8.56445 8.59375C8.96484 8.59375 9.26758 8.29102 9.26758 7.90039L9.26758 4.51172C9.26758 4.12109 8.96484 3.80859 8.56445 3.80859C8.16406 3.80859 7.86133 4.12109 7.86133 4.51172L7.86133 7.90039C7.86133 8.29102 8.16406 8.59375 8.56445 8.59375Z&#34;&gt;
            &lt;/path&gt;
            &lt;path
              d=&#34;M5.9375 12.4121C6.33789 12.4121 6.65039 12.0898 6.65039 11.709L6.65039 0.703125C6.65039 0.322266 6.33789 0 5.9375 0C5.54688 0 5.24414 0.322266 5.24414 0.703125L5.24414 11.709C5.24414 12.0898 5.54688 12.4121 5.9375 12.4121Z&#34;&gt;
            &lt;/path&gt;
            &lt;path
              d=&#34;M3.32031 9.60938C3.7207 9.60938 4.0332 9.28711 4.0332 8.90625L4.0332 3.50586C4.0332 3.125 3.7207 2.79297 3.32031 2.79297C2.91992 2.79297 2.61719 3.125 2.61719 3.50586L2.61719 8.90625C2.61719 9.28711 2.91992 9.60938 3.32031 9.60938Z&#34;&gt;
            &lt;/path&gt;
            &lt;path
              d=&#34;M0.693359 7.90039C1.10352 7.90039 1.40625 7.57812 1.40625 7.1875L1.40625 5.22461C1.40625 4.83398 1.10352 4.50195 0.693359 4.50195C0.302734 4.50195 0 4.83398 0 5.22461L0 7.1875C0 7.57812 0.302734 7.90039 0.693359 7.90039Z&#34;&gt;
            &lt;/path&gt;
          &lt;/g&gt;
          &lt;text x=&#34;545&#34; y=&#34;125&#34;&gt;Mel&lt;/text&gt;
        &lt;/g&gt;
        &lt;g class=&#34;ia-cell&#34; data-ia-modality=&#34;audio&#34; tabindex=&#34;0&#34;&gt;
          &lt;rect class=&#34;ia-box ia-module&#34; x=&#34;486&#34; y=&#34;169&#34; width=&#34;104&#34; height=&#34;40&#34; rx=&#34;7&#34; ry=&#34;7&#34;&gt;&lt;/rect&gt;
          &lt;text x=&#34;538&#34; y=&#34;189&#34;&gt;Flow&lt;/text&gt;
        &lt;/g&gt;
      &lt;/g&gt;

      &lt;g class=&#34;ia-timespan&#34; aria-hidden=&#34;true&#34;&gt;
        &lt;path class=&#34;ia-timespan-line&#34; d=&#34;M 30 494 H 362&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-timespan-stop&#34; d=&#34;M 30 491 V 497&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-timespan-stop&#34; d=&#34;M 362 491 V 497&#34;&gt;&lt;/path&gt;
        &lt;text class=&#34;ia-timespan-label&#34; x=&#34;196&#34; y=&#34;506&#34;&gt;200ms&lt;/text&gt;
      &lt;/g&gt;

      &lt;g class=&#34;ia-timespan&#34; aria-hidden=&#34;true&#34;&gt;
        &lt;path class=&#34;ia-timespan-line&#34; d=&#34;M 372 84 H 590&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-timespan-stop&#34; d=&#34;M 372 81 V 87&#34;&gt;&lt;/path&gt;
        &lt;path class=&#34;ia-timespan-stop&#34; d=&#34;M 590 81 V 87&#34;&gt;&lt;/path&gt;
        &lt;text class=&#34;ia-timespan-label&#34; x=&#34;481&#34; y=&#34;73&#34;&gt;200ms&lt;/text&gt;
      &lt;/g&gt;
    &lt;/svg&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;An illustration of the interaction model architecture for a single 200ms micro-turn. The model takes in
    any subset of text, audio, or video and predicts text and audio.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;script src=&#34;https://thinkingmachines.ai/blog/interaction-models/js/figure-interfacer-architecture.js?v=20260514&#34; defer&gt;&lt;/script&gt;

&lt;p&gt;&lt;strong&gt;Inference optimization.&lt;/strong&gt; At inference time, 200ms chunks require frequent prefills and decodes of small sizes, each having to meet strict latency constraints. Unfortunately, existing LLM inference libraries are not optimized for frequent small prefills—they often have a significant amount of overhead per turn. To address this, we implemented streaming sessions. The client sends each 200ms chunk as a separate request, while the inference server appends these chunks into a persistent sequence in GPU memory. This avoids frequent memory reallocations and metadata computations, and we’ve upstreamed a &lt;a href=&#34;https://github.com/sgl-project/sglang/pull/19171&#34; target=&#34;_blank&#34;&gt;version of this feature&lt;/a&gt; to SGLang. In addition, we also optimized our kernels for latency as well as the shapes we see for bidirectional serving. For example, we use a gather+gemv strategy for MoE kernels instead of the standard grouped gemm, as in prior work from &lt;a href=&#34;https://www.thonking.ai/p/short-supporting-mixtral-in-gpt-fast&#34; target=&#34;_blank&#34;&gt;PyTorch&lt;/a&gt; and &lt;a href=&#34;https://cursor.com/blog/warp-decode&#34; target=&#34;_blank&#34;&gt;Cursor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trainer-sampler alignment.&lt;/strong&gt; We’ve found bitwise trainer-sampler alignment to be useful for training stability as well as debugging the various components of our system. We implement &lt;a href=&#34;https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/&#34; target=&#34;_blank&#34;&gt;batch-invariant kernels&lt;/a&gt; with minimal (&amp;lt;5%) e2e performance overhead.&lt;label for=&#34;sn-im-14&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-14&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Funnily enough, for some period of time using the batch-invariant kernels was actually faster e2e, due to the custom communication kernels which were not only batch-invariant but also much lower latency.&lt;/span&gt; To highlight two particular kernels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;All-reduce and reduce-scatter&lt;/strong&gt;: We use NVLS to implement low-latency comm kernels which are deterministic on Blackwell, and achieve bitwise alignment between somewhat different parallelism strategies (i.e. &lt;a href=&#34;https://arxiv.org/abs/2205.05198&#34; target=&#34;_blank&#34;&gt;Sequence Parallelism&lt;/a&gt; and Tensor Parallelism).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Attention&lt;/strong&gt;: The primary challenge with attention is Split-KV, which can typically lead to inconsistent accumulation orders between decode and prefill.&lt;label for=&#34;sn-im-15&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-15&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Work done in collaboration with Colfax&lt;/span&gt; However, we can maintain consistent accumulation order by choosing to split consistently between decode and prefill. For example, we could split SMs to process 4096 tokens at a time (left-aligned), achieving good efficiency in both prefill and decode.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Coordination between interaction and background models.&lt;/strong&gt; When the interaction model delegates, it sends a rich context package — not a standalone query, but the full conversation. Results stream back as the background model produces them, and the interaction model interleaves these updates into the conversation at a moment appropriate to what the user is currently doing, rather than as an abrupt context switch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Safety.&lt;/strong&gt; Because real-time interaction stresses safety differently than turn-based exchanges, our safety work focused on two axes: modality-appropriate refusals and long-horizon robustness. To make refusals colloquial in speech, we use a text-to-speech model to generate refusal and over-refusal training data covering a range of disallowed topics, with the refusal boundary calibrated to favor naturally-phrased, but no less firm, refusals. To improve robustness across extended speech-to-speech conversations, we used an automated red-teaming harness to generate multi-turn refusal data, while maintaining close behavioral parity with the model’s text-based refusals.&lt;/p&gt;
&lt;h2 id=&#34;benchmarks&#34;&gt;Benchmarks&lt;/h2&gt;
&lt;h3&gt;Intelligence and interactivity frontier&lt;/h3&gt;
&lt;p&gt;We show that our interaction model, named &lt;code&gt;TML-Interaction-Small&lt;/code&gt;, is the first model that has both strong intelligence/instruction following &lt;strong&gt;and&lt;/strong&gt; interactivity. To measure interaction quality we use FD-bench which is one of the few existing benchmarks intended to measure interactivity. In FD-bench v1.5, the model is given prerecorded audio, and must respond at certain times. This benchmark measures model behavior across several scenarios: user interruption, user backchannel, talking to others, and background speech. Our model scores well in all of these areas. To quantify intelligence we use Audio MultiChallenge, a common benchmark that tracks intelligence and instruction following.&lt;/p&gt;
&lt;figure class=&#34;intelligence-interactivity-figure&#34;&gt;
  &lt;div class=&#34;intelligence-interactivity-charts&#34;&gt;
    &lt;div class=&#34;intelligence-interactivity-chart&#34; role=&#34;img&#34;
      aria-label=&#34;Panel A. Scatter chart plotting Audio MultiChallenge intelligence against FD-bench v1.5 interaction quality for six speech-to-speech models.&#34;&gt;
      &lt;svg class=&#34;intelligence-interactivity-svg&#34; viewBox=&#34;0 0 960 535&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34;&gt;
        &lt;text class=&#34;ii-panel-title&#34; x=&#34;502&#34; y=&#34;30&#34;&gt;
          &lt;tspan x=&#34;502&#34;&gt;Intelligence (Audio MultiChallenge, APR) &lt;tspan font-style=&#34;italic&#34;&gt;vs.&lt;/tspan&gt; Interaction
            Quality&lt;/tspan&gt;
          &lt;tspan x=&#34;502&#34; dy=&#34;36&#34;&gt;(FD-bench v1.5, average quality)&lt;/tspan&gt;
        &lt;/text&gt;

        &lt;g class=&#34;ii-plot-body&#34; transform=&#34;translate(0 50)&#34;&gt;
          &lt;g class=&#34;ii-grid&#34;&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;42&#34; x2=&#34;92&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;183.11&#34; y1=&#34;42&#34; x2=&#34;183.11&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;274.22&#34; y1=&#34;42&#34; x2=&#34;274.22&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;365.33&#34; y1=&#34;42&#34; x2=&#34;365.33&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;456.44&#34; y1=&#34;42&#34; x2=&#34;456.44&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;547.56&#34; y1=&#34;42&#34; x2=&#34;547.56&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;638.67&#34; y1=&#34;42&#34; x2=&#34;638.67&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;729.78&#34; y1=&#34;42&#34; x2=&#34;729.78&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;820.89&#34; y1=&#34;42&#34; x2=&#34;820.89&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;912&#34; y1=&#34;42&#34; x2=&#34;912&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;42&#34; x2=&#34;912&#34; y2=&#34;42&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;51.11&#34; x2=&#34;912&#34; y2=&#34;51.11&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;96.67&#34; x2=&#34;912&#34; y2=&#34;96.67&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;142.22&#34; x2=&#34;912&#34; y2=&#34;142.22&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;187.78&#34; x2=&#34;912&#34; y2=&#34;187.78&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;233.33&#34; x2=&#34;912&#34; y2=&#34;233.33&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;278.89&#34; x2=&#34;912&#34; y2=&#34;278.89&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;324.44&#34; x2=&#34;912&#34; y2=&#34;324.44&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;370&#34; x2=&#34;912&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
          &lt;/g&gt;

          &lt;g class=&#34;ii-axis&#34;&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;370&#34; x2=&#34;912&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;42&#34; x2=&#34;92&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
          &lt;/g&gt;

          &lt;g class=&#34;ii-ticks ii-x-ticks&#34;&gt;
            &lt;text x=&#34;92&#34; y=&#34;402&#34;&gt;40&lt;/text&gt;
            &lt;text x=&#34;183.11&#34; y=&#34;402&#34;&gt;45&lt;/text&gt;
            &lt;text x=&#34;274.22&#34; y=&#34;402&#34;&gt;50&lt;/text&gt;
            &lt;text x=&#34;365.33&#34; y=&#34;402&#34;&gt;55&lt;/text&gt;
            &lt;text x=&#34;456.44&#34; y=&#34;402&#34;&gt;60&lt;/text&gt;
            &lt;text x=&#34;547.56&#34; y=&#34;402&#34;&gt;65&lt;/text&gt;
            &lt;text x=&#34;638.67&#34; y=&#34;402&#34;&gt;70&lt;/text&gt;
            &lt;text x=&#34;729.78&#34; y=&#34;402&#34;&gt;75&lt;/text&gt;
            &lt;text x=&#34;820.89&#34; y=&#34;402&#34;&gt;80&lt;/text&gt;
            &lt;text x=&#34;912&#34; y=&#34;402&#34;&gt;85&lt;/text&gt;
          &lt;/g&gt;

          &lt;g class=&#34;ii-ticks ii-y-ticks&#34;&gt;
            &lt;text x=&#34;76&#34; y=&#34;376&#34;&gt;20&lt;/text&gt;
            &lt;text x=&#34;76&#34; y=&#34;284.89&#34;&gt;30&lt;/text&gt;
            &lt;text x=&#34;76&#34; y=&#34;193.78&#34;&gt;40&lt;/text&gt;
            &lt;text x=&#34;76&#34; y=&#34;102.67&#34;&gt;50&lt;/text&gt;
          &lt;/g&gt;

          &lt;text class=&#34;ii-axis-label ii-x-label&#34; x=&#34;502&#34; y=&#34;442&#34;&gt;Interaction Quality →&lt;/text&gt;
          &lt;text class=&#34;ii-axis-label ii-y-label&#34; x=&#34;-206&#34; y=&#34;32&#34; transform=&#34;rotate(-90)&#34;&gt;Intelligence →&lt;/text&gt;

          &lt;g class=&#34;ii-error-bars&#34;&gt;
            &lt;g class=&#34;ii-error-bar&#34; style=&#34;--ii-error-color: #be2121;&#34;&gt;
              &lt;line x1=&#34;784.44&#34; y1=&#34;118.44&#34; x2=&#34;784.44&#34; y2=&#34;195.90&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;779.44&#34; y1=&#34;118.44&#34; x2=&#34;789.44&#34; y2=&#34;118.44&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;779.44&#34; y1=&#34;195.90&#34; x2=&#34;789.44&#34; y2=&#34;195.90&#34;&gt;&lt;/line&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-error-bar&#34; style=&#34;--ii-error-color: #5BA3E8;&#34;&gt;
              &lt;line x1=&#34;237.78&#34; y1=&#34;68.97&#34; x2=&#34;237.78&#34; y2=&#34;152.61&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;232.78&#34; y1=&#34;68.97&#34; x2=&#34;242.78&#34; y2=&#34;68.97&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;232.78&#34; y1=&#34;152.61&#34; x2=&#34;242.78&#34; y2=&#34;152.61&#34;&gt;&lt;/line&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-error-bar&#34; style=&#34;--ii-error-color: #5BA3E8;&#34;&gt;
              &lt;line x1=&#34;219.56&#34; y1=&#34;169.01&#34; x2=&#34;219.56&#34; y2=&#34;250.10&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;214.56&#34; y1=&#34;169.01&#34; x2=&#34;224.56&#34; y2=&#34;169.01&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;214.56&#34; y1=&#34;250.10&#34; x2=&#34;224.56&#34; y2=&#34;250.10&#34;&gt;&lt;/line&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-error-bar&#34; style=&#34;--ii-error-color: #5BA3E8;&#34;&gt;
              &lt;line x1=&#34;246.89&#34; y1=&#34;195.90&#34; x2=&#34;246.89&#34; y2=&#34;275.71&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;241.89&#34; y1=&#34;195.90&#34; x2=&#34;251.89&#34; y2=&#34;195.90&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;241.89&#34; y1=&#34;275.71&#34; x2=&#34;251.89&#34; y2=&#34;275.71&#34;&gt;&lt;/line&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-error-bar&#34; style=&#34;--ii-error-color: #0F6E56;&#34;&gt;
              &lt;line x1=&#34;183.11&#34; y1=&#34;183.50&#34; x2=&#34;183.11&#34; y2=&#34;263.86&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;178.11&#34; y1=&#34;183.50&#34; x2=&#34;188.11&#34; y2=&#34;183.50&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;178.11&#34; y1=&#34;263.86&#34; x2=&#34;188.11&#34; y2=&#34;263.86&#34;&gt;&lt;/line&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-error-bar&#34; style=&#34;--ii-error-color: #0F6E56;&#34;&gt;
              &lt;line x1=&#34;465.56&#34; y1=&#34;271.34&#34; x2=&#34;465.56&#34; y2=&#34;345.31&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;460.56&#34; y1=&#34;271.34&#34; x2=&#34;470.56&#34; y2=&#34;271.34&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;460.56&#34; y1=&#34;345.31&#34; x2=&#34;470.56&#34; y2=&#34;345.31&#34;&gt;&lt;/line&gt;
            &lt;/g&gt;
          &lt;/g&gt;

          &lt;g class=&#34;ii-points&#34;&gt;
            &lt;g class=&#34;ii-point&#34; transform=&#34;translate(784.44 157.16)&#34; data-ii-tooltip-title=&#34;TML-interaction-small&#34;
              data-ii-tooltip-values=&#34;Interaction quality 78.0 | Intelligence 43.4&#34;&gt;
              &lt;g class=&#34;ii-point-marker-shell&#34;&gt;
                &lt;circle class=&#34;ii-point-marker&#34; r=&#34;8.5&#34; fill=&#34;#be2121&#34;&gt;&lt;/circle&gt;
              &lt;/g&gt;
              &lt;circle class=&#34;ii-point-hit-area&#34; r=&#34;22&#34;&gt;&lt;/circle&gt;
              &lt;text x=&#34;-18&#34; y=&#34;5&#34; text-anchor=&#34;end&#34;&gt;TML-small&lt;/text&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-point&#34; transform=&#34;translate(237.78 110.79)&#34; data-ii-tooltip-title=&#34;GPT-realtime-2.0 (xhigh)&#34;
              data-ii-tooltip-values=&#34;Interaction quality 48.0 | Intelligence 48.5&#34;&gt;
              &lt;g class=&#34;ii-point-marker-shell&#34;&gt;
                &lt;path class=&#34;ii-point-marker&#34; d=&#34;M 0 -10 L 9 8 L -9 8 Z&#34; fill=&#34;#5BA3E8&#34;&gt;&lt;/path&gt;
              &lt;/g&gt;
              &lt;circle class=&#34;ii-point-hit-area&#34; r=&#34;22&#34;&gt;&lt;/circle&gt;
              &lt;text x=&#34;18&#34; y=&#34;5&#34; text-anchor=&#34;start&#34;&gt;GPT-2.0 xhigh&lt;/text&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-point&#34; transform=&#34;translate(219.56 209.56)&#34; data-ii-tooltip-title=&#34;GPT-realtime-2.0 (minimal)&#34;
              data-ii-tooltip-values=&#34;Interaction quality 47.0 | Intelligence 37.6&#34;&gt;
              &lt;g class=&#34;ii-point-marker-shell&#34;&gt;
                &lt;circle class=&#34;ii-point-marker&#34; r=&#34;8.5&#34; fill=&#34;#5BA3E8&#34;&gt;&lt;/circle&gt;
              &lt;/g&gt;
              &lt;circle class=&#34;ii-point-hit-area&#34; r=&#34;22&#34;&gt;&lt;/circle&gt;
              &lt;text x=&#34;18&#34; y=&#34;5&#34; text-anchor=&#34;start&#34;&gt;GPT-2.0 min&lt;/text&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-point&#34; transform=&#34;translate(246.89 235.80)&#34; data-ii-tooltip-title=&#34;GPT-realtime-1.5&#34;
              data-ii-tooltip-values=&#34;Interaction quality 48.5 | Intelligence 34.7&#34;&gt;
              &lt;g class=&#34;ii-point-marker-shell&#34;&gt;
                &lt;circle class=&#34;ii-point-marker&#34; r=&#34;8.5&#34; fill=&#34;#5BA3E8&#34;&gt;&lt;/circle&gt;
              &lt;/g&gt;
              &lt;circle class=&#34;ii-point-hit-area&#34; r=&#34;22&#34;&gt;&lt;/circle&gt;
              &lt;text x=&#34;0&#34; y=&#34;40&#34; text-anchor=&#34;middle&#34;&gt;GPT-1.5&lt;/text&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-point&#34; transform=&#34;translate(183.11 223.68)&#34;
              data-ii-tooltip-title=&#34;Gemini-3.1-flash-live-preview (high)&#34;
              data-ii-tooltip-values=&#34;Interaction quality 45.0 | Intelligence 36.1&#34;&gt;
              &lt;g class=&#34;ii-point-marker-shell&#34;&gt;
                &lt;path class=&#34;ii-point-marker&#34; d=&#34;M 0 -10 L 9 8 L -9 8 Z&#34; fill=&#34;#0F6E56&#34;&gt;&lt;/path&gt;
              &lt;/g&gt;
              &lt;circle class=&#34;ii-point-hit-area&#34; r=&#34;22&#34;&gt;&lt;/circle&gt;
              &lt;text x=&#34;-18&#34; y=&#34;5&#34; text-anchor=&#34;end&#34;&gt;Gemini high&lt;/text&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-point&#34; transform=&#34;translate(465.56 308.32)&#34;
              data-ii-tooltip-title=&#34;Gemini-3.1-flash-live-preview (minimal)&#34;
              data-ii-tooltip-values=&#34;Interaction quality 60.5 | Intelligence 26.8&#34;&gt;
              &lt;g class=&#34;ii-point-marker-shell&#34;&gt;
                &lt;circle class=&#34;ii-point-marker&#34; r=&#34;8.5&#34; fill=&#34;#0F6E56&#34;&gt;&lt;/circle&gt;
              &lt;/g&gt;
              &lt;circle class=&#34;ii-point-hit-area&#34; r=&#34;22&#34;&gt;&lt;/circle&gt;
              &lt;text x=&#34;0&#34; y=&#34;40&#34; text-anchor=&#34;middle&#34;&gt;Gemini min&lt;/text&gt;
            &lt;/g&gt;
          &lt;/g&gt;
        &lt;/g&gt;
      &lt;/svg&gt;
    &lt;/div&gt;

    &lt;div class=&#34;intelligence-interactivity-chart&#34; role=&#34;img&#34;
      aria-label=&#34;Panel B. Scatter chart plotting Audio MultiChallenge intelligence against turn-taking latency in seconds for six speech-to-speech models. The x-axis is reversed so faster models appear farther right.&#34;&gt;
      &lt;svg class=&#34;intelligence-interactivity-svg&#34; viewBox=&#34;0 0 960 535&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34;&gt;
        &lt;text class=&#34;ii-panel-title&#34; x=&#34;502&#34; y=&#34;30&#34;&gt;
          &lt;tspan x=&#34;502&#34;&gt;Intelligence (Audio MultiChallenge, APR) &lt;tspan font-style=&#34;italic&#34;&gt;vs.&lt;/tspan&gt; Responsiveness
          &lt;/tspan&gt;
          &lt;tspan x=&#34;502&#34; dy=&#34;36&#34;&gt;(FD-bench v1, simple turn-taking latency)&lt;/tspan&gt;
        &lt;/text&gt;

        &lt;g class=&#34;ii-plot-body&#34; transform=&#34;translate(0 50)&#34;&gt;
          &lt;g class=&#34;ii-grid&#34;&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;42&#34; x2=&#34;92&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;284.94&#34; y1=&#34;42&#34; x2=&#34;284.94&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;526.12&#34; y1=&#34;42&#34; x2=&#34;526.12&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;767.29&#34; y1=&#34;42&#34; x2=&#34;767.29&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;912&#34; y1=&#34;42&#34; x2=&#34;912&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;42&#34; x2=&#34;912&#34; y2=&#34;42&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;51.11&#34; x2=&#34;912&#34; y2=&#34;51.11&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;96.67&#34; x2=&#34;912&#34; y2=&#34;96.67&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;142.22&#34; x2=&#34;912&#34; y2=&#34;142.22&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;187.78&#34; x2=&#34;912&#34; y2=&#34;187.78&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;233.33&#34; x2=&#34;912&#34; y2=&#34;233.33&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;278.89&#34; x2=&#34;912&#34; y2=&#34;278.89&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;324.44&#34; x2=&#34;912&#34; y2=&#34;324.44&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;370&#34; x2=&#34;912&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
          &lt;/g&gt;

          &lt;g class=&#34;ii-axis&#34;&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;370&#34; x2=&#34;912&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
            &lt;line x1=&#34;92&#34; y1=&#34;42&#34; x2=&#34;92&#34; y2=&#34;370&#34;&gt;&lt;/line&gt;
          &lt;/g&gt;

          &lt;g class=&#34;ii-ticks ii-x-ticks&#34;&gt;
            &lt;text x=&#34;912&#34; y=&#34;402&#34;&gt;0.2&lt;/text&gt;
            &lt;text x=&#34;767.29&#34; y=&#34;402&#34;&gt;0.5&lt;/text&gt;
            &lt;text x=&#34;526.12&#34; y=&#34;402&#34;&gt;1.0&lt;/text&gt;
            &lt;text x=&#34;284.94&#34; y=&#34;402&#34;&gt;1.5&lt;/text&gt;
            &lt;text x=&#34;92&#34; y=&#34;402&#34;&gt;1.9&lt;/text&gt;
          &lt;/g&gt;

          &lt;g class=&#34;ii-ticks ii-y-ticks&#34;&gt;
            &lt;text x=&#34;76&#34; y=&#34;376&#34;&gt;20&lt;/text&gt;
            &lt;text x=&#34;76&#34; y=&#34;284.89&#34;&gt;30&lt;/text&gt;
            &lt;text x=&#34;76&#34; y=&#34;193.78&#34;&gt;40&lt;/text&gt;
            &lt;text x=&#34;76&#34; y=&#34;102.67&#34;&gt;50&lt;/text&gt;
          &lt;/g&gt;

          &lt;text class=&#34;ii-axis-label ii-x-label&#34; x=&#34;502&#34; y=&#34;442&#34;&gt;Responsiveness (s) →&lt;/text&gt;
          &lt;text class=&#34;ii-axis-label ii-y-label&#34; x=&#34;-206&#34; y=&#34;32&#34; transform=&#34;rotate(-90)&#34;&gt;Intelligence →&lt;/text&gt;

          &lt;g class=&#34;ii-error-bars&#34;&gt;
            &lt;g class=&#34;ii-error-bar&#34; style=&#34;--ii-error-color: #be2121;&#34;&gt;
              &lt;line x1=&#34;815.53&#34; y1=&#34;118.44&#34; x2=&#34;815.53&#34; y2=&#34;195.90&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;810.53&#34; y1=&#34;118.44&#34; x2=&#34;820.53&#34; y2=&#34;118.44&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;810.53&#34; y1=&#34;195.90&#34; x2=&#34;820.53&#34; y2=&#34;195.90&#34;&gt;&lt;/line&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-error-bar&#34; style=&#34;--ii-error-color: #5BA3E8;&#34;&gt;
              &lt;line x1=&#34;212.59&#34; y1=&#34;68.97&#34; x2=&#34;212.59&#34; y2=&#34;152.61&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;207.59&#34; y1=&#34;68.97&#34; x2=&#34;217.59&#34; y2=&#34;68.97&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;207.59&#34; y1=&#34;152.61&#34; x2=&#34;217.59&#34; y2=&#34;152.61&#34;&gt;&lt;/line&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-error-bar&#34; style=&#34;--ii-error-color: #5BA3E8;&#34;&gt;
              &lt;line x1=&#34;439.29&#34; y1=&#34;169.01&#34; x2=&#34;439.29&#34; y2=&#34;250.10&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;434.29&#34; y1=&#34;169.01&#34; x2=&#34;444.29&#34; y2=&#34;169.01&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;434.29&#34; y1=&#34;250.10&#34; x2=&#34;444.29&#34; y2=&#34;250.10&#34;&gt;&lt;/line&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-error-bar&#34; style=&#34;--ii-error-color: #5BA3E8;&#34;&gt;
              &lt;line x1=&#34;719.06&#34; y1=&#34;195.90&#34; x2=&#34;719.06&#34; y2=&#34;275.71&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;714.06&#34; y1=&#34;195.90&#34; x2=&#34;724.06&#34; y2=&#34;195.90&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;714.06&#34; y1=&#34;275.71&#34; x2=&#34;724.06&#34; y2=&#34;275.71&#34;&gt;&lt;/line&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-error-bar&#34; style=&#34;--ii-error-color: #0F6E56;&#34;&gt;
              &lt;line x1=&#34;284.94&#34; y1=&#34;183.50&#34; x2=&#34;284.94&#34; y2=&#34;263.86&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;279.94&#34; y1=&#34;183.50&#34; x2=&#34;289.94&#34; y2=&#34;183.50&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;279.94&#34; y1=&#34;263.86&#34; x2=&#34;289.94&#34; y2=&#34;263.86&#34;&gt;&lt;/line&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-error-bar&#34; style=&#34;--ii-error-color: #0F6E56;&#34;&gt;
              &lt;line x1=&#34;550.24&#34; y1=&#34;271.34&#34; x2=&#34;550.24&#34; y2=&#34;345.31&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;545.24&#34; y1=&#34;271.34&#34; x2=&#34;555.24&#34; y2=&#34;271.34&#34;&gt;&lt;/line&gt;
              &lt;line x1=&#34;545.24&#34; y1=&#34;345.31&#34; x2=&#34;555.24&#34; y2=&#34;345.31&#34;&gt;&lt;/line&gt;
            &lt;/g&gt;
          &lt;/g&gt;

          &lt;g class=&#34;ii-points&#34;&gt;
            &lt;g class=&#34;ii-point&#34; transform=&#34;translate(815.53 157.16)&#34; data-ii-tooltip-title=&#34;TML-interaction-small&#34;
              data-ii-tooltip-values=&#34;Responsiveness 0.40s | Intelligence 43.4&#34;&gt;
              &lt;g class=&#34;ii-point-marker-shell&#34;&gt;
                &lt;circle class=&#34;ii-point-marker&#34; r=&#34;8.5&#34; fill=&#34;#be2121&#34;&gt;&lt;/circle&gt;
              &lt;/g&gt;
              &lt;circle class=&#34;ii-point-hit-area&#34; r=&#34;22&#34;&gt;&lt;/circle&gt;
              &lt;text x=&#34;-18&#34; y=&#34;5&#34; text-anchor=&#34;end&#34;&gt;TML-small&lt;/text&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-point&#34; transform=&#34;translate(212.59 110.79)&#34; data-ii-tooltip-title=&#34;GPT-realtime-2.0 (xhigh)&#34;
              data-ii-tooltip-values=&#34;Responsiveness 1.65s | Intelligence 48.5&#34;&gt;
              &lt;g class=&#34;ii-point-marker-shell&#34;&gt;
                &lt;path class=&#34;ii-point-marker&#34; d=&#34;M 0 -10 L 9 8 L -9 8 Z&#34; fill=&#34;#5BA3E8&#34;&gt;&lt;/path&gt;
              &lt;/g&gt;
              &lt;circle class=&#34;ii-point-hit-area&#34; r=&#34;22&#34;&gt;&lt;/circle&gt;
              &lt;text x=&#34;18&#34; y=&#34;5&#34; text-anchor=&#34;start&#34;&gt;GPT-2.0 xhigh&lt;/text&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-point&#34; transform=&#34;translate(439.29 209.56)&#34; data-ii-tooltip-title=&#34;GPT-realtime-2.0 (minimal)&#34;
              data-ii-tooltip-values=&#34;Responsiveness 1.18s | Intelligence 37.6&#34;&gt;
              &lt;g class=&#34;ii-point-marker-shell&#34;&gt;
                &lt;circle class=&#34;ii-point-marker&#34; r=&#34;8.5&#34; fill=&#34;#5BA3E8&#34;&gt;&lt;/circle&gt;
              &lt;/g&gt;
              &lt;circle class=&#34;ii-point-hit-area&#34; r=&#34;22&#34;&gt;&lt;/circle&gt;
              &lt;text x=&#34;18&#34; y=&#34;5&#34; text-anchor=&#34;start&#34;&gt;GPT-2.0 min&lt;/text&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-point&#34; transform=&#34;translate(719.06 235.80)&#34; data-ii-tooltip-title=&#34;GPT-realtime-1.5&#34;
              data-ii-tooltip-values=&#34;Responsiveness 0.60s | Intelligence 34.7&#34;&gt;
              &lt;g class=&#34;ii-point-marker-shell&#34;&gt;
                &lt;circle class=&#34;ii-point-marker&#34; r=&#34;8.5&#34; fill=&#34;#5BA3E8&#34;&gt;&lt;/circle&gt;
              &lt;/g&gt;
              &lt;circle class=&#34;ii-point-hit-area&#34; r=&#34;22&#34;&gt;&lt;/circle&gt;
              &lt;text x=&#34;0&#34; y=&#34;-36&#34; text-anchor=&#34;middle&#34;&gt;GPT-1.5&lt;/text&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-point&#34; transform=&#34;translate(284.94 223.68)&#34;
              data-ii-tooltip-title=&#34;Gemini-3.1-flash-live-preview (high)&#34;
              data-ii-tooltip-values=&#34;Responsiveness 1.50s | Intelligence 36.1&#34;&gt;
              &lt;g class=&#34;ii-point-marker-shell&#34;&gt;
                &lt;path class=&#34;ii-point-marker&#34; d=&#34;M 0 -10 L 9 8 L -9 8 Z&#34; fill=&#34;#0F6E56&#34;&gt;&lt;/path&gt;
              &lt;/g&gt;
              &lt;circle class=&#34;ii-point-hit-area&#34; r=&#34;22&#34;&gt;&lt;/circle&gt;
              &lt;text x=&#34;18&#34; y=&#34;5&#34; text-anchor=&#34;start&#34;&gt;Gemini high&lt;/text&gt;
            &lt;/g&gt;
            &lt;g class=&#34;ii-point&#34; transform=&#34;translate(550.24 308.32)&#34;
              data-ii-tooltip-title=&#34;Gemini-3.1-flash-live-preview (minimal)&#34;
              data-ii-tooltip-values=&#34;Responsiveness 0.95s | Intelligence 26.8&#34;&gt;
              &lt;g class=&#34;ii-point-marker-shell&#34;&gt;
                &lt;circle class=&#34;ii-point-marker&#34; r=&#34;8.5&#34; fill=&#34;#0F6E56&#34;&gt;&lt;/circle&gt;
              &lt;/g&gt;
              &lt;circle class=&#34;ii-point-hit-area&#34; r=&#34;22&#34;&gt;&lt;/circle&gt;
              &lt;text x=&#34;0&#34; y=&#34;40&#34; text-anchor=&#34;middle&#34;&gt;Gemini min&lt;/text&gt;
            &lt;/g&gt;
          &lt;/g&gt;
        &lt;/g&gt;
      &lt;/svg&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&#34;intelligence-interactivity-legend&#34; aria-label=&#34;Model legend&#34;&gt;
    &lt;span class=&#34;ii-legend-item&#34;&gt;&lt;span class=&#34;ii-legend-marker ii-marker-circle&#34;
        style=&#34;--ii-marker-color: #be2121;&#34;&gt;&lt;/span&gt;TML-interaction-small&lt;/span&gt;
    &lt;span class=&#34;ii-legend-item&#34;&gt;&lt;span class=&#34;ii-legend-marker ii-marker-circle&#34;
        style=&#34;--ii-marker-color: #5BA3E8;&#34;&gt;&lt;/span&gt;GPT-realtime-2.0 (minimal)&lt;/span&gt;
    &lt;span class=&#34;ii-legend-item&#34;&gt;&lt;span class=&#34;ii-legend-marker ii-marker-triangle&#34;
        style=&#34;--ii-marker-color: #5BA3E8;&#34;&gt;&lt;/span&gt;GPT-realtime-2.0 (xhigh)&lt;/span&gt;
    &lt;span class=&#34;ii-legend-item&#34;&gt;&lt;span class=&#34;ii-legend-marker ii-marker-circle&#34;
        style=&#34;--ii-marker-color: #5BA3E8;&#34;&gt;&lt;/span&gt;GPT-realtime-1.5&lt;/span&gt;
    &lt;span class=&#34;ii-legend-item&#34;&gt;&lt;span class=&#34;ii-legend-marker ii-marker-circle&#34;
        style=&#34;--ii-marker-color: #0F6E56;&#34;&gt;&lt;/span&gt;Gemini-3.1-flash-live-preview (minimal)&lt;/span&gt;
    &lt;span class=&#34;ii-legend-item&#34;&gt;&lt;span class=&#34;ii-legend-marker ii-marker-triangle&#34;
        style=&#34;--ii-marker-color: #0F6E56;&#34;&gt;&lt;/span&gt;Gemini-3.1-flash-live-preview (high)&lt;/span&gt;
  &lt;/div&gt;


  &lt;figcaption&gt;
    Intelligence and Interactivity Frontier. Our model dominates interaction quality while being more
    intelligent than any non thinking model. We achieve the best responsiveness measured as a latency between user and
    model turns.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;script&gt;
  (() =&gt; {
    const figures = Array.from(document.querySelectorAll(&#39;.intelligence-interactivity-figure&#39;));
    if (figures.length === 0) return;

    const loadScript = () =&gt; {
      if (window.__figureintelligenceinteractivityLoading) return;
      window.__figureintelligenceinteractivityLoading = true;
      const script = document.createElement(&#39;script&#39;);
      script.src = &#39;/blog/interaction-models/js/figure-intelligence-interactivity.js?v=20260514&#39;;
      script.async = true;
      document.head.appendChild(script);
    };

    figures.forEach((figure) =&gt; {
      figure.addEventListener(&#39;pointerenter&#39;, loadScript, { once: true });
      figure.addEventListener(&#39;focusin&#39;, loadScript, { once: true });
    });

    if (&#39;IntersectionObserver&#39; in window) {
      const observer = new IntersectionObserver((entries) =&gt; {
        if (entries.some((entry) =&gt; entry.isIntersecting)) {
          loadScript();
          observer.disconnect();
        }
      }, { rootMargin: &#39;900px 0px&#39;, threshold: 0 });

      figures.forEach((figure) =&gt; observer.observe(figure));
      return;
    }

    const schedule = window.requestIdleCallback || ((callback) =&gt; window.setTimeout(callback, 1));
    schedule(loadScript, { timeout: 2000 });
  })();
&lt;/script&gt;

&lt;p&gt;For more intelligence, safety, and interactivity/latency results please see the table below. We report our performance on both streaming and turn-based benchmarks.&lt;/p&gt;
&lt;div class=&#34;benchmark-results-controls&#34; role=&#34;group&#34; aria-label=&#34;Benchmark table controls&#34;&gt;
  &lt;button class=&#34;benchmark-results-control&#34; type=&#34;button&#34; data-benchmark-scroll=&#34;left&#34; aria-label=&#34;Scroll benchmark table left&#34; title=&#34;Scroll table left&#34;&gt;
    &lt;svg class=&#34;benchmark-results-control-icon&#34; viewBox=&#34;0 0 24 24&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34;&gt;
      &lt;path d=&#34;M15 18 L9 12 L15 6&#34;&gt;&lt;/path&gt;
    &lt;/svg&gt;
  &lt;/button&gt;
  &lt;button class=&#34;benchmark-results-control&#34; type=&#34;button&#34; data-benchmark-scroll=&#34;right&#34; aria-label=&#34;Scroll benchmark table right&#34; title=&#34;Scroll table right&#34;&gt;
    &lt;svg class=&#34;benchmark-results-control-icon&#34; viewBox=&#34;0 0 24 24&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34;&gt;
      &lt;path d=&#34;M9 6 L15 12 L9 18&#34;&gt;&lt;/path&gt;
    &lt;/svg&gt;
  &lt;/button&gt;
&lt;/div&gt;
&lt;div class=&#34;benchmark-results-scroll&#34;&gt;
  &lt;table class=&#34;benchmark-results-table&#34;&gt;
    &lt;thead&gt;
      &lt;tr class=&#34;benchmark-model-group-row&#34;&gt;
        &lt;td class=&#34;benchmark-heading-spacer&#34; colspan=&#34;2&#34; aria-hidden=&#34;true&#34;&gt;&lt;/td&gt;
        &lt;th class=&#34;benchmark-model-group benchmark-instant-start&#34; colspan=&#34;5&#34;&gt;Instant&lt;/th&gt;
        &lt;th class=&#34;benchmark-model-group benchmark-thinking-start&#34; colspan=&#34;2&#34;&gt;Thinking&lt;/th&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td class=&#34;benchmark-heading-spacer&#34; colspan=&#34;2&#34; aria-hidden=&#34;true&#34;&gt;&lt;/td&gt;
        &lt;th class=&#34;benchmark-model benchmark-instant-start&#34;&gt;&lt;span class=&#34;benchmark-lock-content&#34;&gt;TML-interaction&lt;br&gt;-small&lt;/span&gt;&lt;/th&gt;
        &lt;th class=&#34;benchmark-model&#34;&gt;GPT-realtime-2.0&lt;br&gt;(minimal)&lt;/th&gt;
        &lt;th class=&#34;benchmark-model&#34;&gt;GPT-realtime-1.5&lt;/th&gt;
        &lt;th class=&#34;benchmark-model&#34;&gt;Gemini-3.1-flash-live&lt;br&gt;(minimal)&lt;/th&gt;
        &lt;th class=&#34;benchmark-model&#34;&gt;Qwen 3.5&lt;br&gt;OMNI-plus-realtime&lt;/th&gt;
        &lt;th class=&#34;benchmark-model benchmark-thinking-start&#34;&gt;GPT-realtime-2.0&lt;br&gt;(xhigh)&lt;/th&gt;
        &lt;th class=&#34;benchmark-model&#34;&gt;Gemini-3.1-flash-live&lt;br&gt;(high)&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr class=&#34;benchmark-section-streaming&#34;&gt;
        &lt;td class=&#34;benchmark-group benchmark-group-streaming&#34; rowspan=&#34;4&#34;&gt;&lt;span class=&#34;benchmark-group-label&#34;&gt;Streaming&lt;/span&gt;&lt;/td&gt;
        &lt;td class=&#34;benchmark-name&#34;&gt;
          &lt;span class=&#34;benchmark-title&#34;&gt;FD-bench V1&lt;/span&gt;
          &lt;span class=&#34;benchmark-subtitle&#34;&gt;Turn-taking latency (s) · Audio&lt;/span&gt;
        &lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-instant-start benchmark-value-best&#34;&gt;&lt;span class=&#34;benchmark-lock-content&#34;&gt;0.40&lt;/span&gt;&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;1.18&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;0.59&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;0.57&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;2.14&lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-thinking-start&#34;&gt;1.63&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;0.94&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr class=&#34;benchmark-section-streaming&#34;&gt;
        &lt;td class=&#34;benchmark-name&#34;&gt;
          &lt;span class=&#34;benchmark-title&#34;&gt;FD-bench V1.5&lt;/span&gt;
          &lt;span class=&#34;benchmark-subtitle&#34;&gt;Average · Audio&lt;/span&gt;
        &lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-instant-start benchmark-value-best&#34;&gt;&lt;span class=&#34;benchmark-lock-content&#34;&gt;77.8&lt;/span&gt;&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;46.8&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;48.3&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;54.3&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;39.0&lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-thinking-start&#34;&gt;47.8&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;45.5&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr class=&#34;benchmark-section-streaming&#34;&gt;
        &lt;td class=&#34;benchmark-name&#34;&gt;
          &lt;span class=&#34;benchmark-title&#34;&gt;FD-bench V3&lt;/span&gt;
          &lt;span class=&#34;benchmark-subtitle&#34;&gt;Response Quality (%) / &lt;br/&gt;Pass@1 (%) · Audio + Tools&lt;/span&gt;
        &lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-instant-start benchmark-value-best&#34;&gt;&lt;span class=&#34;benchmark-lock-content&#34;&gt;82.8&lt;sup&gt;*&lt;/sup&gt; / 68.0&lt;sup&gt;*&lt;/sup&gt;&lt;/span&gt;&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;80.0 / 52.0&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;77.9 / 55.0&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;68.5 / 48.0&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;60.0 / 50.0&lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-thinking-start&#34;&gt;81.0 / 58.0&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;71.4 / 48.0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr class=&#34;benchmark-section-streaming&#34;&gt;
        &lt;td class=&#34;benchmark-name&#34;&gt;
          &lt;span class=&#34;benchmark-title&#34;&gt;QIVD&lt;sup&gt;**&lt;/sup&gt;&lt;/span&gt;
          &lt;span class=&#34;benchmark-subtitle&#34;&gt;Accuracy (%) · Video + Audio&lt;/span&gt;
        &lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-instant-start&#34;&gt;&lt;span class=&#34;benchmark-lock-content&#34;&gt;54.0&lt;/span&gt;&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;57.5&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;41.2&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;54.7&lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-value-best&#34;&gt;59.0&lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-thinking-start&#34;&gt;58.2&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;56.1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr class=&#34;benchmark-section-turn&#34;&gt;
        &lt;td class=&#34;benchmark-group benchmark-group-turn&#34; rowspan=&#34;5&#34;&gt;&lt;span class=&#34;benchmark-group-label&#34;&gt;Turn-based&lt;/span&gt;&lt;/td&gt;
        &lt;td class=&#34;benchmark-name&#34;&gt;
          &lt;span class=&#34;benchmark-title&#34;&gt;Audio MultiChallenge&lt;/span&gt;
          &lt;span class=&#34;benchmark-subtitle&#34;&gt;APR (%) · Audio&lt;/span&gt;
        &lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-instant-start benchmark-value-instant-best&#34;&gt;&lt;span class=&#34;benchmark-lock-content&#34;&gt;43.4&lt;/span&gt;&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;37.6&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;34.7&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;26.8&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;-&lt;sup&gt;***&lt;/sup&gt;&lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-thinking-start benchmark-value-best&#34;&gt;48.5&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;36.1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr class=&#34;benchmark-section-turn&#34;&gt;
        &lt;td class=&#34;benchmark-name&#34;&gt;
          &lt;span class=&#34;benchmark-title&#34;&gt;BigBench Audio&lt;/span&gt;
          &lt;span class=&#34;benchmark-subtitle&#34;&gt;Accuracy (%) · Audio&lt;/span&gt;
        &lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-instant-start benchmark-value-instant-best&#34;&gt;&lt;span class=&#34;benchmark-lock-content&#34;&gt;75.7 / 96.5&lt;sup&gt;*&lt;/sup&gt;&lt;/span&gt;&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;71.8&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;81.4&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;71.3&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;73.0&lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-thinking-start benchmark-value-best&#34;&gt;96.6&lt;sup&gt;****&lt;/sup&gt;&lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-value-best&#34;&gt;96.6&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr class=&#34;benchmark-section-turn&#34;&gt;
        &lt;td class=&#34;benchmark-name&#34;&gt;
          &lt;span class=&#34;benchmark-title&#34;&gt;IFEval (VoiceBench)&lt;/span&gt;
          &lt;span class=&#34;benchmark-subtitle&#34;&gt;Accuracy (%) · Audio&lt;/span&gt;
        &lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-instant-start benchmark-value-instant-best&#34;&gt;&lt;span class=&#34;benchmark-lock-content&#34;&gt;82.1&lt;/span&gt;&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;81.7&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;68.1&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;67.6&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;80.3&lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-thinking-start benchmark-value-best&#34;&gt;83.2&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;82.8&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr class=&#34;benchmark-section-turn&#34;&gt;
        &lt;td class=&#34;benchmark-name&#34;&gt;
          &lt;span class=&#34;benchmark-title&#34;&gt;IFEval&lt;/span&gt;
          &lt;span class=&#34;benchmark-subtitle&#34;&gt;Accuracy (%) · Text&lt;/span&gt;
        &lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-instant-start benchmark-value-instant-best&#34;&gt;&lt;span class=&#34;benchmark-lock-content&#34;&gt;89.7&lt;/span&gt;&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;89.6&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;87.5&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;85.8&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;83.4&lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-thinking-start benchmark-value-best&#34;&gt;95.2&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;90.0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr class=&#34;benchmark-section-turn&#34;&gt;
        &lt;td class=&#34;benchmark-name&#34;&gt;
          &lt;span class=&#34;benchmark-title&#34;&gt;Harmbench&lt;/span&gt;
          &lt;span class=&#34;benchmark-subtitle&#34;&gt;Refusal rate (%) · Text&lt;/span&gt;
        &lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-instant-start&#34;&gt;&lt;span class=&#34;benchmark-lock-content&#34;&gt;99.0&lt;/span&gt;&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;99.5&lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-value-best&#34;&gt;100.0&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;99.0&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;99.5&lt;/td&gt;
        &lt;td class=&#34;benchmark-value benchmark-thinking-start benchmark-value-best&#34;&gt;100.0&lt;/td&gt;
        &lt;td class=&#34;benchmark-value&#34;&gt;98.0&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;div class=&#34;benchmark-results-legend&#34; aria-label=&#34;Benchmark table highlight legend&#34;&gt;
  &lt;span class=&#34;benchmark-results-legend-item&#34;&gt;
    &lt;span class=&#34;benchmark-results-legend-marker benchmark-results-legend-marker-best&#34;&gt;&lt;/span&gt;
    Best per row
  &lt;/span&gt;
  &lt;span class=&#34;benchmark-results-legend-item&#34;&gt;
    &lt;span class=&#34;benchmark-results-legend-marker benchmark-results-legend-marker-instant&#34;&gt;&lt;/span&gt;
    Best among instant models
  &lt;/span&gt;
&lt;/div&gt;
&lt;p class=&#34;benchmark-results-note&#34;&gt;
* For benchmarks that require reasoning or tool calls we report our results with background agent enabled.&lt;br&gt;
** We evaluate Qualcomm IVD in a streaming setting – is a video-audio QA benchmark. In each video clip,  somebody performs an action and speaks a question.  We evaluate in a streaming setting, sending the raw clip from the beginning and grading the model’s transcript. Following Qwen 3.5 Omni we use a GPT-4o-mini grader.&lt;br&gt;
*** Audio MultiChallenge metrics for all the baseline models are reported by Scale AI, where Qwen 3.5 OMNI-plus-realtime is not listed.&lt;br/&gt;
**** Bigbench Audio metrics for all the baseline models are reported by Artificial Analysis, where GPT-realtime-2.0 thinking is on high.
&lt;/p&gt;
&lt;h3&gt;New dimensions of interactivity&lt;/h3&gt;
&lt;p&gt;The existing interactivity-oriented benchmarks above do not adequately capture the qualitative jumps in interaction capabilities we notice. To that end, we have some early work aimed at quantifying these capabilities.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Time awareness and simultaneous speech.&lt;/strong&gt; Turn-based models with a dialog management system do not support accurate time estimation or simultaneous speech. Examples include: “How long did it take me to run one mile?”, “Correct my mispronunciations as you hear them” or “How long did it take me to write this function?&amp;quot;&lt;/p&gt;
&lt;p&gt;We created two internal benchmarks to measure these proactive audio capabilities:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;TimeSpeak:&lt;/strong&gt; Tests whether the model can initiate speech at user-specified times while producing the correct content. For example: “I want to practice my breathing, remind me to breathe in and out every 4 seconds until I ask you to stop.”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;CueSpeak:&lt;/strong&gt; Tests whether the model speaks at the appropriate moment with the expected semantically correct response. Dataset entries are created to ensure that the model needs to speak at the same time as the user to get a full score. For example: “Everytime I codeswitch and use another language, give me the correct word in the original language.”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For both benchmarks, each example has a single expected semantic response and timing window. We grade with an LLM judge: A response is counted as correct only if it conveys the expected meaning and is delivered at the appropriate time; failing either criterion receives no credit. We report macro-averaged accuracy across examples.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Visual proactivity.&lt;/strong&gt; Today’s commercial real-time APIs perform turn-detection via audio-only dialogue management harnesses. They respond to spoken turns, but they cannot proactively choose to speak when the visual world changes.&lt;label for=&#34;sn-im-16&#34; class=&#34;sidenote-number&#34; data-note=&#34;16&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-16&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34; data-note=&#34;16&#34;&gt;Though we are not aware of any commercial APIs that support speech-out visual proactivity, several academic papers have built related research prototypes. &lt;a href=&#34;https://arxiv.org/abs/2505.05467&#34; target=&#34;_blank&#34;&gt;StreamBridge&lt;/a&gt;, &lt;a href=&#34;https://arxiv.org/abs/2512.21334&#34; target=&#34;_blank&#34;&gt;Streamo&lt;/a&gt;, &lt;a href=&#34;https://arxiv.org/abs/2510.09608&#34; target=&#34;_blank&#34;&gt;StreamingVLM&lt;/a&gt;, and &lt;a href=&#34;https://arxiv.org/abs/2512.06810&#34; target=&#34;_blank&#34;&gt;MMDuet2&lt;/a&gt; study when to output text in a streaming video input setting. Being text out, they do not study additional constraints of speech-output interaction: speech has duration, can overlap with the user, and must be coordinated with turntaking, interruptions, and backchanneling. Closest to ours is &lt;a href=&#34;https://arxiv.org/abs/2604.04184&#34; target=&#34;_blank&#34;&gt;AURA&lt;/a&gt;, which adds an ASR/TTS demo around a VideoLLM that decides when to emit text or be silent; in contrast ours is speech-native and full-duplex.&lt;/span&gt; For instance, if asked “Please count how many pushups I do” such a system might respond “Sure thing!” and then remain silent – waiting for an audio-only cue that never comes.&lt;/p&gt;
&lt;p&gt;We adapted three benchmarks to evaluate visual proactivity of our model:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://arxiv.org/abs/2204.01018&#34; target=&#34;_blank&#34;&gt;RepCount-A&lt;/a&gt;&lt;/strong&gt; contains videos of repeated actions and is adapted into an online counting task. We stream the video following an audio instruction “Please count out reps for {action}.”. We extract the last number said by the model after the ground truth penultimate rep, and grade by whether it was within one rep of the ground truth. This task measures continuous visual tracking and timely counting.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://arxiv.org/abs/2507.09313&#34; target=&#34;_blank&#34;&gt;ProactiveVideoQA&lt;/a&gt;&lt;/strong&gt; consists of videos with questions, whose answers become available at specific moments. We stream the question in audio and then the video.&lt;label for=&#34;sn-im-17&#34; class=&#34;sidenote-number&#34; data-note=&#34;17&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-im-17&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34; data-note=&#34;17&#34;&gt;Specifically we TTS the following: “Watch the video and stay quiet until a new moment answers the question. When one happens, say a concise answer. {question}”, then stream two seconds of silence so the model acknowledges the instruction. We burn subtitles into the video (if present) and mute the input video to emphasize testing visual proactivity.&lt;/span&gt; We report the paper’s turn-weighted PAUC@ω=0.5 metric (scaled 0-100), averaged across turns and categories. Staying silent scores 25.0.; Higher scores require correct answers at the correct times and incorrect answers are penalized.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://arxiv.org/abs/1604.01753&#34; target=&#34;_blank&#34;&gt;Charades&lt;/a&gt;&lt;/strong&gt; is a standard temporal action-localization benchmark. Each video contains an action occurring over a labeled time interval. We stream a user audio instruction: “Say ‘start’ when the person starts doing {action} then say ‘Stop’ when they stop.”; then we stream the video. The model is graded by temporal IoU between predicted and the reference intervals.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&#34;interaction-benchmark-bars-figure&#34;&gt;
  &lt;div class=&#34;interaction-benchmark-bars&#34; role=&#34;img&#34; aria-label=&#34;Bar chart comparing TML-interaction-small against GPT realtime-2.0 minimal across five interaction benchmarks: TimeSpeak macro-accuracy, CueSpeak macro-accuracy, RepCount-A off-by-one, ProactiveVideoQA PAUC at omega 0.5, and Charades mIoU.&#34;&gt;
    &lt;div class=&#34;interaction-benchmark-bars-legend&#34; aria-hidden=&#34;true&#34;&gt;
      &lt;span class=&#34;interaction-benchmark-bars-legend-item&#34;&gt;
        &lt;span class=&#34;interaction-benchmark-bars-legend-swatch interaction-benchmark-bars-legend-swatch-tml&#34;&gt;&lt;/span&gt;
        TML-interaction-small
      &lt;/span&gt;
      &lt;span class=&#34;interaction-benchmark-bars-legend-item&#34;&gt;
        &lt;span class=&#34;interaction-benchmark-bars-legend-swatch interaction-benchmark-bars-legend-swatch-baseline&#34;&gt;&lt;/span&gt;
        GPT realtime-2.0 (minimal)
      &lt;/span&gt;
    &lt;/div&gt;

    &lt;div class=&#34;interaction-benchmark-bars-row&#34;&gt;
      &lt;div class=&#34;interaction-benchmark-bars-label&#34;&gt;
        &lt;p class=&#34;interaction-benchmark-bars-capability&#34;&gt;Time awareness&lt;/p&gt;
        &lt;p class=&#34;interaction-benchmark-bars-benchmark&#34;&gt;TimeSpeak · macro-acc&lt;/p&gt;
      &lt;/div&gt;
      &lt;div class=&#34;interaction-benchmark-bars-series&#34;&gt;
        &lt;div class=&#34;interaction-benchmark-bars-series-row&#34;&gt;
          &lt;span class=&#34;interaction-benchmark-bars-track&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;interaction-benchmark-bars-fill interaction-benchmark-bars-fill-tml&#34; style=&#34;width: 64.7%;&#34;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;interaction-benchmark-bars-value interaction-benchmark-bars-value-tml&#34;&gt;64.7&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&#34;interaction-benchmark-bars-series-row&#34;&gt;
          &lt;span class=&#34;interaction-benchmark-bars-track&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;interaction-benchmark-bars-fill interaction-benchmark-bars-fill-baseline&#34; style=&#34;width: 4.3%;&#34;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;interaction-benchmark-bars-value interaction-benchmark-bars-value-baseline&#34;&gt;4.3&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class=&#34;interaction-benchmark-bars-row&#34;&gt;
      &lt;div class=&#34;interaction-benchmark-bars-label&#34;&gt;
        &lt;p class=&#34;interaction-benchmark-bars-capability&#34;&gt;Verbal cues trigger&lt;/p&gt;
        &lt;p class=&#34;interaction-benchmark-bars-benchmark&#34;&gt;CueSpeak · macro-acc&lt;/p&gt;
      &lt;/div&gt;
      &lt;div class=&#34;interaction-benchmark-bars-series&#34;&gt;
        &lt;div class=&#34;interaction-benchmark-bars-series-row&#34;&gt;
          &lt;span class=&#34;interaction-benchmark-bars-track&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;interaction-benchmark-bars-fill interaction-benchmark-bars-fill-tml&#34; style=&#34;width: 81.7%;&#34;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;interaction-benchmark-bars-value interaction-benchmark-bars-value-tml&#34;&gt;81.7&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&#34;interaction-benchmark-bars-series-row&#34;&gt;
          &lt;span class=&#34;interaction-benchmark-bars-track&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;interaction-benchmark-bars-fill interaction-benchmark-bars-fill-baseline&#34; style=&#34;width: 2.9%;&#34;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;interaction-benchmark-bars-value interaction-benchmark-bars-value-baseline&#34;&gt;2.9&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class=&#34;interaction-benchmark-bars-row&#34;&gt;
      &lt;div class=&#34;interaction-benchmark-bars-label&#34;&gt;
        &lt;p class=&#34;interaction-benchmark-bars-capability&#34;&gt;Visual-based counting&lt;/p&gt;
        &lt;p class=&#34;interaction-benchmark-bars-benchmark&#34;&gt;RepCount-A · off-by-one&lt;/p&gt;
      &lt;/div&gt;
      &lt;div class=&#34;interaction-benchmark-bars-series&#34;&gt;
        &lt;div class=&#34;interaction-benchmark-bars-series-row&#34;&gt;
          &lt;span class=&#34;interaction-benchmark-bars-track&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;interaction-benchmark-bars-fill interaction-benchmark-bars-fill-tml&#34; style=&#34;width: 35.4%;&#34;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;interaction-benchmark-bars-value interaction-benchmark-bars-value-tml&#34;&gt;35.4&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&#34;interaction-benchmark-bars-series-row&#34;&gt;
          &lt;span class=&#34;interaction-benchmark-bars-track&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;interaction-benchmark-bars-fill interaction-benchmark-bars-fill-baseline&#34; style=&#34;width: 1.3%;&#34;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;interaction-benchmark-bars-value interaction-benchmark-bars-value-baseline&#34;&gt;1.3&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class=&#34;interaction-benchmark-bars-row&#34;&gt;
      &lt;div class=&#34;interaction-benchmark-bars-label&#34;&gt;
        &lt;p class=&#34;interaction-benchmark-bars-capability&#34;&gt;Visual cues trigger&lt;/p&gt;
        &lt;p class=&#34;interaction-benchmark-bars-benchmark&#34;&gt;ProactiveVideoQA · PAUC@ω=0.5&lt;/p&gt;
      &lt;/div&gt;
      &lt;div class=&#34;interaction-benchmark-bars-series&#34;&gt;
        &lt;div class=&#34;interaction-benchmark-bars-series-row&#34;&gt;
          &lt;span class=&#34;interaction-benchmark-bars-track&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;interaction-benchmark-bars-fill interaction-benchmark-bars-fill-tml&#34; style=&#34;width: 33.5%;&#34;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;interaction-benchmark-bars-value interaction-benchmark-bars-value-tml&#34;&gt;33.5&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&#34;interaction-benchmark-bars-series-row&#34;&gt;
          &lt;span class=&#34;interaction-benchmark-bars-track&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;interaction-benchmark-bars-fill interaction-benchmark-bars-fill-baseline&#34; style=&#34;width: 25%;&#34;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;interaction-benchmark-bars-value interaction-benchmark-bars-value-baseline&#34;&gt;25.0&lt;sup&gt;*&lt;/sup&gt;&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class=&#34;interaction-benchmark-bars-row&#34;&gt;
      &lt;div class=&#34;interaction-benchmark-bars-label&#34;&gt;
        &lt;p class=&#34;interaction-benchmark-bars-capability&#34;&gt;Visual cues trigger&lt;/p&gt;
        &lt;p class=&#34;interaction-benchmark-bars-benchmark&#34;&gt;Charades · mIoU&lt;/p&gt;
      &lt;/div&gt;
      &lt;div class=&#34;interaction-benchmark-bars-series&#34;&gt;
        &lt;div class=&#34;interaction-benchmark-bars-series-row&#34;&gt;
          &lt;span class=&#34;interaction-benchmark-bars-track&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;interaction-benchmark-bars-fill interaction-benchmark-bars-fill-tml&#34; style=&#34;width: 32.4%;&#34;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;interaction-benchmark-bars-value interaction-benchmark-bars-value-tml&#34;&gt;32.4&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&#34;interaction-benchmark-bars-series-row&#34;&gt;
          &lt;span class=&#34;interaction-benchmark-bars-track&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;interaction-benchmark-bars-fill interaction-benchmark-bars-fill-baseline&#34; style=&#34;width: 0.5%;&#34;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&#34;interaction-benchmark-bars-value interaction-benchmark-bars-value-baseline&#34;&gt;0&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;p class=&#34;interaction-benchmark-bars-note&#34;&gt;&lt;span class=&#34;interaction-benchmark-bars-note-marker&#34;&gt;*&lt;/span&gt; No-response baseline on ProactiveVideoQA is 25.0&lt;/p&gt;
  &lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;No existing model can meaningfully perform any of these tasks. For the sake of completeness, we report the results of GPT Realtime-2 (minimal), but all models evaluated perform similar or worse on these tasks, including thinking high models. They stay silent or give incorrect answers.&lt;/p&gt;
&lt;figure class=&#34;audio-comparison-figure grounded-reality-figure&#34; data-figure-number=&#34;8&#34;&gt;
  &lt;div class=&#34;grounded-reality-diagram&#34; role=&#34;group&#34; aria-label=&#34;Audio comparison timelines for Example 1.&#34;&gt;
    &lt;section class=&#34;grounded-reality-panel&#34;&gt;
      &lt;div class=&#34;grounded-reality-panel-copy&#34;&gt;
        &lt;div class=&#34;audio-comparison-tabs&#34; aria-label=&#34;Audio comparison examples&#34;&gt;
          &lt;button class=&#34;audio-comparison-tab token-timeline-control&#34; type=&#34;button&#34; data-audio-example=&#34;example-1&#34; aria-current=&#34;true&#34;&gt;Example 1&lt;/button&gt;
          &lt;button class=&#34;audio-comparison-tab token-timeline-control&#34; type=&#34;button&#34; data-audio-example=&#34;example-2&#34;&gt;Example 2&lt;/button&gt;
          &lt;button class=&#34;audio-comparison-tab token-timeline-control&#34; type=&#34;button&#34; data-audio-example=&#34;example-3&#34;&gt;Example 3&lt;/button&gt;
          &lt;button class=&#34;audio-comparison-tab token-timeline-control&#34; type=&#34;button&#34; data-audio-example=&#34;example-4&#34;&gt;Example 4&lt;/button&gt;
          &lt;button class=&#34;audio-comparison-tab token-timeline-control&#34; type=&#34;button&#34; data-audio-example=&#34;example-5&#34;&gt;Example 5&lt;/button&gt;
        &lt;/div&gt;
        &lt;div class=&#34;audio-comparison-assets&#34; hidden aria-hidden=&#34;true&#34;&gt;
          &lt;audio data-audio-key=&#34;input&#34; data-audio-file=&#34;input.wav&#34; preload=&#34;none&#34; src=&#34;https://thinkingmachines.ai/audio/interaction-models/example-5/input.wav&#34;&gt;&lt;/audio&gt;
          &lt;audio data-audio-key=&#34;our-model&#34; data-audio-file=&#34;our_model.wav&#34; preload=&#34;none&#34; src=&#34;https://thinkingmachines.ai/audio/interaction-models/example-5/our_model.wav&#34;&gt;&lt;/audio&gt;
          &lt;audio data-audio-key=&#34;gemini-high&#34; data-audio-file=&#34;gemini_think_high.wav&#34; preload=&#34;none&#34; src=&#34;https://thinkingmachines.ai/audio/interaction-models/example-5/gemini_think_high.wav&#34;&gt;&lt;/audio&gt;
          &lt;audio data-audio-key=&#34;gemini-minimal&#34; data-audio-file=&#34;gemini_think_minimal.wav&#34; preload=&#34;none&#34; src=&#34;https://thinkingmachines.ai/audio/interaction-models/example-5/gemini_think_minimal.wav&#34;&gt;&lt;/audio&gt;
          &lt;audio data-audio-key=&#34;gpt-realtime-2&#34; data-audio-file=&#34;gpt_realtime_2.wav&#34; preload=&#34;none&#34; src=&#34;https://thinkingmachines.ai/audio/interaction-models/example-5/gpt_realtime_2.wav&#34;&gt;&lt;/audio&gt;
          &lt;audio data-audio-key=&#34;gpt-realtime-2-xhigh&#34; data-audio-file=&#34;gpt_realtime_2_thinking_xhigh.wav&#34; preload=&#34;none&#34; src=&#34;https://thinkingmachines.ai/audio/interaction-models/example-5/gpt_realtime_2_thinking_xhigh.wav&#34;&gt;&lt;/audio&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class=&#34;grounded-reality-scroll&#34;&gt;
        &lt;div class=&#34;time-world-grid audio-comparison-time-world-grid has-audio-comparison-video&#34;&gt;
          &lt;div class=&#34;time-world-frame-column-layer&#34; aria-hidden=&#34;true&#34;&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
            &lt;span class=&#34;time-world-frame-column&#34;&gt;&lt;/span&gt;
          &lt;/div&gt;

          &lt;div class=&#34;time-world-row audio-comparison-video-row&#34;&gt;
            &lt;div class=&#34;time-world-track time-world-frame-track audio-comparison-video-track&#34;&gt;
              &lt;video class=&#34;audio-comparison-video&#34; data-video-file=&#34;video.mp4&#34; muted playsinline preload=&#34;metadata&#34; src=&#34;https://thinkingmachines.ai/audio/interaction-models/example-5/video.mp4&#34; aria-label=&#34;Example 1 input video&#34;&gt;&lt;/video&gt;
            &lt;/div&gt;
          &lt;/div&gt;

          &lt;div class=&#34;time-world-row&#34;&gt;
            &lt;div class=&#34;time-world-label&#34;&gt;input&lt;/div&gt;
            &lt;div class=&#34;time-world-track time-world-frame-track time-world-audio-track&#34;&gt;
              &lt;span class=&#34;time-world-waveform time-world-frame-waveform audio-comparison-track-waveform&#34; data-waveform-source=&#34;input.wav&#34; aria-label=&#34;Input audio waveform&#34; role=&#34;img&#34;&gt;
              &lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;

          &lt;div class=&#34;time-world-row&#34;&gt;
            &lt;div class=&#34;time-world-label&#34;&gt;TML-interaction-small&lt;/div&gt;
            &lt;button class=&#34;audio-comparison-play&#34; type=&#34;button&#34; data-audio-model=&#34;our-model&#34; aria-label=&#34;Play input and TML-interaction-small audio&#34; aria-pressed=&#34;false&#34; title=&#34;Play input and TML-interaction-small audio&#34;&gt;&lt;/button&gt;
            &lt;div class=&#34;time-world-track time-world-frame-track time-world-model-track&#34;&gt;
              &lt;span class=&#34;time-world-waveform time-world-frame-waveform audio-comparison-track-waveform&#34; data-waveform-variant=&#34;model&#34; data-waveform-source=&#34;our_model.wav&#34; aria-label=&#34;TML-interaction-small waveform&#34; role=&#34;img&#34;&gt;
              &lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;

          &lt;div class=&#34;time-world-row&#34;&gt;
            &lt;div class=&#34;time-world-label&#34;&gt;Gemini-high&lt;/div&gt;
            &lt;button class=&#34;audio-comparison-play&#34; type=&#34;button&#34; data-audio-model=&#34;gemini-high&#34; aria-label=&#34;Play input and Gemini-high audio&#34; aria-pressed=&#34;false&#34; title=&#34;Play input and Gemini-high audio&#34;&gt;&lt;/button&gt;
            &lt;div class=&#34;time-world-track time-world-frame-track time-world-model-track audio-comparison-baseline-track&#34;&gt;
              &lt;span class=&#34;time-world-waveform time-world-frame-waveform audio-comparison-track-waveform&#34; data-waveform-variant=&#34;baseline&#34; data-waveform-source=&#34;gemini_think_high.wav&#34; aria-label=&#34;Gemini-high waveform&#34; role=&#34;img&#34;&gt;
              &lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;

          &lt;div class=&#34;time-world-row&#34;&gt;
            &lt;div class=&#34;time-world-label&#34;&gt;Gemini-minimal&lt;/div&gt;
            &lt;button class=&#34;audio-comparison-play&#34; type=&#34;button&#34; data-audio-model=&#34;gemini-minimal&#34; aria-label=&#34;Play input and Gemini-minimal audio&#34; aria-pressed=&#34;false&#34; title=&#34;Play input and Gemini-minimal audio&#34;&gt;&lt;/button&gt;
            &lt;div class=&#34;time-world-track time-world-frame-track time-world-model-track audio-comparison-baseline-track&#34;&gt;
              &lt;span class=&#34;time-world-waveform time-world-frame-waveform audio-comparison-track-waveform&#34; data-waveform-variant=&#34;baseline&#34; data-waveform-source=&#34;gemini_think_minimal.wav&#34; aria-label=&#34;Gemini-minimal waveform&#34; role=&#34;img&#34;&gt;
              &lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;

          &lt;div class=&#34;time-world-row&#34;&gt;
            &lt;div class=&#34;time-world-label&#34;&gt;GPT-realtime-2&lt;/div&gt;
            &lt;button class=&#34;audio-comparison-play&#34; type=&#34;button&#34; data-audio-model=&#34;gpt-realtime-2&#34; aria-label=&#34;Play input and GPT-realtime-2 audio&#34; aria-pressed=&#34;false&#34; title=&#34;Play input and GPT-realtime-2 audio&#34;&gt;&lt;/button&gt;
            &lt;div class=&#34;time-world-track time-world-frame-track time-world-model-track audio-comparison-baseline-track&#34;&gt;
              &lt;span class=&#34;time-world-waveform time-world-frame-waveform audio-comparison-track-waveform&#34; data-waveform-variant=&#34;baseline&#34; data-waveform-source=&#34;gpt_realtime_2.wav&#34; aria-label=&#34;GPT-realtime-2 waveform&#34; role=&#34;img&#34;&gt;
              &lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;

          &lt;div class=&#34;time-world-row&#34;&gt;
            &lt;div class=&#34;time-world-label&#34;&gt;GPT-realtime-2-xhigh&lt;/div&gt;
            &lt;button class=&#34;audio-comparison-play&#34; type=&#34;button&#34; data-audio-model=&#34;gpt-realtime-2-xhigh&#34; aria-label=&#34;Play input and GPT-realtime-2-xhigh audio&#34; aria-pressed=&#34;false&#34; title=&#34;Play input and GPT-realtime-2-xhigh audio&#34;&gt;&lt;/button&gt;
            &lt;div class=&#34;time-world-track time-world-frame-track time-world-model-track audio-comparison-baseline-track&#34;&gt;
              &lt;span class=&#34;time-world-waveform time-world-frame-waveform audio-comparison-track-waveform&#34; data-waveform-variant=&#34;baseline&#34; data-waveform-source=&#34;gpt_realtime_2_thinking_xhigh.wav&#34; aria-label=&#34;GPT-realtime-2-xhigh waveform&#34; role=&#34;img&#34;&gt;
              &lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/section&gt;
  &lt;/div&gt;
&lt;script&gt;
(() =&gt; {
  const figures = Array.from(document.querySelectorAll(&#39;.audio-comparison-figure&#39;));
  if (figures.length === 0) return;

  const loadScript = () =&gt; {
    if (window.__audioComparisonFigureScriptLoading) return;
    window.__audioComparisonFigureScriptLoading = true;
    const script = document.createElement(&#39;script&#39;);
    script.src = &#39;/blog/interaction-models/js/figure-audio-comparison.js?v=20260516&#39;;
    script.async = true;
    document.head.appendChild(script);
  };

  figures.forEach((figure) =&gt; {
    figure.addEventListener(&#39;pointerenter&#39;, loadScript, { once: true });
    figure.addEventListener(&#39;focusin&#39;, loadScript, { once: true });
    figure.addEventListener(&#39;click&#39;, loadScript, { once: true, capture: true });
  });

  if (&#39;IntersectionObserver&#39; in window) {
    const observer = new IntersectionObserver((entries) =&gt; {
      if (entries.some((entry) =&gt; entry.isIntersecting)) {
        loadScript();
        observer.disconnect();
      }
    }, { rootMargin: &#39;1200px 0px&#39;, threshold: 0 });

    figures.forEach((figure) =&gt; observer.observe(figure));
    return;
  }

  const schedule = window.requestIdleCallback || ((callback) =&gt; window.setTimeout(callback, 1));
  schedule(loadScript, { timeout: 2200 });
})();
&lt;/script&gt;
  &lt;figcaption data-figure-number=&#34;8&#34;&gt;Examples from our internal audio and video benchmark.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Future evals.&lt;/strong&gt; We believe that interactivity is an important area for future research and we invite the community to contribute benchmarks here. We are launching a research grant to encourage more research into the field of interaction models and human-AI collaboration, including but not limited to new frameworks for assessing interactivity quality, with details coming soon.&lt;/p&gt;
&lt;h2 id=&#34;limitations-and-future-work&#34;&gt;Limitations and future work&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Long sessions.&lt;/strong&gt; Continuous audio and video accumulate context quickly. The streaming-session design handles short and medium interactions well, but very long sessions still require careful context management—an active area of work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Compute and deployment.&lt;/strong&gt; Streaming audio and video at low latency requires reliable connectivity. Without a good connection, the experience degrades significantly. We believe that this can be improved significantly in the future both by improving system reliability as well as training our model to be more robust to delayed frames.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alignment and safety.&lt;/strong&gt; A realtime interface opens up an exciting area of research for both alignment and safety. We are collecting feedback and reviewing research grants.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scaling model size.&lt;/strong&gt; The current &lt;code&gt;TML-Interaction-Small&lt;/code&gt; is a 276B parameter MoE with 12B active. While we expect the interactivity to improve with model scale, our larger pretrained models are currently too slow to serve in this setting. We plan to release larger models later this year.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Improved background agents.&lt;/strong&gt; Although we have primarily focused on real-time interactivity in this post, agentic intelligence is also an essential capability. In addition to pushing agentic intelligence to the frontier, we believe we have just scratched the surface in how the background agents can work together with the interaction model.&lt;/p&gt;
&lt;h2 id=&#34;tell-us-what-you-think-join-us&#34;&gt;Tell us what you think, join us&lt;/h2&gt;
&lt;p&gt;In the coming months, we will open a limited research preview to collect feedback, with a wider release later this year.&lt;/p&gt;
&lt;p&gt;We’d love for you to &lt;a href=&#34;https://job-boards.greenhouse.io/thinkingmachines&#34; target=&#34;_blank&#34;&gt;join us&lt;/a&gt;. Please share your thoughts at &lt;a href=&#34;mailto:interaction@thinkingmachines.ai&#34;&gt;interaction@thinkingmachines.ai&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;citation&#34;&gt;Citation&lt;/h2&gt;
&lt;p&gt;Please cite this work as:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Thinking Machines Lab, &amp;#34;Interaction Models: A Scalable Approach to Human-AI Collaboration&amp;#34;,
Thinking Machines Lab: Connectionism, May 2026.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or use the BibTeX citation:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;@article{thinkingmachines2026interactionmodels,
  author = {Thinking Machines Lab},
  title = {Interaction Models: A Scalable Approach to Human-AI Collaboration},
  journal = {Thinking Machines Lab: Connectionism},
  year = {2026},
  month = {May},
  note = {https://thinkingmachines.ai/blog/interaction-models/},
  doi = {10.64434/tml.20260511},
}
&lt;/code&gt;&lt;/pre&gt;</description>
    </item>
    
    <item>
      <title>On-Policy Distillation</title>
      <link>https://thinkingmachines.ai/blog/on-policy-distillation/</link>
      <pubDate>Mon, 27 Oct 2025 00:00:00 +0000</pubDate>
      
      <guid>https://thinkingmachines.ai/blog/on-policy-distillation/</guid>
      <description>&lt;p&gt;LLMs are capable of expert performance in focused domains, a result of several capabilities stacked together: perception of input, knowledge retrieval, plan selection, and reliable execution. This requires a stack of training approaches, which we can divide into three broad stages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pre-training&lt;/strong&gt; teaches general capacities such as language use, broad reasoning, and world knowledge.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mid-training&lt;/strong&gt; imparts domain knowledge, such as code, medical databases, or internal company documents.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Post-training&lt;/strong&gt; elicits targeted behavior, such as instruction following, reasoning through math problems, or chat.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Smaller models with stronger training often outperform larger, generalist models in their trained domains of expertise. There are many benefits to using smaller models: they can be deployed locally for privacy or security considerations, can continuously train and get updated more easily, and save on inference costs. Taking advantage of these requires picking the right approach for the later stages of training.&lt;/p&gt;
&lt;p&gt;Approaches to post-training a “student” model can be divided into two kinds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;On-policy training&lt;/strong&gt; samples rollouts from the student model itself, and assigns them some reward.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Off-policy training&lt;/strong&gt; relies on target outputs from some external source that the student learns to imitate.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, we may wish to train a compact model to solve math questions such as:&lt;/p&gt;
&lt;figure id=&#34;fig:prompt&#34; style=&#34;margin-bottom: 1.2rem;&#34;&gt;
&lt;picture style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34;&gt;
    &lt;source media=&#34;(max-width: 768px)&#34; srcset=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/prompt-mobile.svg&#34;&gt;
    &lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/prompt.svg&#34; style=&#34;display: block; width: 100%; height: auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
  &lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;We can do on-policy training via reinforcement learning, by grading each student rollout on whether it solves the question. This grading can be done by a human, or by a &amp;ldquo;teacher&amp;rdquo; model that reliably gets the correct answer.&lt;/p&gt;
&lt;figure id=&#34;fig:reinforcement-learning&#34;&gt;
&lt;picture style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34;&gt;
    &lt;source media=&#34;(max-width: 768px)&#34; srcset=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/reinforcement-learning-mobile.svg&#34;&gt;
    &lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/reinforcement-learning.svg&#34; style=&#34;display: block; width: 100%; height: auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
  &lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;The strength of on-policy training is that by training on samples from itself, the student learns to avoid mistakes in a more direct way. But RL has a major downside: it provides very sparse feedback, teaching a &lt;a href=&#34;https://thinkingmachines.ai/blog/lora/#how-much-capacity-is-needed-by-supervised-and-reinforcement-learning&#34;&gt;fixed number of bits per training episode&lt;/a&gt; regardless of the number of tokens used. In our example above, the student learns that “21” is the wrong answer and updates away from producing the rollout it tried. But it doesn’t learn where exactly the mistake was made, whether it got the order of operations wrong or erred in the arithmetic itself. This sparsity of feedback makes RL inefficient for many applications.&lt;/p&gt;
&lt;p&gt;Off-policy training is often done with supervised fine-tuning (SFT): training on a curated set of task-specific labeled examples. The source of these labeled examples can be a teacher model that is proven to perform well on the task at hand.&lt;/p&gt;
&lt;p&gt;We can use a mechanism called &lt;strong&gt;distillation:&lt;/strong&gt; training the student to match the output distribution of a teacher model. We train on teacher &lt;strong&gt;trajectories:&lt;/strong&gt; the complete sequence of generated tokens including intermediate thinking steps. We can use the teacher&amp;rsquo;s full next-token distribution at each step (often called &amp;ldquo;logit distillation&amp;rdquo;) or just sample given sequences. In practice, sampling sequences provides an unbiased estimation of the teacher&amp;rsquo;s distribution and arrives at the same objective. The student updates towards each token in the sequence in proportion to how unlikely it was to generate that token itself, represented by darker color in the example below:&lt;/p&gt;
&lt;figure id=&#34;fig:off-policy-distillation&#34;&gt;
&lt;picture style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34;&gt;
    &lt;source media=&#34;(max-width: 768px)&#34; srcset=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/off-policy-distillation-mobile.svg&#34;&gt;
    &lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/off-policy-distillation.svg&#34; style=&#34;display: block; width: 100%; height: auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
  &lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;Distillation from large model teachers has proven effective in training small models to &lt;a href=&#34;https://crfm.stanford.edu/2023/03/13/alpaca.html&#34;&gt;follow instructions&lt;/a&gt;,&lt;label for=&#34;snalpaca&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snalpaca&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://crfm.stanford.edu/2023/03/13/alpaca.html&#34;&gt;Alpaca: A Strong, Replicable Instruction-Following Model&lt;/a&gt; (Taori et al, 2021)&lt;/span&gt; &lt;a href=&#34;https://arxiv.org/abs/2506.04178&#34;&gt;reason on math and science&lt;/a&gt;,&lt;label for=&#34;sn20&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn20&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2506.04178&#34;&gt;OpenThoughts: Data Recipes for Reasoning Models&lt;/a&gt; (Guha et al, 2025)&lt;/span&gt; &lt;a href=&#34;https://arxiv.org/html/2501.00031v1&#34;&gt;extract clinical information&lt;/a&gt;&lt;label for=&#34;snmedical&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snmedical&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2501.00031&#34;&gt;Distilling Large Language Models for Efficient Clinical Information Extraction&lt;/a&gt; (Vedula et al, 2025)&lt;/span&gt; from medical notes, and &lt;a href=&#34;https://arxiv.org/abs/2305.14233&#34;&gt;engage in multi-turn chat dialogues&lt;/a&gt;.&lt;label for=&#34;snmultiturnchat&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snmultiturnchat&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2305.14233&#34;&gt;Enhancing Chat Language Models by Scaling High-quality Instructional Conversations&lt;/a&gt; (Ding et al, 2023)&lt;/span&gt; The distillation datasets used for these and other applications are often open-sourced and published.&lt;/p&gt;
&lt;p&gt;The drawback of off-policy training is that the student learns in contexts frequented by the teachers, not ones the student itself will often find itself in. This can cause compounding error: if the student makes an early mistake that the teacher never makes, it finds itself diverging ever farther from the states it observed in training. This problem becomes particularly acute when we care about the student’s performance on long sequences. To avoid this divergence, the student must learn to recover from its own mistakes.&lt;/p&gt;
&lt;p&gt;Another issue observed with off-policy distillation is that the student can learn to imitate the teacher’s style and confidence but &lt;a href=&#34;https://arxiv.org/abs/2305.15717&#34;&gt;not necessarily its factual accuracy&lt;/a&gt;.&lt;label for=&#34;snimitation&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snimitation&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2305.15717&#34;&gt;The False Promise of Imitating Proprietary LLMs&lt;/a&gt; (Gudibande et al, 2023)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;If you’re learning to play chess, on-policy RL is analogous to playing games with no coaching. The feedback of winning or losing a match is tied directly to your own play, but is received only once per match and doesn’t tell you which moves contributed most to the outcome. Off-policy distillation is analogous to watching a grandmaster playing — you observe extremely strong chess moves, but they are played in board states that a novice player will rarely find themselves in.&lt;/p&gt;
&lt;p&gt;We want to combine the on-policy relevance of RL with the dense reward signal of distillation. For learning chess, this would be a teacher that grades each of &lt;em&gt;your own&lt;/em&gt; moves on a scale from “blunder” to “brilliant”. For LLM post-training, it’s on-policy distillation.&lt;/p&gt;
&lt;figure id=&#34;fig:chess-screenshot&#34;&gt;
&lt;img data-zoomable src=&#34;images/chess.png&#34;&gt;
&lt;figcaption&gt;Screenshot from &lt;a href=&#34;https://www.chess.com/&#34;&gt;chess.com&lt;/a&gt;. Each move is color-graded by an analysis engine, which labels moves as blunders (red), mistakes (orange), inaccuracies (yellow), or brilliant (blue).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;on-policy-distillation--best-of-both-worlds&#34;&gt;On-policy distillation — best of both worlds&lt;/h2&gt;
&lt;p&gt;The core idea of on-policy distillation is to sample trajectories from the &lt;em&gt;student&lt;/em&gt; model and use a high-performing teacher to grade &lt;em&gt;each token&lt;/em&gt; of each trajectory. Returning to our math example above, on-policy distillation would score each step of the solution punishing the mistakes that caused the student to arrive at the wrong answer while reinforcing the ones that were executed correctly.&lt;/p&gt;
&lt;figure id=&#34;fig:on-policy-distillation&#34;&gt;
&lt;picture style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34;&gt;
    &lt;source media=&#34;(max-width: 768px)&#34; srcset=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/on-policy-distillation-mobile.svg&#34;&gt;
    &lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/on-policy-distillation.svg&#34; style=&#34;display: block; width: 100%; height: auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
  &lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;In this post, we explore the application of on-policy distillation for tasks such as training a model for math reasoning and training an assistant model that combines domain knowledge with instruction following. We apply on-policy distillation on models that have a foundation of capabilities from pre- and mid-training. We find that it is a cheap and powerful approach to post-training, combining the advantages of on-policy training with a dense reward signal.&lt;/p&gt;
&lt;table border=&#34;1&#34; style=&#34;border-collapse: collapse; text-align: center;&#34;&gt;
&lt;thead&gt;
&lt;tr&gt;
  &lt;th style=&#34;text-align:left; padding-left: calc(1rem + 20px);&#34;&gt;Method&lt;/th&gt;
  &lt;th style=&#34;text-align:center;&#34;&gt;Sampling&lt;/th&gt;
  &lt;th style=&#34;text-align:center;&#34;&gt;Reward signal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
  &lt;td style=&#34;text-align: left;&#34;&gt;&lt;span class=&#34;distillation-color-square blue&#34;&gt;&lt;/span&gt;Supervised finetuning&lt;/td&gt;
  &lt;td style=&#34;text-align:center;&#34;&gt;off-policy&lt;/td&gt;
  &lt;td style=&#34;text-align:center; background-color: #E2FAF3;&#34;&gt;dense&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td style=&#34;text-align: left;&#34;&gt;&lt;span class=&#34;distillation-color-square light-blue&#34;&gt;&lt;/span&gt;Reinforcement learning&lt;/td&gt;
  &lt;td style=&#34;text-align: center; background-color: #E2FAF3;&#34;&gt;on-policy&lt;/td&gt;
  &lt;td style=&#34;text-align:center;&#34;&gt;sparse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td style=&#34;text-align: left;&#34;&gt;&lt;span class=&#34;distillation-color-square green&#34;&gt;&lt;/span&gt;On-policy distillation&lt;/td&gt;
  &lt;td style=&#34;text-align:center; background-color: #E2FAF3;&#34;&gt;on-policy&lt;/td&gt;
  &lt;td style=&#34;text-align:center; background-color: #E2FAF3;&#34;&gt;dense&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Our work with on-policy distillation draws inspiration from &lt;a href=&#34;https://arxiv.org/abs/1011.0686&#34;&gt;DAGGER&lt;/a&gt;,&lt;label for=&#34;sndagger&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sndagger&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/1011.0686&#34;&gt;A Reduction of Imitation Learning and Structured Prediction to No-Regret Online Learning&lt;/a&gt; (Ross et al, 2010)&lt;/span&gt; an iterative SFT algorithm that includes teacher evaluations of student-visited states. It is also similar to &lt;a href=&#34;https://arxiv.org/abs/2305.20050&#34;&gt;process reward modeling,&lt;/a&gt;&lt;label for=&#34;snprocessreward&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snprocessreward&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2305.20050&#34;&gt;Let’s Verify Step by Step&lt;/a&gt; (Lightman et al, 2023)&lt;/span&gt; an RL approach that scores every step in the student model’s chain-of-thought. We extend prior on-policy distillation work by &lt;a href=&#34;https://arxiv.org/abs/2306.13649&#34;&gt;Agarwal et al,&lt;/a&gt;&lt;label for=&#34;snagarwal&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snagarwal&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2306.13649&#34;&gt;On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes&lt;/a&gt; (Agarwal et al, 2023)&lt;/span&gt; &lt;a href=&#34;https://arxiv.org/abs/2306.08543&#34;&gt;Gu et al,&lt;/a&gt;&lt;label for=&#34;snminillm&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snminillm&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2306.08543&#34;&gt;MiniLLM: Knowledge Distillation of Large Language Models&lt;/a&gt; (Gu et al, 2023)&lt;/span&gt; and &lt;a href=&#34;https://arxiv.org/abs/2505.09388&#34;&gt;the Qwen3 team&lt;/a&gt;&lt;label for=&#34;snqwen3tech&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snqwen3tech&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2505.09388&#34;&gt;Qwen3 Technical Report&lt;/a&gt; (Qwen Team, 2025)&lt;/span&gt;. Using the &lt;a href=&#34;https://thinkingmachines.ai/tinker/&#34;&gt;Tinker training API&lt;/a&gt;, we replicate Qwen3’s result of achieving equivalent performance on reasoning benchmarks with on-policy distillation for a fraction the cost of RL.&lt;/p&gt;
&lt;h2 id=&#34;implementation&#34;&gt;Implementation&lt;/h2&gt;
&lt;p&gt;You can follow along with each step of the implementation in the &lt;a href=&#34;https://github.com/thinking-machines-lab/tinker-cookbook/tree/main/tinker_cookbook/recipes/distillation&#34;&gt;Tinker cookbook&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;loss-function-reverse-kl&#34;&gt;Loss function: reverse KL&lt;/h3&gt;
&lt;p&gt;On-policy distillation can use a variety of loss functions for grading the student’s trajectories.&lt;label for=&#34;snoptions&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snoptions&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;See Agarwal et al. for an analysis of various loss function choices.&lt;/span&gt; For simplicity, we choose the per-token reverse KL — the divergence between the student’s ($\pi_\theta$) and teacher’s ($\pi_\text{teacher}$) distribution for each token conditioned on the same prior trajectory:&lt;/p&gt;
$$\text{KL}\Bigl(\pi_\theta \lvert\rvert \pi_\text{teacher}\Bigr) = \mathbb{E}_{x \sim {\pi_\theta}} \Bigl[ \log \pi_\theta(x_{t+1} | x_{1..t}) - \log \pi_\text{teacher}(x_{t+1} | x_{1..t}) \Bigr]$$&lt;p&gt;Our reward function minimizes the reverse KL, which pushes the student to approximate the teacher’s behavior in every state the student finds itself in. When the student behaves identically to the teacher, reverse KL is zero. For simplicity, we use a discount factor of zero: at any given timestep,&lt;label for=&#34;sndiscount&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sndiscount&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Although more mathematically correct, we do not find discount factors &amp;gt; 0 to improve performance in practice, so we choose zero for simplicity.&lt;/span&gt; the student only optimizes the immediate next token, with no consideration for future tokens.&lt;/p&gt;
&lt;p&gt;Reverse KL has natural synergy with RL, which generally optimizes a form of sequence-level reverse KL induced by the reward model. However, unlike most reward models in practice, the reverse KL is “unhackable” in the sense that low KL always corresponds to a high probability of desirable behavior from the teacher model’s point of view. Two other useful properties of reverse KL are that it is “mode seeking”&lt;label for=&#34;snjang&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snjang&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;See &lt;a href=&#34;https://blog.evjang.com/2016/08/variational-bayes.html&#34;&gt;Eric Jang&amp;rsquo;s post&lt;/a&gt; for more discussion of mode seeking behaviors.&lt;/span&gt; — it learns one specific behavior (the teacher’s) instead of spreading its distribution across several suboptimal options — and it reduces &lt;a href=&#34;https://arxiv.org/abs/1506.03099&#34;&gt;exposure bias&lt;/a&gt;.&lt;label for=&#34;snexposure&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snexposure&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/1506.03099&#34;&gt;Scheduled Sampling for Sequence Prediction with Recurrent Neural Networks&lt;/a&gt; (Bengio et al, 2015). See Gu et al. for more discussion.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This approach offers significant compute savings. Since it doesn&amp;rsquo;t require a rollout to finish sampling to calculate the reward, we can use shorter or partial rollouts for training. Querying the teacher’s log probabilities also requires just a single forward pass from the larger model, while the trajectories are generated by the smaller and cheaper student.&lt;/p&gt;
&lt;p&gt;We do not require a separate reward or labeling model either. There could be advantages to combining distillation-based per-token rewards with sequence-level environment rewards; this is an interesting area for potential future research.&lt;/p&gt;
&lt;h3 id=&#34;illustration&#34;&gt;Illustration&lt;/h3&gt;
&lt;p&gt;Below we see a real example of an incorrect student trajectory graded by a teacher. The example is from &lt;a href=&#34;https://simple-bench.com/&#34;&gt;SimpleBench&lt;/a&gt;, and relies on the model making a key observation that the question&amp;rsquo;s premise is important: the correct answer is “B. 0” since ice cubes will melt in a frying pan. The student, &lt;a href=&#34;https://huggingface.co/Qwen/Qwen3-4B-Instruct-2507&#34;&gt;Qwen3-4B-Instruct-2507&lt;/a&gt;, incorrectly treats this as a pure math problem with no consideration of the physical context.&lt;/p&gt;
&lt;figure id=&#34;fig:example-kl-illustration&#34; style=&#34;text-align: center;&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/example-kl-illustration.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;Example trajectory graded by a teacher model. Tokens in darker red correspond to a higher reverse KL.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Darker colors represent tokens that receive a higher penalty from the teacher model, &lt;a href=&#34;https://huggingface.co/Qwen/Qwen3-235B-A22B-Instruct-2507&#34;&gt;Qwen3-235B-A22B-Instruct-2507&lt;/a&gt;, which solves this problem correctly. We see that it penalizes tokens that start phrases which lead the student astray, intuitively corresponding to important &lt;a href=&#34;https://arxiv.org/abs/2506.01939&#34;&gt;&amp;ldquo;forking tokens&amp;rdquo;&lt;/a&gt; for guiding reasoning.&lt;label for=&#34;snforking&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snforking&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2506.01939&#34;&gt;Beyond the 80/20 Rule: High-Entropy Minority Tokens Drive Effective Reinforcement Learning for LLM Reasoning&lt;/a&gt; (Wang et al, 2025)&lt;/span&gt; The final answer, though wrong, isn’t penalized — it is entirely predictable conditional on the whole preceding sequence.&lt;/p&gt;
&lt;h3 id=&#34;pseudocode&#34;&gt;Pseudocode&lt;/h3&gt;
&lt;p&gt;We implement on-policy distillation on top of the &lt;a href=&#34;https://github.com/thinking-machines-lab/tinker-cookbook/blob/main/tinker_cookbook/rl/train.py&#34;&gt;RL script in Tinker&lt;/a&gt;, which already implements sampling, reward computation, and policy gradient-style training.&lt;label for=&#34;snonel&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snonel&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Our implementation can actually be a one-line change on top of RL implementations which use KL regularization: we merely swap out the regularizer model.&lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;i&gt;Initialize teacher client.&lt;/i&gt; The Tinker API enables easily creating different clients for different models, without needing to worry about the utilization of model engines. We use a sampling client, as we do not need to propagate logprobs through the teacher model.&lt;/li&gt;
&lt;li&gt;&lt;i&gt;Sample trajectories.&lt;/i&gt; We sample rollouts from the student exactly as we would in RL. During sampling, RL already computes the student’s logprobs $\log \pi_\theta(x)$ for use as part of the &lt;a href=&#34;https://tinker-docs.thinkingmachines.ai/losses#policy-gradient-importance_sampling&#34;&gt;importance-sampling&lt;/a&gt; loss.&lt;/li&gt;
&lt;li&gt;&lt;i&gt;Compute reward.&lt;/i&gt; We query the teacher client with &lt;code&gt;compute_logprobs&lt;/code&gt; on the sampled trajectories, which returns the teacher&amp;rsquo;s logprobs $\log \pi_\text{teacher}(x)$ on the tokens $x$ sampled by the student.&lt;label for=&#34;snlogit&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snlogit&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;In this post, we do not consider logit (top-k) distillation in any of our experiments, which could be used to further improve compute efficiency.&lt;/span&gt; We then use this to calculate the reverse KL.&lt;/li&gt;
&lt;li&gt;&lt;i&gt;Train with RL.&lt;/i&gt; We set the per-token advantage to the negative reverse KL, and call the RL importance-sampling loss function to perform the training update on the student model.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Initialize teacher client (main):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;teacher_client&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;service_client&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;create_sampling_client&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;base_model&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;teacher_config&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;base_model&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;model_path&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;teacher_config&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;load_checkpoint_path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Sample trajectories (main):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;trajectories&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;do_group_rollout&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;student_client&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;env_group_builder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;sampled_logprobs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;trajectories&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;loss_fn_inputs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;logprobs&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Compute reward (compute_teacher_reverse_kl):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;teacher_logprobs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;teacher_client&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;compute_logprobs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;trajectories&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;reverse_kl&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sampled_logprobs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;teacher_logprobs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;trajectories&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;advantages&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;reverse_kl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Train with RL (train_step):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;training_client&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;forward_backward&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;trajectories&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;loss_fn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;importance_sampling&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the experiments below, we generally apply on-policy distillation to models that have already been mid-trained on specific domain knowledge. This training increases the probability that the student will generate tokens within the teacher’s distribution, though it’s usually far from sufficient for replicating the teacher’s performance.&lt;label for=&#34;snsupport&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snsupport&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;SFT, using forwards KL, adds &lt;a href=&#34;https://en.wikipedia.org/wiki/Support_(mathematics)&#34;&gt;support&lt;/a&gt; for new tokens. Reverse-KL methods can then perform mode seeking within the initialization’s support.&lt;/span&gt; Often, as we&amp;rsquo;ll see in our personalization example, the probability of generating the relevant tokens starts at zero as the student lacks any relevant domain knowledge.&lt;/p&gt;
&lt;p&gt;We use on-policy distillation for post-training, and compare it to other approaches to this last crucial stage of training expert models.&lt;/p&gt;
&lt;h2 id=&#34;distillation-for-reasoning&#34;&gt;Distillation for reasoning&lt;/h2&gt;
&lt;p&gt;We use distillation to train mathematical reasoning in the &lt;a href=&#34;https://huggingface.co/Qwen/Qwen3-8B-Base&#34;&gt;Qwen3-8B-Base&lt;/a&gt; model, using &lt;a href=&#34;https://huggingface.co/Qwen/Qwen3-32B&#34;&gt;Qwen3-32B&lt;/a&gt; as a teacher model. Both the teacher (Qwen3-32B) and student (Qwen3-8B-Base) are &lt;a href=&#34;https://tinker-docs.thinkingmachines.ai/model-lineup&#34;&gt;supported models&lt;/a&gt; on Tinker today, so you can reproduce our experiments with the Tinker cookbook.&lt;label for=&#34;sndeprecation&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sndeprecation&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;b&gt;Update (June 2026):&lt;/b&gt; The teacher (Qwen3-32B) and student (Qwen3-8B-Base) used here have since been &lt;a href=&#34;https://tinker-docs.thinkingmachines.ai/tinker/model-deprecations/&#34;&gt;retired from the Tinker model lineup&lt;/a&gt;. The &lt;a href=&#34;https://github.com/thinking-machines-lab/tinker-cookbook/tree/main/tinker_cookbook/recipes/distillation&#34;&gt;distillation recipes in the Tinker cookbook&lt;/a&gt; have been updated to use &lt;a href=&#34;https://huggingface.co/Qwen/Qwen3.5-9B-Base&#34;&gt;Qwen3.5-9B-Base&lt;/a&gt; as the student and &lt;a href=&#34;https://huggingface.co/Qwen/Qwen3.5-9B&#34;&gt;Qwen3.5-9B&lt;/a&gt; as the teacher, so the experiments remain reproducible.&lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&#34;off-policy-distillation&#34;&gt;Off-policy distillation&lt;/h3&gt;
&lt;p&gt;As mentioned above, all our experiments start with mid-training in the form of off-policy distillation — supervised fine-tuning on a dataset of teacher-generated examples. The dataset used for mathematical reasoning is &lt;a href=&#34;https://huggingface.co/datasets/open-thoughts/OpenThoughts3-1.2M&#34;&gt;OpenThoughts-3&lt;/a&gt;, a collection of reasoning prompts and responses generated by &lt;a href=&#34;https://huggingface.co/Qwen/QwQ-32B&#34;&gt;QwQ-32B&lt;/a&gt; (a reasoning model similar to Qwen3-32B).&lt;/p&gt;
&lt;p&gt;Training the student (Qwen3-8B-Base) on 400k prompts with full fine-tuning achieves a score of 60% on AIME&#39;24, a benchmark of math problems. We can also train with LoRA,&lt;label for=&#34;sn10&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn10&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2106.09685&#34;&gt;LoRA: Low-Rank Adaptation of Large Language Models&lt;/a&gt; (Hu et al, 2021)&lt;/span&gt; though it lags behind full fine-tuning when training on high-volume datasets. In all cases, we see performance increase log-linearly — the initial performance gains are cheap but the latter are costly.&lt;/p&gt;
&lt;figure id=&#34;fig:experiment-off-policy-distillation&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/experiment-off-policy-distillation.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;AIME&#39;24 score over the course of off-policy distillation (SFT). After the initial 50-100K prompts, performance follows a predictable log-linear scaling curve. As predicted in &lt;a href=&#34;https://thinkingmachines.ai/blog/lora/&#34;&gt;LoRA Without Regret&lt;/a&gt;, we observe worse LoRA performance when running large-scale SFT with high batch size.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We can treat the model fine-tuned on 400k prompts as a checkpoint before trying various post-training approaches to increase its performance. We can compare the effort it would take to raise the score on the AIME’24 benchmark from 60% to 70%.&lt;/p&gt;
&lt;p&gt;The default approach is to fine-tune on more prompts, continuing the process of off-policy distillation. Extrapolating the log-linear trend, we estimate that the model would achieve 70% on AIME’24 at approximately 2M prompts. This extrapolation requires the scaling law to hold up without stalling, which isn’t trivial. However, there are examples of large-scale off-policy distillation improving an 8B model’s performance past 70%, such as &lt;a href=&#34;https://huggingface.co/datasets/open-thoughts/OpenThoughts3-1.2M&#34;&gt;OpenThoughts-3&lt;/a&gt; and &lt;a href=&#34;https://huggingface.co/deepseek-ai/DeepSeek-R1-0528-Qwen3-8B&#34;&gt;DeepSeek-R1-0528-Qwen3-8B&lt;/a&gt;.&lt;label for=&#34;sndeepseekdistill&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sndeepseekdistill&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;DeepSeek-R1-0528-Qwen3-8B achieves 86% on the benchmark, with the number of training prompts not specified. Older models (Qwen2.5-7B, Qwen2.5-14B) &lt;a href=&#34;https://arxiv.org/abs/2501.12948&#34;&gt;achieve performance of 55.5% and 69.7%&lt;/a&gt;, respectively, after training on 800k distillation prompts from DeepSeek-R1.&lt;/span&gt; We can use this extrapolation as an optimistic estimate for the cost-performance ratio of off-policy distillation.&lt;/p&gt;
&lt;h3 id=&#34;reinforcement-learning&#34;&gt;Reinforcement learning&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&#34;https://arxiv.org/pdf/2505.09388&#34;&gt;Qwen3 technical report&lt;/a&gt; reaches performance of 67.6% on the benchmark using 17,920 GPU hours of RL on top of a similar SFT initialization. It is hard to compare this directly to the cost of distillation, but given some reasonable assumptions about the SFT training stack, this is similar to the cost of training on 2M off-policy distillation prompts.&lt;/p&gt;
&lt;figure&gt;
&lt;table border=&#34;1&#34; style=&#34;border-collapse: collapse; text-align: center;&#34;&gt;
&lt;thead&gt;
&lt;tr&gt;
  &lt;th style=&#34;text-align:center;&#34;&gt;Method&lt;/th&gt;
  &lt;th style=&#34;text-align:center;&#34;&gt;AIME’24&lt;/th&gt;
  &lt;th style=&#34;text-align:center;&#34;&gt;GPQA-Diamond&lt;/th&gt;
  &lt;th style=&#34;text-align:center;&#34;&gt;GPU Hours&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
  &lt;td style=&#34;text-align: left;&#34;&gt;&lt;span class=&#34;distillation-color-square blue&#34;&gt;&lt;/span&gt;Off-policy distillation&lt;/td&gt;
  &lt;td style=&#34;text-align:center;&#34;&gt;55.0%&lt;/td&gt;
  &lt;td style=&#34;text-align:center;&#34;&gt;55.6%&lt;/td&gt;
  &lt;td style=&#34;text-align:center;&#34;&gt;Unreported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td style=&#34;text-align: left;&#34;&gt;&lt;span class=&#34;distillation-color-square light-blue&#34;&gt;&lt;/span&gt;+ Reinforcement learning&lt;/td&gt;
  &lt;td style=&#34;text-align:center;&#34;&gt;67.6%&lt;/td&gt;
  &lt;td style=&#34;text-align:center;&#34;&gt;61.3%&lt;/td&gt;
  &lt;td style=&#34;text-align:center;&#34;&gt;17,920&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td style=&#34;text-align: left;&#34;&gt;&lt;span class=&#34;distillation-color-square green&#34;&gt;&lt;/span&gt;+ On-policy distillation&lt;/td&gt;
  &lt;td style=&#34;text-align:center;&#34;&gt;74.4%&lt;/td&gt;
  &lt;td style=&#34;text-align:center;&#34;&gt;63.3%&lt;/td&gt;
  &lt;td style=&#34;text-align:center;&#34;&gt;1,800&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;figcaption&gt;From &lt;a href=&#34;https://arxiv.org/abs/2505.09388&#34;&gt;Qwen3 Technical Report&lt;/a&gt;, Table 21.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The Qwen team also reports reaching a higher score of 74.4 on AIME’24 at one-tenth the cost of RL with on-policy distillation, which served as inspiration for our work. We attempt to replicate it below in our basic setup.&lt;/p&gt;
&lt;h3 id=&#34;on-policy-distillation&#34;&gt;On-policy distillation&lt;/h3&gt;
&lt;p&gt;As an alternative to off-policy distillation or RL, we run on-policy distillation as described above.&lt;label for=&#34;snteacher&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snteacher&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;We actually use Qwen3-8B as a teacher, as it performs slightly better. However, for the purpose of comparing compute, we can still measure the FLOPs of the 32B model.&lt;/span&gt; Starting from the 400k SFT checkpoint, on-policy distillation achieves an AIME’24 of 70% in about 150 steps.&lt;label for=&#34;sndprompts&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sndprompts&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;150 steps corresponds to about 77K prompts; we train with 4 samples per prompt.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&#34;fig:experiment-on-policy-distillation-loras&#34; style=&#34;text-align: center;&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/experiment-on-policy-distillation-loras.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;AIME&#39;24 over the course of on-policy distillation. We measure additonal compute in terms of training FLOPs (see below). On-policy distillation is significantly more compute-efficient than SFT, especially for LoRA models. At rank = 32, LoRA trails full finetuning by 13% after SFT, but only 6% after on-policy distillation.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Comparing compute costs across methods is nontrivial, as the ratio of training vs sampling vs log-prob computation cost varies significantly depending on implementation. Below, we compute the cost in terms of FLOPs which penalizes methods that can be effectively parallelized on GPUs. In particular, it overestimates the practical cost of computing log-probs.&lt;/p&gt;
&lt;table border=&#34;1&#34; style=&#34;border-collapse: collapse;&#34;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&#34;text-align:center;&#34;&gt;Method&lt;/th&gt;
      &lt;th style=&#34;text-align:center;&#34;&gt;AIME’24&lt;/th&gt;
      &lt;th style=&#34;text-align:center;&#34;&gt;Teacher FLOPs&lt;/th&gt;
      &lt;th style=&#34;text-align:center;&#34;&gt;Student FLOPs&lt;/th&gt;
      &lt;th style=&#34;text-align:center;&#34;&gt;CE vs SFT-2M&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align:left;&#34;&gt;&lt;i&gt;Initialization: SFT-400K&lt;/i&gt;&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;60%&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;8.5 × 10&lt;sup&gt;20&lt;/sup&gt;&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;3.8 × 10&lt;sup&gt;20&lt;/sup&gt;&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;–&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align:left;&#34;&gt;&lt;span class=&#34;distillation-color-square blue&#34;&gt;&lt;/span&gt;SFT-2M (extrapolated)&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;~70% (extrapolated)&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;3.4 × 10&lt;sup&gt;21&lt;/sup&gt;&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;1.5 × 10&lt;sup&gt;21&lt;/sup&gt;&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;1×&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align:left;&#34;&gt;&lt;span class=&#34;distillation-color-square light-blue&#34;&gt;&lt;/span&gt;Reinforcement learning&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;68%&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;-&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;-&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;≈1×&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align:left;&#34;&gt;&lt;span class=&#34;distillation-color-square green&#34;&gt;&lt;/span&gt;On-policy distillation&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;70%&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;8.4 × 10&lt;sup&gt;19&lt;/sup&gt;&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;8.2 × 10&lt;sup&gt;19&lt;/sup&gt;&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;9-30×&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We find a baseline cost reduction of 9x when the SFT dataset is given, as in our example with OpenThoughts-3, or is amortized across many training runs.&lt;label for=&#34;snceformula&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snceformula&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;CE = (&lt;span class=&#34;distillation-color-square green&#34;&gt;&lt;/span&gt;Student + &lt;span class=&#34;distillation-color-square green&#34;&gt;&lt;/span&gt;Teacher) / (&lt;span class=&#34;distillation-color-square blue&#34;&gt;&lt;/span&gt;Student)&lt;/span&gt; In this case we do not count the cost of teacher FLOPs for off-policy training but do for on-policy, since we must run the teacher model to compute log-probs for the student’s trajectory. Since this computation can be cheaply parallelized across GPUs, the cost reduction in GPU hours is closer to 18x.&lt;/p&gt;
&lt;p&gt;However, we often want to train a small model on a new task for which no off-policy distillation dataset is available. If we include the full cost of the teacher model in off-policy distillation &amp;ndash; ie, including the additional cost of sampling from the teacher model &amp;ndash; the total cost reduction is about 30x.&lt;label for=&#34;snceformula2&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snceformula2&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;CE = (&lt;span class=&#34;distillation-color-square green&#34;&gt;&lt;/span&gt;Student + &lt;span class=&#34;distillation-color-square green&#34;&gt;&lt;/span&gt;Teacher) / (&lt;span class=&#34;distillation-color-square blue&#34;&gt;&lt;/span&gt;Student + &lt;span class=&#34;distillation-color-square blue&#34;&gt;&lt;/span&gt;Teacher)&lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&#34;distillation-for-personalization&#34;&gt;Distillation for personalization&lt;/h2&gt;
&lt;p&gt;In addition to training small models to high performance on common tasks, another use-case for distillation is personalization. Examples include adhering to a particular tone in conversation and format of output, or capabilities like tool use and cost budgeting. We often want to train this behavior in combination with new domain knowledge.&lt;/p&gt;
&lt;p&gt;Training both at once is generally difficult, and light-weight finetunes are often insufficient for this objective,&lt;label for=&#34;snmidtrain&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snmidtrain&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2403.05612&#34;&gt;Unfamiliar Finetuning Examples Control How Language Models Hallucinate&lt;/a&gt; (Kang et al, 2024)&lt;/span&gt; thereby requiring a larger midtrain. Learning post-training behaviors on top of new knowledge requires a complex post-training stack, often consisting of proprietary data and reward models. While this approach is accessible to frontier labs, it can be difficult or prohibitively expensive for other practitioners to replicate.&lt;/p&gt;
&lt;p&gt;In this section, we show that on-policy distillation can be used effectively to post-train specialized behavior. This approach is also applicable to continual learning or “test-time training”: updating models when they are deployed without regressing the base performance. We use an example application of a model mid-trained on our internal company documents.&lt;/p&gt;
&lt;h3 id=&#34;training-an-internal-assistant&#34;&gt;Training an internal assistant&lt;/h3&gt;
&lt;p&gt;A common objective for a custom model is to act as an assistant: to possess expert knowledge in some field and, in addition, reliable assistant-like behavior. We may need separate training for each, especially when the domain of expertise can’t be learned from pre-training data only or when learning it interferes with behavior.&lt;/p&gt;
&lt;p&gt;Our example is an internal company assistant, for which we have two desiderata:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The model is &lt;strong&gt;knowledgeable&lt;/strong&gt; about the domain (company documents). Pretrained models have seen zero internal documents from the company, and therefore can only guess, regardless of model scale. We will measure this using an internal knowledge recall eval (&amp;ldquo;internal QA&amp;rdquo;).&lt;/li&gt;
&lt;li&gt;The model exhibits strong &lt;strong&gt;post-training&lt;/strong&gt; behavior, ie. instruction following. We will measure this with the commonly-used &lt;a href=&#34;https://arxiv.org/abs/2311.07911&#34;&gt;IF-eval&lt;/a&gt;.&lt;label for=&#34;snifeval&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snexample&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2311.07911&#34;&gt;Instruction-Following Evaluation for Large Language Models&lt;/a&gt; (Zhou et al, 2023)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;training-on-new-knowledge-degrades-learned-behavior&#34;&gt;Training on new knowledge degrades learned behavior&lt;/h3&gt;
&lt;p&gt;We will start with Qwen3-8B, rather than the base model. Qwen3-8B is post-trained on useful skills for an assistant, such as instruction following and reasoning with RL. Prior research has shown that such reinforcement learning only trains small subnetworks of the original model,&lt;label for=&#34;snmukherjee2025&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snmukherjee2025&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2505.11711&#34;&gt;Reinforcement Learning Finetunes Small Subnetworks in Large Language Models&lt;/a&gt; (Mukherjee et al, 2025)&lt;/span&gt; and thus can be fragile when the network is further trained on a large amount of data. We study the extent to which it happens, and how the desired behavior can be recovered.&lt;/p&gt;
&lt;p&gt;In order to reduce such catastrophic forgetting, a common approach in mid-training is to mix in &amp;ldquo;background data&amp;rdquo; from the original model&amp;rsquo;s pretraining distribution.&lt;label for=&#34;snmidtrain&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snmidtrain&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2510.14865&#34;&gt;Midtraining Bridges Pretraining and Posttraining Distributions&lt;/a&gt; (Liu et al, 2025)&lt;/span&gt; In this case, we don&amp;rsquo;t have access to Qwen3&amp;rsquo;s pretraining distribution. Therefore, we consider a stronger and more expensive baseline: we take &lt;a href=&#34;https://huggingface.co/datasets/allenai/tulu-3-sft-mixture&#34;&gt;Tulu3&lt;/a&gt;&lt;label for=&#34;sntulu&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sntulu&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2411.15124&#34;&gt;Tulu 3: Pushing Frontiers in Open Language Model Post-Training&lt;/a&gt; (Ivison et al, 2024)&lt;/span&gt; prompts &amp;ndash; a broad chat and instruction-following dataset &amp;ndash; and re-sample them with Qwen3-8B in order to act as chat background data.&lt;/p&gt;
&lt;p&gt;This &amp;ldquo;on-policy&amp;rdquo; background data sampled by Qwen3-8B acts as a forwards KL regularizer, reinforcing the model&amp;rsquo;s original behavior throughout mid-training. We find sampling from Qwen3-8B is better than Qwen3-32B for preserving chat capabilities throughout mid-training, highlighting the sensitivity of the data source; similar on-policy SFT results have been found in Chen et al.&lt;label for=&#34;snchenforget&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snchenforget&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2510.18874&#34;&gt;Retaining by Doing: The Role of On-Policy Data in Mitigating Forgetting&lt;/a&gt; (Chen et al, 2025)&lt;/span&gt; We hypothesize this approach can be even more effective than having access to the original pretraining data distribution, at the cost of having to sample a large-scale dataset.&lt;/p&gt;
&lt;p&gt;We then fine-tune Qwen3-8B on different mixes of our internal documents and chat data. Increasing the proportion of document data straightforwardly improves the model’s knowledge. However, although mixing in at least 30% of chat data helps preserve most instruction-following ability, there is no weighting which maintains the original performance on IF-eval.&lt;label for=&#34;snifevalperf&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snifevalperf&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;This is true even if the SFT dataset contains 100% chat data. We address this further in the discussion on continual learning.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&#34;fig:experiment-personalization-midtrain&#34; style=&#34;text-align: center;&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/experiment-personalization-midtrain.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;Sweeping over the ratio of internal documents: background chat data during mid-training. Although mixing in a little chat data helps to prevent a catastrophic regression, no weight maintains the original IF-eval performance.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;For any given mix, we observe IF-eval performance degrades during fine-tuning. This compromises our ability to use longer training to help specialize the model further.&lt;label for=&#34;snoverparametrized&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snoverparametrized&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Directionally, we may expect an overparametrized model trained on some dataset to update its behavior only the context of the dataset without impacting what it does in other contexts. However, we do not observe this in practice, as training on raw document data regresses performance even in the QA context.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&#34;fig:experiment-midtrain-if-eval&#34; style=&#34;text-align: center;&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/experiment-midtrain-if-eval.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;IF-eval decreases during midtraining across all mixes of data. When we use a linear learning rate (pictured), the degradation eventually flattens and slowly begins to recover as the learning rate decays. However, performance never fully recovers.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;An alternative commonly-used approach is to use LoRA in order to constrain the parameter update, thereby reducing the possibility of catastrophic forgetting. However, this approach is still insufficient for preserving IF-eval, and the LoRA learns less.&lt;label for=&#34;snbiderman&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snbiderman&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2405.09673&#34;&gt;LoRA Learns Less and Forgets Less&lt;/a&gt; (Biderman et al, 2024)&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&#34;fig:experiment-midtrain-lora&#34; style=&#34;text-align: center;&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/experiment-midtrain-lora.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;When used for our personalization midtrain on top of the post-trained Qwen3-8B, LoRA learns less (knowledge) and still forgets its original post-training behaviors.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&#34;on-policy-distillation-recovers-post-training-behavior&#34;&gt;On-policy distillation recovers post-training behavior&lt;/h3&gt;
&lt;p&gt;Next, we seek to restore instruction-following behavior after fine-tuning on internal documents. This behavior was originally trained with RL which is expensive and, as we have seen, fragile. Instead, we run on-policy distillation with the earlier version of the model, Qwen3-8B, as the teacher on &lt;a href=&#34;https://huggingface.co/datasets/allenai/tulu-3-sft-mixture&#34;&gt;Tulu3&lt;/a&gt; prompts. Note that this phase of training has no relation to the internal document data, and is designed solely to recover instruction following.&lt;/p&gt;
&lt;p&gt;The use of an earlier version of the model as a teacher to “re-invoke” capabilities lost during fine-tuning makes on-policy distillation very promising for continuous learning. We could alternate between phases of fine-tuning on new data and distillation to recover behavior to allow our model to learn and stay up-to-date on knowledge over time. This phase-alternating approach has previously been explored by Cobbe et al.&lt;label for=&#34;snbiderman&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snbiderman&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2009.04416&#34;&gt;Phasic Policy Gradient&lt;/a&gt; (Cobbe et al, 2020)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;After fine-tuning on an 70-30 mix of internal document data and chat data, on-policy distillation recovers nearly full performance on IF-eval without losing any knowledge; we also observe some positive transfer between chat capabilities and the model&amp;rsquo;s &amp;ldquo;knowledge&amp;rdquo; performance on the internal QA eval.&lt;/p&gt;
&lt;figure&gt;
&lt;table border=&#34;1&#34; style=&#34;border-collapse: collapse;&#34;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&#34;text-align:center;&#34;&gt;Model&lt;/th&gt;
      &lt;th style=&#34;text-align:center;&#34;&gt;Internal QA Eval (Knowledge)&lt;/th&gt;
      &lt;th style=&#34;text-align:center;&#34;&gt;IF-eval (Chat)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align:left;&#34;&gt;&lt;i&gt;Qwen3-8B&lt;/i&gt;&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;18%&lt;/td&gt;
      &lt;td style=&#34;text-align:center; background-color: #E2FAF3;&#34;&gt;85%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align:left;&#34;&gt;&lt;i&gt;+ midtrain (100%)&lt;/i&gt;&lt;/td&gt;
      &lt;td style=&#34;text-align:center; background-color: #E2FAF3;&#34;&gt;43%&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;45%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align:left;&#34;&gt;&lt;span class=&#34;distillation-color-square blue&#34;&gt;&lt;/span&gt;+ midtrain (70%)&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;36%&lt;/td&gt;
      &lt;td style=&#34;text-align:center;&#34;&gt;79%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align:left;&#34;&gt;&lt;span class=&#34;distillation-color-square green&#34;&gt;&lt;/span&gt;+ midtrain (70%) + distill&lt;/td&gt;
      &lt;td style=&#34;text-align:center; background-color: #E2FAF3;&#34;&gt;41%&lt;/td&gt;
      &lt;td style=&#34;text-align:center; background-color: #E2FAF3;&#34;&gt;83%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;figcaption&gt;Domain-specific (Internal QA eval) and chat (IF-eval) performance after mid-training. Although mid-training forgets the post-trained behaviors of Qwen3-8B, they are cheaply restored via on-policy distillation, alongside the additional knowledge learned via the mid-train.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In essence, we have treated the language model itself as a reward model, with high-probability behaviors being rewarded.&lt;label for=&#34;sndpo&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sndpo&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2305.18290&#34;&gt;Direct Preference Optimization: Your Language Model is Secretly a Reward Model&lt;/a&gt; (Rafailov et al, 2023)&lt;/span&gt; This has connections to inverse RL: high-probability behaviors correspond to advantageous rewards in an assumed underlying preference model.&lt;label for=&#34;snirl&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snirl&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://ai.stanford.edu/~ang/papers/icml00-irl.pdf&#34;&gt;Algorithms for Inverse Reinforcement Learning&lt;/a&gt; (Ng and Russell, 2000)&lt;/span&gt; Any instruction-tuned open-weight model can be used as a reward model in this sense; we merely need access to the &lt;code&gt;compute_logprobs&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;Distillation as a tool for integrating behaviors and knowledge has also been explored for hybrid reasoning models (&lt;a href=&#34;https://arxiv.org/abs/2505.09388&#34;&gt;Qwen3&lt;/a&gt;) and specialist distillation.&lt;label for=&#34;snspecialist&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snspecialist&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://github.com/deepseek-ai/DeepSeek-V3.2-Exp/blob/main/DeepSeek_V3_2.pdf&#34;&gt;DeepSeek-V3.2-Exp: Boosting Long-Context Efficiency with DeepSeek Sparse Attention&lt;/a&gt; (DeepSeek-AI Team, 2025)&lt;/span&gt; As our and &lt;a href=&#34;https://arxiv.org/abs/2510.18874&#34;&gt;Chen et al.&amp;rsquo;s&lt;/a&gt; results suggest, on-policy learning can be a crucial tool for augmenting similar distillation-based &amp;ldquo;model merging&amp;rdquo; setups.&lt;/p&gt;
&lt;h2 id=&#34;discussion&#34;&gt;Discussion&lt;/h2&gt;
&lt;h3 id=&#34;dense-supervision-greatly-improves-compute-efficiency&#34;&gt;Dense supervision greatly improves compute efficiency&lt;/h3&gt;
&lt;p&gt;Reinforcement learning and on-policy distillation both learn via reverse KL, pruning the space of actions present in the base policy. The difference is in the density of reward. In &lt;a href=&#34;https://thinkingmachines.ai/blog/lora/&#34;&gt;LoRA Without Regret&lt;/a&gt; we presented the information-theoretic perspective that reinforcement learning only teaches $O(1)$ bits per episode. In contrast, distillation teaches $O(N)$ bits per episode, where $N$ is the number of tokens. Can we quantify the training efficiency gained through denser reward?&lt;/p&gt;
&lt;p&gt;We ran an experiment for making a direct comparison between the two:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start with Qwen3-8B-Base (no additional SFT).&lt;/li&gt;
&lt;li&gt;Run RL on DeepMath, matching our procedure from &lt;a href=&#34;https://thinkingmachines.ai/blog/lora/&#34;&gt;LoRA Without Regret&lt;/a&gt;. We use a LoRA rank of 128. The resultant model is the teacher for distillation.&lt;/li&gt;
&lt;li&gt;On-policy distill from the RL-trained model (2) back into the base model (1).&lt;/li&gt;
&lt;/ol&gt;
&lt;figure id=&#34;fig:experiment-self-distillation&#34; style=&#34;text-align: center;&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/experiment-self-distillation.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;Starting from the same initialization, on-policy distillation can learns the RL-trained policy in approximately 7-10x fewer gradient steps, which corresponds to a compute efficiency of 50-100x.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We see that distillation reaches the teacher’s level of performance approximately 7-10x faster than RL with matched model architecture (LoRA rank 128). The reverse KL decreases to near-zero and the AIME score is recovered in under 10 gradient steps, while RL took 70 steps to reach that level.&lt;/p&gt;
&lt;p&gt;Cumulatively, the reduction in compute required is on the order of 50-100x:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;While RL requires training at approximately the evaluation context (in order for the policy to learn the context limit and not incur format penalty), distillation learns reasonably at shorter context lengths, as there is no sharp cutoff in reward between a trajectory which has finished sampling and a trajectory that continues.&lt;/li&gt;
&lt;li&gt;When the SFT initialization is strong,&lt;label for=&#34;snbs&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snbs&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;ie: when the teacher policy is within the support of the student policy. When this is not true, as in &amp;ldquo;Distillation for reasoning&amp;rdquo;, we require a significantly larger batch size.&lt;/span&gt; on-policy distillation works effectively with much smaller batch sizes since it provides significantly more bits per episode, thereby reducing gradient noise.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Although it is generally difficult to train reinforcement learning models with process supervision, these results indicate that as a broad direction, process supervision and dense rewards have the potential to improve learning efficiency by an order of magnitude. This matches earlier results in RL research from Lightman et al.&lt;/p&gt;
&lt;h3 id=&#34;distillation-can-effectively-reuse-training-data-for-data-efficiency&#34;&gt;Distillation can effectively reuse training data for data efficiency&lt;/h3&gt;
&lt;p&gt;For practitioners, collecting large datasets of training prompts can be difficult and time-consuming. Therefore, we want to be able to reuse prompts several times in training. With RL, training multiple epochs on the same prompt often leads to simple memorization of the final answer, especially with large models.&lt;label for=&#34;snleviathan2023&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snleviathan2023&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2504.20571&#34;&gt;&amp;ldquo;Reinforcement Learning for Reasoning in Large Language Models with One Training Example&amp;rdquo;&lt;/a&gt; (Wang et al, 2025) presents a positive result, however, in some settings.&lt;/span&gt; In contrast, on-policy distillation learns to approximate the teacher’s complete distribution by minimizing reverse KL, rather than memorizing a single answer. This allows us to train many samples from the same prompt.&lt;/p&gt;
&lt;p&gt;We repeat the above experiment of training Qwen3-8B-Base on math, but now with only a single randomly chosen prompt from the dataset.&lt;label for=&#34;snsingle&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snsingle&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Prompt: &amp;ldquo;Evaluate the limit: $\lim_{x \to \infty} \sqrt{x} \left( \sqrt[3]{x+1} - \sqrt[3]{x-1} \right)$&amp;quot;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We train on this prompt for 20 consecutive steps, each with a batch of 256 rollouts, for 5120 graded sequences in total. We train on the same prompt for multiple steps in sequential fashion, which normally leads to overfitting. Though this is naturally less compute-efficient, we do approximately match the performance of the teacher model despite only training on a single prompt.&lt;/p&gt;
&lt;figure id=&#34;fig:experiment-self-distillation-multiepoch&#34; style=&#34;text-align: center;&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/experiment-self-distillation-multiepoch.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;In this example, multi-epoch training on top of one training example is sufficient to distill the teacher&#39;s AIME&#39;24 performance. Our default configuration (also used in the personalization experiments) runs on-policy distillation with 64 prompts / batch, and 4 samples / prompt. All methods shown are trained with 256 samples / batch. Note that the right chart is showing training KL, hence it is natural for 1 prompt total to outperform 1 prompt / batch.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&#34;rl-searches-in-the-space-of-semantic-strategies&#34;&gt;RL searches in the space of semantic strategies&lt;/h3&gt;
&lt;p&gt;We have seen that on-policy distillation can replicate the learning provided by RL with much fewer steps of training. One interpretation of this result is that, unlike pre-training, RL doesn’t spend a lot of compute on the gradient steps themselves. We should think of RL as spending most of its compute on &lt;em&gt;search&lt;/em&gt; — rolling out a policy and assigning credit — rather than on making updates.&lt;label for=&#34;snbitterlessonmentioned&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snbitterlessonmentioned&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;From &lt;a href=&#34;http://www.incompleteideas.net/IncIdeas/BitterLesson.html&#34;&gt;The Bitter Lesson&lt;/a&gt; (Rich Sutton): “breakthrough progress eventually arrives by an opposing approach based on scaling computation by &lt;b&gt;search&lt;/b&gt; and &lt;b&gt;learning&lt;/b&gt;&amp;quot;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Pre-training via stochastic gradient descent is exploring the high-dimensional parameter space. Pre-training requires a vast amount of information and is very difficult to distill, in part because the parameter space is somewhat unique to each network.&lt;label for=&#34;sngudibande&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sngudibande&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/1803.03635&#34;&gt;&amp;ldquo;The Lottery Ticket Hypothesis: Finding Sparse, Trainable Neural Networks”&lt;/a&gt; (Frankle and Carbin, 2018)&lt;/span&gt; The gradient steps required for pretraining are extremely computationally expensive and time-consuming.&lt;/p&gt;
&lt;p&gt;In contrast, we should think of RL as exploring the space of semantic strategies.&lt;label for=&#34;snexploration&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snexploration&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Note that exploration over strategies is subtlely different from exploration over outcomes; RL requires the base model to have nonzero success to start with, and therefore has already &amp;ldquo;found the outcome&amp;rdquo;, but the strategy can be refined over the course of RL to make successful outcomes more likely.&lt;/span&gt; At every step, RL tries a small modification of some strategy it has found in the past. Rather than exploring in the parameter space, it “stumbles” onto new strategies by luck — it is randomly sampling from the set of weights it already has.&lt;/p&gt;
&lt;p&gt;Once a good strategy is found, distillation serves as a shortcut for learning it: on-policy distillation does not need to model the intermediate strategies during the curriculum of RL, but rather only the final strategy learned. If we are only interested in the final strategy (common in production use-cases), we need not spend the compute to model all the intermediate ones.&lt;/p&gt;
&lt;p&gt;Consider an analogy: in science research, we spend a lot of time and resources looking for answers and exploring new ideas. Once a result is discovered, it is much simpler to teach it to others by expressing it in natural language. We can contrast this to intuitive physical skills such as playing a sport. They are much harder to teach to others, since the knowledge exists in an innate language (e.g., muscle memory) that is only readily understood by ourselves. Sports are only learned with repeated practice.&lt;/p&gt;
&lt;h3 id=&#34;on-policy-learning-as-a-tool-for-continual-learning&#34;&gt;On-policy learning as a tool for continual learning&lt;/h3&gt;
&lt;p&gt;In the section on distillation for personalization, we explored the ability of on-policy distillation to re-introduce specialized trained behaviors into the model. This generalizes to a broader set of continual learning tasks, which require acquiring new knowledge without degrading prior capabilities.&lt;/p&gt;
&lt;p&gt;Prior work has found on-policy learning (RL) forgets less than off-policy learning.&lt;label for=&#34;snrazor&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;snrazor&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2509.04259&#34;&gt;&amp;ldquo;RL&amp;rsquo;s Razor: Why Online Reinforcement Learning Forgets Less&amp;rdquo;&lt;/a&gt; (Shenfeld et al, 2025)&lt;/span&gt; However, RL only shapes behavior — it cannot teach new knowledge well, and thus can’t be sufficient for continual learning.&lt;/p&gt;
&lt;p&gt;In the above section, we saw that SFT (including off-policy distillation) fails at scaffolding continual learning because it degrades behavior. We investigate this more closely and demonstrate this with a direct example. Similarly to above, we build a dataset by taking Tulu3 prompts and sampling from Qwen3-32B at &lt;code&gt;temperature = 1.0&lt;/code&gt; and no further modifications. This dataset therefore has a KL of zero against Qwen3-32B.&lt;label for=&#34;kl0&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;kl0&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;The importance of &amp;ldquo;truly on-policy&amp;rdquo; KL=0 data was also explored in our previous post, &lt;a href=&#34;https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/&#34;&gt;Defeating Nondeterminism in LLM Inference&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;What happens when we run SFT on this dataset of a model’s own samples? We see that any practical learning rate that’s greater than zero leads to a degradation of performance on the instruction-following evaluation!&lt;/p&gt;
&lt;figure id=&#34;fig:experiment-sft-on-policy&#34; style=&#34;text-align: center;&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/on-policy-distillation/svgs/experiment-sft-on-policy.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;Running SFT on top of Qwen3-32B&#39;s own samples degrades performance. We use the same learning rate as from the personalization section, which was swept for practical performance considerations. A linear learning rate can prevent forwards KL / IF-eval from regressing indefinitely, but does not recover performance before the LR decays to zero.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A possible explanation for this is that while KL divergence is 0 in expectation, every finite batch will exhibit a slightly different distribution in practice. Training on these finite batches causes a non-zero gradient update, which then diverges the updated model’s policy from that of its original state. This process turns training on one’s own samples into off-policy training over time, which causes the same error accumulation and divergence over long sequences that happens with off-policy training.&lt;/p&gt;
&lt;p&gt;On-policy distillation always stays on-policy, and since the teacher stays fixed, the student converges on the teacher’s desirable behavior, without regressing in the self-distillation setting as SFT does. This makes on-policy distillation a very promising tool for continual learning.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We have explored the application of on-policy distillation for applications such as training a small model for math reasoning or a continuously-learning assistant. We compared on-policy distillation to two other approaches to post-training: off-policy distillation, and on-policy RL. We find that on-policy distillation combines the best of both worlds: the reliable performance of on-policy training, with the cost-efficiency of a dense reward signal.&lt;/p&gt;
&lt;p&gt;Post-training is a crucial part of reaching frontier model capabilities. By leveraging on-policy sampling from the student with dense supervision from a teacher, the on-policy distillation recipe reaches those capabilities at a fraction of the cost of frontier high-compute RL runs.&lt;/p&gt;
&lt;p&gt;Our implementation can be found in the &lt;a href=&#34;https://github.com/thinking-machines-lab/tinker-cookbook/tree/main/tinker_cookbook/recipes/distillation&#34;&gt;Tinker cookbook&lt;/a&gt;. Our work explored simple and straightforward instantiations of on-policy distillation to clearly showcase its advantages. We hope to continue research on new applications of distillation, new methods for improving teacher supervision, and ways to improve data efficiency and continual learning.&lt;/p&gt;
&lt;p&gt;At Thinking Machines, our mission is to empower people with AI models that combine frontier performance with adaptability and personalization. On-policy distillation is a potent tool for achieving that.&lt;/p&gt;
&lt;h2 id=&#34;citation&#34;&gt;Citation&lt;/h2&gt;
&lt;p&gt;Please cite this work as:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Lu, Kevin and Thinking Machines Lab, &amp;#34;On-Policy Distillation&amp;#34;,
Thinking Machines Lab: Connectionism, Oct 2025.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or use the BibTeX citation:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;@article{lu2025onpolicydistillation,
  author = {Kevin Lu and Thinking Machines Lab},
  title = {On-Policy Distillation},
  journal = {Thinking Machines Lab: Connectionism},
  year = {2025},
  note = {https://thinkingmachines.ai/blog/on-policy-distillation},
  doi = {10.64434/tml.20251026},
}
&lt;/code&gt;&lt;/pre&gt;</description>
    </item>
    
    <item>
      <title>LoRA Without Regret</title>
      <link>https://thinkingmachines.ai/blog/lora/</link>
      <pubDate>Mon, 29 Sep 2025 00:00:00 +0000</pubDate>
      
      <guid>https://thinkingmachines.ai/blog/lora/</guid>
      <description>&lt;script src=&#34;https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js&#34;&gt;&lt;/script&gt;
&lt;p&gt;Today&amp;rsquo;s leading language models contain upwards of a trillion parameters, pretrained on tens of trillions of tokens. Base model performance keeps improving with scale, as these trillions are necessary for learning and representing all the patterns in written-down human knowledge.&lt;/p&gt;
&lt;p&gt;In contrast, post-training involves smaller datasets and generally focuses on narrower domains of knowledge and ranges of behavior. It seems wasteful to use a terabit of weights to represent updates from a gigabit or megabit of training data. This intuition has motivated parameter efficient fine-tuning (PEFT), which adjusts a large network by updating a much smaller set of parameters.&lt;/p&gt;
&lt;p&gt;The leading PEFT method is low-rank adaptation, or LoRA. LoRA replaces each weight matrix W from the original model with a modified version $W&#39; = W + \gamma BA$, where B and A are matrices that together have far fewer parameters than W, and $\gamma$ is a constant scaling factor. In effect, LoRA creates a low-dimensional representation of the updates imparted by fine-tuning.&lt;/p&gt;
&lt;p&gt;LoRA may offer advantages in the cost and speed of post-training, and there are also a few operational reasons to prefer it to full fine-tuning (henceforth, FullFT):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Multi-tenant serving.&lt;/strong&gt; Since LoRA trains an adapter (i.e., the A and B matrices) while keeping the original weights unchanged, a single inference server can keep many adapters (different model versions) in memory and sample from them simultaneously in a batched way.&lt;label for=&#34;sn12&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn12&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2310.18547&#34;&gt;Punica: Multi-Tenant LoRA Serving&lt;/a&gt; (Chen, Ye, et al, 2023)&lt;/span&gt; Modern inference engines such as vLLM and SGLang implement this feature.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Layout size for training.&lt;/strong&gt; When fine-tuning the whole model, the optimizer state needs to be stored along with the original weights, often at higher precision. As a result, FullFT usually requires an order of magnitude more accelerators than sampling from the same model does, and thus a different layout.&lt;label for=&#34;sn13&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn13&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;For training, besides storing the weights, we typically need to store gradients and optimizer moments for all of the weights; moreover, these variables are often stored in higher precision (float32) than what&amp;rsquo;s used to store the weights for inference (bfloat16 or lower).&lt;/span&gt; Since LoRA trains far fewer weights and uses far less memory, it can be trained on a layout only slightly larger than what is used for sampling. This makes training more accessible, and often more efficient.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ease of loading and transfer.&lt;/strong&gt; With fewer weights to store, LoRA adapters are fast and easy to set up or transfer between machines.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These reasons are sufficient to explain the growing popularity of LoRA since the publication of the original LoRA paper in 2021.&lt;label for=&#34;sn10&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn10&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2106.09685&#34;&gt;LoRA: Low-Rank Adaptation of Large Language Models&lt;/a&gt; (Hu et al, 2021)&lt;/span&gt; However, the literature is unclear on how well LoRA performs relative to FullFT.&lt;/p&gt;
&lt;p&gt;There is agreement that LoRA underperforms in settings that resemble pre-training,&lt;label for=&#34;sn11&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn11&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2405.09673&#34;&gt;LoRA Learns Less and Forgets Less&lt;/a&gt; (Biderman et al, 2024)&lt;/span&gt; namely those with very large datasets that exceed the storage limits of LoRA parameters. But for dataset sizes that are typical in post-training, LoRA has sufficient capacity to store the essential information. However, this fact makes no guarantees regarding sample efficiency and compute efficiency. The question is: &lt;em&gt;can LoRA match the performance of full fine-tuning, and if so, under which conditions?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In our experiments, we find that indeed, when we get a few key details right, LoRA learns with the same sample efficiency as FullFT and achieves the same ultimate performance.&lt;/p&gt;
&lt;h2 id=&#34;what-matters-for-lora&#34;&gt;What matters for LoRA&lt;/h2&gt;
&lt;p&gt;This article covers a series of supervised fine-tuning and reinforcement learning experiments we conducted to determine the conditions under which LoRA matches FullFT efficiency. To this end, we did a few things differently from previous experiments on LoRA:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;We investigated the general relationship between training set size and number of LoRA parameters, rather than focusing on specific datasets and tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In supervised learning, we measured &lt;em&gt;log loss&lt;/em&gt; rather than employing sampling-based evals, with the same goal of generality in mind. Log loss measurement gives clean results and scaling laws over ranges of training steps and training parameters.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We find that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;For supervised fine-tuning on small-to-medium-sized instruction-tuning and reasoning datasets, LoRA performs the same as full fine-tuning.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For datasets that exceed LoRA capacity, LoRA underperforms FullFT. Rather than the loss reaching a distinct floor that it can&amp;rsquo;t go below, LoRA results in worse training efficiency that depends on the relationship between model capacity to dataset size.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In some scenarios, LoRA is less tolerant of large batch sizes than full fine-tuning — it pays a larger penalty in loss as batch size increases beyond some point. This penalty is not mitigated by increasing the LoRA rank; it is a property of the product-of-matrices parametrization, which has different training dynamics than optimizing the original weight matrix.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Even in small data settings, LoRA performs better when applied to all weight matrices, especially MLP and MoE layers. Attention-only LoRA underperforms even when we match the number of trainable parameters by using higher rank for attention-only LoRA.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LoRA performs equivalently to FullFT for reinforcement learning even with small ranks. We find that RL requires very low capacity, a result we anticipated based on information-theoretical arguments.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We also studied the impact of hyperparameters used for LoRA on its learning rate relative to full fine-tuning. We examine some invariances in hyperparameters like init scales and multipliers, and explain why the 1/r prefactor makes the optimal learning rate (LR) approximately independent of rank. We also show experimentally how the optimal LR for LoRA relates to the optimal LR for FullFT.&lt;/p&gt;
&lt;p&gt;The outcome of our experiments is the characterization of a &amp;ldquo;low-regret regime&amp;rdquo; where LoRA performs similarly to FullFT in terms of dataset size and LoRA parameters. We found this regime covers most post-training scenarios, opening the door to the use of efficient fine-tuning in many applications.&lt;/p&gt;
&lt;h2 id=&#34;methods-and-results&#34;&gt;Methods and results&lt;/h2&gt;
&lt;p&gt;We designed our experiments to measure in detail the relative performance of LoRA compared to FullFT across a range of conditions. Here are some details of our experimental setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;We varied the LoRA rank over three orders of magnitude, with rank between 1 and 512, and compared these to full fine-tuning.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To eliminate potential confounds from using a suboptimal learning rate, we swept the LR for each experimental condition. We used constant learning rate schedule (no warmup or cooldown).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Our experiments used Llama 3 series models&lt;label for=&#34;sn24&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn24&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2407.21783&#34;&gt;The Llama 3 Herd of Models&lt;/a&gt; (Dubey et al, 2024)&lt;/span&gt; and Qwen3 models&lt;label for=&#34;sn25&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn25&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2505.09388&#34;&gt;Qwen3 Technical Report&lt;/a&gt; (Qwen Team, 2025)&lt;/span&gt;, including a mixture of experts (MoE) model.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The main supervised learning experiments used the Tulu3&lt;label for=&#34;sn19&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn19&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2411.15124&#34;&gt;Tulu 3: Pushing Frontiers in Open Language Model Post-Training&lt;/a&gt; (Ivison et al, 2024)&lt;/span&gt; and OpenThoughts3&lt;label for=&#34;sn20&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn20&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2506.04178&#34;&gt;OpenThoughts: Data Recipes for Reasoning Models&lt;/a&gt; (Guha et al, 2025)&lt;/span&gt; datasets, focused on instruction following and reasoning, respectively. The two sets differ significantly in scope, structure, and application, supporting the generality of our results.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Our RL experiments used mathematical reasoning tasks with answer correctness as the reward.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;lora-rank&#34;&gt;LoRA rank&lt;/h3&gt;
&lt;p&gt;We trained for a single epoch on the Tulu3 dataset and a subset of the OpenThoughts3 datasets. For each dataset and model size, we swept over LoRA rank and learning rate. In the plots below, we draw one colored line for each rank, where the line is obtained by taking the pointwise minimum over all learning rates at each training step:&lt;/p&gt;
&lt;figure id=&#34;fig:rank-sweep&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/lora/svg/fig1.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;LoRA training curves for various ranks on Tulu3 and OpenThoughts3 datasets. FullFT and high-rank LoRAs have similar learning curves with loss decreasing linearly with the logarithm of steps. Lower-rank LoRAs fall off the minimum-loss curve when the adapter runs out of capacity. In the bottom plots (1B model) high-rank LoRA performs better than FullFT on one dataset and worse on the other. There might be some random variation in how LoRA performs on different datasets, due to differences in training dynamics or generalization behavior.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We see that FullFT and high-rank LoRAs have similar learning curves with loss decreasing linearly with the logarithm of the number of steps. Medium and low-rank LoRAs fall off the minimum-loss learning curves at some threshold of steps that correlates with rank. Intuitively, learning slows down when the adapter runs out of capacity, which in turn is determined by rank.&lt;/p&gt;
&lt;p&gt;Next, we plot how loss changes with LR to check that our sweep covers the best learning rate for each rank.&lt;/p&gt;
&lt;figure id=&#34;fig:lr-sweep&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/lora/svg/fig2.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;Learning rate versus final loss for various LoRA ranks on Tulu3. Minimum loss is approximately the same for high rank LoRA and FullFT. Optimal LR is 10 times higher for LoRA.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We find that the optimal learning rate for FullFT is lower by a factor of 10 than for high-rank LoRAs.&lt;label for=&#34;sn14&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn14&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;See Biderman et al. (2024), Figure S1, for an experiment with sampling evals, which finds a similar 10x ratio.&lt;/span&gt; We&amp;rsquo;ll return to this in our discussion of LoRA hyperparameters later on.&lt;/p&gt;
&lt;p&gt;The optimal LR seems to be similar for all the LoRA runs across different ranks; we give a theoretical explanation for this finding below. However, there does seem to be some rank dependence, with lower optimal LR for rank=1 than for higher-rank LoRAs. The optimal LR changes by a factor of less than 2 between rank=4 and rank=512.&lt;/p&gt;
&lt;h3 id=&#34;batch-size-effects&#34;&gt;Batch size effects&lt;/h3&gt;
&lt;p&gt;We found that in some settings, LoRA is less tolerant of large batch sizes than FullFT. The performance gap grows with larger batch sizes, independent of rank. For this next experiment, we used a small 10,000-example subset of OpenThoughts3.&lt;/p&gt;
&lt;figure id=&#34;fig:bs-sweep&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/lora/svg/fig3.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;Batch size effects on LoRA vs FullFT performance. Left: Learning curves for different batch sizes show a persistent gap between LoRA (dashed) and FullFT (solid) at large batch sizes. Right: Final loss as a function of batch size shows LoRA pays a larger penalty for increased batch size.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The left-hand plot in Figure 3 shows a persistent gap between the LoRA (dashed lines) and FullFT (solid line) learning curves at large batch sizes. The gap is smaller and shrinks over time for the smaller batch size of 32.&lt;/p&gt;
&lt;p&gt;The right-hand chart plots final loss as a function of batch size. We see the gap in loss for LoRA increasingly diverging from FullFT for larger batch sizes.&lt;/p&gt;
&lt;p&gt;The learning gap at large batches doesn&amp;rsquo;t seem to depend on rank, but rather seems to be a property of LoRA. The likely reason is that the product-of-matrices parametrization (BA) has less favorable optimization dynamics on this dataset than the full matrix (W). However, both LoRA and FullFT achieve their best loss at smaller batch sizes, so this gap may not matter as much in practice.&lt;/p&gt;
&lt;h2 id=&#34;layers-where-lora-is-applied&#34;&gt;Layers Where LoRA Is Applied&lt;/h2&gt;
&lt;p&gt;We investigated the effects of applying LoRA to different layers in the network. The original paper by Hu et al. recommended applying LoRA only to the attention matrices, and many subsequent papers followed suit, though a recent trend has been to apply it to all layers.&lt;label for=&#34;sn15&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn15&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Similar to our results, the QLoRA paper also found that LoRA performed worse than MLP or MLP+attention, though they found that MLP+attention &amp;gt; MLP &amp;gt; attention, whereas we found the first two to be roughly equal.&lt;/span&gt; Indeed, we achieved far better results when applying LoRA to all layers, in particular, the MLP (including MoE) layers. In fact, applying LoRA to the attention matrices shows no additional benefits beyond applying it to the MLPs only.&lt;label for=&#34;sn16&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn16&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Biderman et al. (2024) obtained a similar result, with attention-only LoRA providing no additional benefit on top of MLP-only.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&#34;fig:fig4&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/lora/svg/fig4.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;Attention-only LoRA significantly underperforms MLP-only LoRA, and does not further improve performance on top of LoRA-on-MLP. This effect holds for a dense model (Llama-3.1-8B) and a sparse MoE (Qwen3-30B-A3B-Base).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The underperformance of attention-only LoRA is not explained by having fewer parameters. In this particular case, attention-only with rank 256 underperforms MLP-only with rank 128, despite them having approximately the same number of parameters. (Compare the bold numbers in the table below.)&lt;/p&gt;
&lt;table border=&#34;1&#34; style=&#34;border-collapse: collapse;&#34;&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;LoRA configuration&lt;/th&gt;&lt;th&gt;Params&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;span class=&#34;lora-color-square green&#34;&gt;&lt;/span&gt;mlp, rank=256&lt;/td&gt;&lt;td&gt;0.49B&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;span class=&#34;lora-color-square red&#34;&gt;&lt;/span&gt;attn, rank=256&lt;/td&gt;&lt;td&gt;&lt;b&gt;0.25B&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;span class=&#34;lora-color-square blue&#34;&gt;&lt;/span&gt;all, rank=256&lt;/td&gt;&lt;td&gt;0.70B&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;span class=&#34;lora-color-square gold&#34;&gt;&lt;/span&gt;mlp, rank=128&lt;/td&gt;&lt;td&gt;&lt;b&gt;0.24B&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&#34;text-align: center; font-style: italic; margin-top: 0.5em; margin-bottom: 1em;&#34;&gt;Parameter counts for LoRA on Llama-3.1-8B&lt;/p&gt;
&lt;p&gt;For the MoE experiment, we trained a separate LoRA on each expert, with the rank of each equal to the total rank divided by the number of active experts (equal to 8 for Qwen3 MoE). This scaling keeps the ratio of LoRA parameters to FullFT parameters the same for MoE layers as for other layers.&lt;/p&gt;
&lt;p&gt;We did similar experiments comparing different LoRA layers in two additional settings: (1) supervised learning on a small subset of the OpenThoughts3 dataset with rank=256, and (2) reinforcement learning on the MATH dataset. We describe our experimental setup in the following section. Attention-only LoRA underperforms MLP-only LoRA (which performs similarly to MLP+attention) in these settings as well.&lt;/p&gt;
&lt;figure id=&#34;fig:fig5&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/lora/svg/fig5.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;Learning rate vs final loss or reward, when varying which layers we apply LoRA to.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&#34;reinforcement-learning&#34;&gt;Reinforcement learning&lt;/h3&gt;
&lt;p&gt;A key finding from our experiments is that LoRA fully matches the learning performance of FullFT when running policy gradient algorithms for reinforcement learning, even with ranks as low as 1.&lt;/p&gt;
&lt;p&gt;For these experiments, we used a basic policy gradient algorithm with an importance sampling correction; $\text{objective} =\sum_t \frac{p_{\text{learner}}}{p_{\text{sampler}}} Adv_t$.&lt;label for=&#34;sn28&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn28&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;See &lt;a href=&#34;https://fengyao.notion.site/off-policy-rl&#34;&gt;Your Efficient RL Framework Secretly Brings You Off-Policy RL Training&lt;/a&gt;&lt;/span&gt;
We used a GRPO-like centering scheme&lt;label for=&#34;sn26&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn26&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2402.03300&#34;&gt;DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models&lt;/a&gt; (Shao et al, 2024)&lt;/span&gt; where we sample multiple completions per problem and subtract the mean reward per group.&lt;/p&gt;
&lt;p&gt;Figure 6 (below) shows LR sweeps on the MATH&lt;label for=&#34;sn21&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn21&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2103.03874&#34;&gt;Measuring Mathematical Problem Solving With the MATH Dataset&lt;/a&gt; (Hendrycks et al, 2021)&lt;/span&gt; and GSM&lt;label for=&#34;sn22&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn22&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2110.14168&#34;&gt;GSM8K: Training Verifiers to Solve Math Word Problems&lt;/a&gt; (Cobbe et al, 2021)&lt;/span&gt; datasets, using typical hyperparameters for each. We used the Llama-3.1-8B base model as Qwen2.5 and Qwen3 are known to have been pretrained on data that improves their math performance, as described by the Qwen tech reports&lt;label for=&#34;sn1&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn1&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2412.15115&#34;&gt;Qwen2.5 Technical Report&lt;/a&gt; (Qwen Team, 2024)&lt;/span&gt;, which makes it harder to measure what is being learned only during RL.&lt;/p&gt;
&lt;p&gt;LoRA shows a wider range of performant learning rates and arrives at the same peak performance as FullFT (black line), at least within the precision limits afforded by the noisiness of RL.&lt;/p&gt;
&lt;figure id=&#34;fig:fig5-&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/lora/svg/fig5-.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;Learning rate vs final reward (accuracy) when doing RL on grade school math (GSM, left) or MATH (right) dataset.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This result is anticipated by an information-theoretic argument. Supervised learning arguably provides O(number of tokens) bits per episode. In contrast, in policy gradient methods, learning is driven by the advantage function which provides only O(1) bits per episode. When each episode contains thousands of tokens, RL absorbs ~1000 times less information per token in training than supervised learning does.&lt;/p&gt;
&lt;p&gt;We can use more precise numbers based on our experiments. In the MATH example, we trained on ~10,000 problems with 32 samples per problem. Assuming each completion yields a single bit of information, the whole training process only needs to absorb 320,000 bits. Rank-1 LoRA for Llama-3.1-8B already has 3M parameters&lt;label for=&#34;sn17&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn17&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;We calculated this by adding up rank·$d_{in}$ (for matrix A) and rank·$d_{out}$ (for B) over all weight matrices in the model.&lt;/span&gt;, almost 10 times that number. Even at rank-1, LoRA has more than enough capacity to absorb all the information provided during training.&lt;/p&gt;
&lt;p&gt;As another point of comparison, &lt;a href=&#34;https://www.nature.com/articles/s41586-025-09422-z&#34;&gt;DeepSeek-R1-Zero&lt;/a&gt; was trained on 5.3M episodes&lt;label for=&#34;sn18&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn18&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Training took place for 10,400 steps, each step consisting of 32 unique questions, each question sampled 16 times.&lt;/span&gt;, corresponding to 5.3M bits of information. This is less than the number of parameters in a low-rank LoRA, and we predict that the results can be replicated with LoRA.&lt;/p&gt;
&lt;p&gt;For additional validation of our findings of LoRA&amp;rsquo;s effectiveness in reasoning RL, we carried out larger-scale experiments with Qwen3-8b-base on the DeepMath dataset&lt;label for=&#34;sn23&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn23&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2504.11456&#34;&gt;DeepMath-103K: A Large-Scale, Challenging, Decontaminated, and Verifiable Mathematical Dataset for Advancing Reasoning&lt;/a&gt; (He et al, 2025)&lt;/span&gt; as it is much larger than the MATH dataset and in general contains harder problems. To speed up experiments, we restricted the samples to a length of 8192 tokens for training and evaluation. This sample length allows for backtracking and reasoning but limits the performance, relative to longer chain-of-thought.&lt;/p&gt;
&lt;figure id=&#34;fig:fig6&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/lora/svg/fig6.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;Experiments on the DeepMath dataset with Qwen3-8b-base. In the left plot, we show the learning curve for different ranks and full fine-tuning. For each of these settings, we show the best learning rate, which results in the highest final performance. On the right, we plot learning rate vs final performance. As in our previous math experiments, LoRA seems to have a wider peak of near-optimal learning rates.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure id=&#34;fig:fig7&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/lora/svg/fig7.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;Additional plots from experiments on the DeepMath dataset with Qwen3-8b-Base. The left plot shows the benchmark scores on the AIME test set, which is more challenging than the training set. The right plot shows the chain-of-thought (CoT) length over training steps, which can be seen as a sign of learning to reason.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We observe that when picking the optimal learning rates for each setting, training progresses in an almost identical way for LoRAs with different sizes and full fine-tuning. Moreover, we see similar findings when we evaluate the models on the held-out problems of AIME 2024 and AIME 2025. Furthermore, we observe similar qualitative behavior from the LoRA and full-finetuning runs: both develop advanced reasoning behaviors such as backtracking, self-verification and in-context exploration, which is visible in the lengthening of the model CoTs.&lt;/p&gt;
&lt;h2 id=&#34;setting-lora-hyperparameters&#34;&gt;Setting LoRA hyperparameters&lt;/h2&gt;
&lt;p&gt;One barrier to LoRA adoption is the necessity to choose optimal hyperparameters, which are different from ones optimized for FullFT. In this section, we show that this problem isn&amp;rsquo;t as daunting as it appears at first glance and discuss our findings related to hyperparameter choice.&lt;/p&gt;
&lt;h3 id=&#34;optimal-learning-rate-and-rank&#34;&gt;Optimal learning rate and rank&lt;/h3&gt;
&lt;p&gt;Following Hu et al., we consider the following parametrization for LoRA:&lt;/p&gt;
$$W&#39; = W + \frac{\alpha}{r}BA$$&lt;p&gt;Where $r$ is the LoRA rank, $\alpha$ is the LoRA scaling factor, and $A$, $B$ are the LoRA weight matrices (of rank $r$). We use $\alpha = 32$ for the experiments in this article, following standard practice from other implementations.&lt;/p&gt;
&lt;p&gt;The $1/r$ scaling factor makes the optimal learning rate approximately independent of rank. In fact, a stronger condition holds – the learning curve is exactly the same at the beginning of training, regardless of rank. This effect is striking, and in our experiments the closeness of the learning curves for different ranks had us worried that a bug caused the rank parameter to be ignored. It follows that in a short training regime, the optimal LR is also independent of rank. However, as we showed above in our plots of learning rate vs loss (Figure 2), optimal LR has some rank-dependence in the longer-training regime.&lt;/p&gt;
&lt;figure id=&#34;fig:fig8&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/lora/svg/fig8.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;These plots look at the differences in the learning curves, early in training, for different ranks with the same learning rate. On the left, we show the learning curves. The right shows the difference between rank 16 and 256, which grows over time. Strangely, it is negative (though tiny) for the first few steps, so that part of the curve is missing from the plot.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We can partly explain this result by looking at the expected update to the LoRA matrix after the very first training update. We can think of the LoRA product $BA$ as the sum of $r$ rank-1 outer products: $BA = \sum_{i=1}^r b_i a_i^T = \sum_{i=1}^r \Delta_i$, where we define $\Delta_i = b_i a_i^T$. Here, $\partial \text{Loss}/\partial \Delta_i$ is the same for all $i$; however the gradients $\partial \text{Loss}/\partial b_i$ and $\partial \text{Loss}/\partial a_i$ will depend on the initialization ($\partial \text{Loss}/\partial b_i$ depends on $a_i$, for example). Since the initialization of $a_i$ and $b_i$ do not depend on rank, it follows that $\mathbb{E}[\Delta_i]$ is the same for all $i$ and does not depend on rank. At the first step of training, the expected update from each of these terms is equal and independent of the rank. It follows that $(1/r)\sum_{i=1}^r \Delta_i$ is just a sample average of $r$ terms with the same expectation, so the expectation of the average, i.e., the change to the adapter $(1/r)BA$, doesn&amp;rsquo;t depend on the rank.&lt;/p&gt;
&lt;h3 id=&#34;parametrization-invariances&#34;&gt;Parametrization invariances&lt;/h3&gt;
&lt;p&gt;There are four hyperparameters potentially applicable to LoRA:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The scale factor $α$ which appears in $α / r$.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The learning rate for the down-projection matrix $A$, $LR_A$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The learning rate for the up-projection matrix $B$, $LR_B$.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The initialization scale of matrix $A$, $\text{init}_A$. For a random initialization, this is the standard deviation of $A$&amp;rsquo;s initial elements. Matrix $B$ is initialized to zero, so there is no need to define $\text{init}_B$.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Having to tune four different parameters may seem overwhelming. However, invariances in the training dynamics mean that two of these are redundant, and learning behavior is determined by two. We show this invariance by noting that when training with Adam and $ε = 0$, the&lt;label for=&#34;sn2&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn2&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;We can extend this result to $ε &gt; 0$; we need to scale it by $1/q$ because the gradients are scaled by that factor.&lt;/span&gt; optimization process is invariant to the following two-parameter transformation.&lt;/p&gt;
&lt;p&gt;For $p, q &gt; 0$:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$α → \frac{1}{pq} \cdot α$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$\text{init}_A → p \cdot \text{init}_A$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$LR_A → p \cdot LR_A$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$LR_B → q \cdot LR_B$&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since two degrees of freedom out of the four don&amp;rsquo;t affect the learning process, we are left with a 2D parameter space. We can choose different bases for this 2D space, such as the following one which lends itself to a straightforward interpretation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$α \cdot \text{init}_A \cdot LR_B$. This determines the scale of initial updates, or, equivalently, the initial slope of the learning curve. Since $B$ is initialized to zero, $LR_A$ and the initial updates to $A$ are irrelevant.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$\text{init}_A / LR_A$. Since Adam updates the elements of $A$ by approximately $LR_A$ at each step, this timescale parameter determines the number of steps it takes to significantly transform $A$ away from its initial state.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can reinterpret some proposals from previous work on LoRA in terms of this basis.&lt;/p&gt;
&lt;p&gt;LoRA+&lt;label for=&#34;sn3&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn3&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2402.12354&#34;&gt;LoRA+: Efficient Low Rank Adaptation of Large Models&lt;/a&gt; (Hayou et al, 2024)&lt;/span&gt; proposes to use different LRs on $A$ and $B$, with a higher rate for $B$. Expressed in terms of our basis above, increasing $LR_B$ is equivalent to increasing $\text{init}_A/LR_A$ so that $A$ changes on a longer timescale.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.unsloth.ai/get-started/fine-tuning-llms-guide/lora-hyperparameters-guide&#34;&gt;Unsloth&amp;rsquo;s LoRA Hyperparameter Guide&lt;/a&gt; recommends using higher values of $α$ for high-rank LoRA, e.g. by avoiding the $1/r$ scaling. This is also equivalent to increasing $\text{init}_A/LR_A$. When we increase $α$, $LR_A$ and $LR_B$ need to be lowered in compensation to get the same update size. This in turn simply makes $LR_A$ smaller relative to $\text{init}_A$.&lt;/p&gt;
&lt;p&gt;In our experiments, we used the standard parametrization used in the Huggingface &lt;code&gt;peft&lt;/code&gt; library&lt;label for=&#34;sn4&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn4&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://github.com/huggingface/peft&#34;&gt;PEFT: State-of-the-art Parameter-Efficient Fine-Tuning methods&lt;/a&gt; (Mangrulkar et al, 2022)&lt;/span&gt; proposed by Hu et al: a uniform distribution for $A$ with scale $1/\sqrt{d_{in}}$, zero initialization for $B$, the same LR for both, and $α = 32$. We were unable to improve on these hyperparameters in our experimentation.&lt;/p&gt;
&lt;h3 id=&#34;optimal-learning-rates-for-lora-vs-fullft&#34;&gt;Optimal learning rates for LoRA vs. FullFT&lt;/h3&gt;
&lt;p&gt;Our experiments showed that the optimal LR for LoRA is consistently 10x the one used for FullFT in the same application, for both supervised learning and reinforcement learning. This shows up in every U-shaped plot of performance (loss or reward) charted against learning rate. This observation should make it more straightforward to transfer learning hyperparameters from FullFT to LoRA.&lt;/p&gt;
&lt;p&gt;We don&amp;rsquo;t yet have an adequate theoretical explanation for this observation. We can attempt to derive this result from the facts that optimal LoRA LR is invariant to rank and that full-rank LoRA is directly comparable to FullFT. This analysis suggests a LR ratio of the model&amp;rsquo;s hidden size divided by $2 \cdot \alpha$, which doesn&amp;rsquo;t match the empirical result of the optimal ratio being fixed at 10 independent of the base model.&lt;/p&gt;
&lt;p&gt;For our empirical analysis, we conducted an LR sweep of 14 different Llama and Qwen models for both LoRA and FullFT on the Tulu3 dataset. From those sweeps, we fit a function that predicts the optimal learning rate based on the model&amp;rsquo;s hidden size and an indicator of whether it&amp;rsquo;s Llama or Qwen. The functional form used was:&lt;/p&gt;
$$\text{LR} = M_{\text{LoRA}} \cdot \left(\frac{2000}{\text{hidden size}}\right)^{\text{model pow} + \text{LoRA pow}}$$&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$M_{\text{LoRA}}$ is a multiplier applied when LoRA is used (1 if FullFT)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$\text{model pow}$ is an exponent adjustment, calculated separately for each model source (Llama and Qwen)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$\text{LoRA pow}$ is an additional exponent adjustment for LoRA&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$\text{hidden size}$ is the dimension of the residual stream of the model.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We scored a predicted learning rate by using linear interpolation to predict the loss, based on the data from our sweep, and rated the parameters by summing the predicted loss over the 14 problems. Our optimization found a multiplier of 9.8 for LoRA over FullFT, and different dependence on hidden_size for Qwen3 and Llama models, but LoRA LRs had the same dependence on hidden_size as FullFT LRs, i.e., the optimization found $\text{LoRA pow} = 0$.&lt;/p&gt;
&lt;h3 id=&#34;learning-rates-in-short-and-long-runs&#34;&gt;Learning rates in short and long runs&lt;/h3&gt;
&lt;p&gt;The typical initialization of LoRA creates an implicit schedule of change in the effective learning rate. This leads to differences between short and long training runs, and some differences in the shape of learning curves compared to FullFT.&lt;/p&gt;
&lt;p&gt;At the start of training, $B$ is initialized to zero. While $B$ is very small, changes in $A$ have negligible effects on the adapter $BA$ which is added to the original network weights. As $B$ grows larger, updates to $A$ start to have a bigger impact on the network outputs, with the effective learning rate increasing over the course of training as $B$ approaches $A$ in scale. We found that by the end of the full training runs on the Tulu3 and OpenThoughts datasets, the $B$ matrices ended up with larger spectral norms than the $A$ matrices.&lt;/p&gt;
&lt;p&gt;This implies that the optimal LR should be set higher for shorter training runs. Preliminary evidence suggests an optimal multiplier around 15x over the FullFT for short runs&lt;label for=&#34;sn5&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn5&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Based on anecdotal evidence, the higher multiplier is effective under ~100 steps or so.&lt;/span&gt;, converging to the aforementioned 10x multiplier for longer runs.&lt;/p&gt;
&lt;h2 id=&#34;discussion&#34;&gt;Discussion&lt;/h2&gt;
&lt;p&gt;We want to move beyond our empirical results to discuss some broader considerations related to LoRA performance and applicability that would be of interest to both researchers and builders.&lt;/p&gt;
&lt;p&gt;First, let us examine in more depth our main result, namely the two conditions under which LoRA performs similarly to full fine-tuning:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;LoRA is applied to all layers of the network, especially the MLP/MoE layers which house most of the parameters.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LoRA works well when not capacity constrained, i.e., the number of trainable parameters exceeds the amount of information to be learned, which can be estimated in terms of dataset size.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When (1) is satisfied, we get similar learning dynamics to FullFT at the very start of training. Then, as per (2), LoRA continues to look like FullFT until we start reaching capacity limits.&lt;/p&gt;
&lt;h3 id=&#34;why-lora-might-be-needed-on-all-layers&#34;&gt;Why LoRA might be needed on all layers&lt;/h3&gt;
&lt;p&gt;As we showed earlier, if we put LoRA on only the attention layers, we get slower learning even in the tiny-data regime.&lt;/p&gt;
&lt;p&gt;One possible explanation could come from thinking about the empirical neural tangent kernel (eNTK) as an approximation of what happens when we do a small amount of fine-tuning, following Malladi et al.&lt;label for=&#34;sn6&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn6&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2210.05643&#34;&gt;A Kernel-Based View of Language Model Fine-Tuning&lt;/a&gt; (Malladi et al, 2022)&lt;/span&gt; eNTK is based on the dot products of gradients, specifically gradients $g_i = \partial/\partial\theta \log p(\text{token}_i | \text{prefix}_i)$, and $K(i, j) = g_i \cdot g_j$. As a consequence, the layers with the most parameters will typically have the most influence on the kernel. The paper also points out the eNTK for LoRA is approximately the same as that for full fine-tuning, when you train all the layers. So LoRA training $\approx$ eNTK(LoRA) $\approx$ eNTK(FullFT) $\approx$ FullFT. The approximation eNTK(LoRA) $\approx$ eNTK(FullFT) only holds when we apply LoRA to the layers that contain most of the parameters which make up the dot products.&lt;/p&gt;
&lt;h3 id=&#34;how-much-capacity-is-needed-by-supervised-and-reinforcement-learning&#34;&gt;How much capacity is needed by supervised and reinforcement learning?&lt;/h3&gt;
&lt;p&gt;Past work&lt;label for=&#34;sn7&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn7&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2404.05405&#34;&gt;Physics of Language Models: Part 3.3, Knowledge Capacity Scaling Laws&lt;/a&gt; (Allen-Zhu and Li, 2024)&lt;/span&gt; has shown that neural networks can store 2 bits per parameter. These results pertain to the maximum amount of information absorbed in the long-training limit, not to the compute efficiency or rate of learning.&lt;/p&gt;
&lt;p&gt;The 2-bits-per-parameter result relied on synthetic datasets cleverly constructed to contain a precise amount of information. It&amp;rsquo;s not as straightforward to estimate the information content required for a given realistic learning problem. One classic observation is that when minimizing log-loss, the total log-loss measured during the first epoch of training provides a measurement of the dataset&amp;rsquo;s description length. That is, an upper bound for the number of bits required to memorize the dataset. LLM datasets usually have a loss of around 1 bit (0.69 nats) per token, depending on dataset and model size.&lt;/p&gt;
&lt;p&gt;This estimate measures the capacity required to perfectly memorize the dataset, which overestimates the actual capacity needed for &amp;ldquo;generalizable&amp;rdquo; learning that reduces log-loss on test data. Measuring the capacity requirements of supervised learning and how these interact with the number of trainable parameters is an open question for future work.&lt;/p&gt;
&lt;p&gt;For RL, we claimed that policy gradient algorithms learn roughly 1 bit of information per episode, given that there&amp;rsquo;s a single reward value at the end of the episode. This isn&amp;rsquo;t a fundamental property of RL, as other algorithms could conceivably learn a lot more from each episode. For example, model-based RL algorithms train the learning agent to predict the observations and build a world model, potentially extractingmore information per episode. The claim of 1-bit-per-episode may only apply narrowly to policy gradient algorithms.&lt;/p&gt;
&lt;p&gt;We can sharpen the bits-counting argument in information-theoretic terms. Consider an episode, consisting of a trajectory $\tau$ and final reward, as a message (i.e., a noisy channel) that provides some information about the unknown reward function $R$. We&amp;rsquo;ll condition on the current policy and training history and look at the mutual information between the policy gradient estimator and $R$. The REINFORCE update is $G = S \cdot \text{Adv}$ with $S = \nabla \log p_\theta(\tau)$. $S$ is independent of $R$ given the history, so the only $R$-dependent component is the scalar advantage.&lt;/p&gt;
&lt;p&gt;By the data processing inequality:&lt;/p&gt;
$$I(G ; R | \text{history}) \leq I((S, \text{Adv}) ; R | \text{history}) = I(\text{Adv} ; R | S, \text{history}) \leq H(\text{Adv}).$$&lt;p&gt;If we quantize the advantage into $B$ bins, then $H(\text{Adv}) \lesssim \log(B)$. That is, the number of bits of useful information gleaned per episode is $O(1)$, independent of model size. These bits tell us which member of a discrete set of reward functions (or, equivalently, optimal-policy classes) we&amp;rsquo;re in. This analysis of mutual information mirrors what&amp;rsquo;s used in some theoretical analysis of optimization algorithms.&lt;label for=&#34;sn8&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn8&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://www.mit.edu/~rakhlin/papers/ibc_optimization.pdf&#34;&gt;Information Complexity of Black-Box Convex Optimization: A New Look via Feedback Information Theory&lt;/a&gt; (Raginsky and Rakhlin, 2009)&lt;/span&gt; Note that this estimate is an &lt;em&gt;upper&lt;/em&gt; bound on the information absorbed by training; the actual amount learned will depend on the policy initialization and other details. For example, if we initialize with a policy that doesn&amp;rsquo;t get any reward, then the entropy of the advantage is zero (not log(B)), and it won&amp;rsquo;t learn anything.&lt;/p&gt;
&lt;h3 id=&#34;compute-efficiency-advantage-of-lora&#34;&gt;Compute efficiency advantage of LoRA&lt;/h3&gt;
&lt;p&gt;Our experiments above measured learning progress against the number of training steps, but we may also be interested in the &lt;em&gt;compute efficiency&lt;/em&gt; of different methods. We calculate that LoRA takes slightly more than ⅔ of the FLOPs that full fine-tuning does per pass. As a result, it will often outperform FullFT on compute efficiency overall.&lt;/p&gt;
&lt;p&gt;We derive this ⅔ ratio by analyzing the FLOPs used in the forward–backward pass on a given weight matrix. These operations account for the vast majority of FLOPs in neural network models. We use the following notation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$W \in \mathbb{R}^{N \times N}$ is a weight matrix&lt;/li&gt;
&lt;li&gt;$x \in \mathbb{R}^N$ is an input vector&lt;/li&gt;
&lt;li&gt;$y = Wx \in \mathbb{R}^N$ is an output vector&lt;/li&gt;
&lt;li&gt;$\bar{x}, \bar{y} \in \mathbb{R}^N$ are the gradients of the loss with respect to $x$ and $y$, computed in the backward pass&lt;/li&gt;
&lt;li&gt;$\bar{W} \in \mathbb{R}^{N \times N}$ is the gradient of the loss with respect to $W$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Full fine-tuning performs the following operations:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Forward&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$y = Wx$ ($N^2$ multiply–adds)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Backward&lt;/strong&gt;&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;$\bar{x} = W^T \bar{y}$ ($N^2$ multiply–adds)&lt;/li&gt;
&lt;li&gt;$\bar{W} \mathrel{+}= x \bar{y}^T$ ($N^2$ multiply–adds)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The forward pass requires $N^2$ multiply-adds, and the backward pass requires another $2 \cdot N^2$ for $3N^2$ total. Training, which requires both, thus uses 3 times the FLOPs of forward-only inference.&lt;/p&gt;
&lt;p&gt;With LoRA, we replace $W$ by $W + BA$, where $B \in \mathbb{R}^{N \times R}$ and $A \in \mathbb{R}^{R \times N}$, with $R \ll N$. Since we only update $\bar{A}$ and $\bar{B}$, we replace the third step of updating $\bar{W}$ with a much cheaper operation. $A$ and $B$ are $N \cdot R$ matrices, so the full forward-backward computation on each requires $3NR$ multiply-adds instead of $3N^2$ for $W$. The total for both is $6NR$. We also perform the forward-backward pass on $Wx$ and $\bar{x}$, equivalent to the first two steps of FullFT. The total number of multiply-adds is $2N^2 + 6NR$. With $R \ll N$, this is slightly more than $\frac{2}{3}$ of $3N^2$.&lt;/p&gt;
&lt;p&gt;If we plotted LoRA performance over FLOPs&lt;label for=&#34;sn9&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn9&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;This analysis omits FLOPs used for attention, which could be significant in long-context settings.&lt;/span&gt; instead of training steps, it would show a clear advantage over FullFT.&lt;/p&gt;
&lt;h3 id=&#34;open-questions&#34;&gt;Open questions&lt;/h3&gt;
&lt;p&gt;There are several questions related to our results that we would love to see investigated in the future:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Sharpening our predictions of LoRA performance and the precise conditions under which it matches full fine-tuning. We have roughly characterized the regime of equal performance and can estimate the required capacity in terms of tokens or episodes, but we can&amp;rsquo;t yet make accurate forecasts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Our theoretical understanding of LoRA learning rates and training dynamics is limited. A fuller theory that explains the ratio between LoRA and FullFT learning rates would be valuable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How do LoRA variants such as PiSSA&lt;label for=&#34;sn27&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn27&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://arxiv.org/abs/2404.02948&#34;&gt;PiSSA: Principal Singular Values and Singular Vectors Adaptation of Large Language Models&lt;/a&gt; (Meng, Wang &amp;amp; Zhang, 2024)&lt;/span&gt; perform when measured according to the methodology in this article?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are various options for applying LoRA to MoE layers. LoRA users would benefit from an investigation into how well they perform, and how compatible each approach is with methods like tensor parallelism and expert parallelism that are important for large MoE models.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;closing-thoughts&#34;&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;At Thinking Machines, we believe in the power of fine-tuning to advance AI usefulness in many domains of expertise. Our interest in LoRA is driven by a goal of making this power widely accessible and easily customizable to specific needs.&lt;/p&gt;
&lt;p&gt;Aside from its practical uses, research on LoRA has also led us to deeper investigations of model capacity, dataset complexity, and sample efficiency. Looking at how learning speed and performance depend on capacity provides a lens for studying fundamental questions in machine learning. We look forward to advancing this study in the future.&lt;/p&gt;
&lt;h2 id=&#34;acknowledgements&#34;&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;We thank Dan Alexander Biderman, Weizhu Chen, Daniel Han, and Sadhika Malladi for their insightful feedback on an earlier draft of this post.&lt;/p&gt;
&lt;h2 id=&#34;citation&#34;&gt;Citation&lt;/h2&gt;
&lt;p&gt;Please cite this work as:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Schulman, John and Thinking Machines Lab, &amp;#34;LoRA Without Regret&amp;#34;,
Thinking Machines Lab: Connectionism, Sep 2025.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or use the BibTeX citation:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;@article{schulman2025lora,
  author = {John Schulman and Thinking Machines Lab},
  title = {LoRA Without Regret},
  journal = {Thinking Machines Lab: Connectionism},
  year = {2025},
  note = {https://thinkingmachines.ai/blog/lora/},
  doi = {10.64434/tml.20250929},
}
&lt;/code&gt;&lt;/pre&gt;</description>
    </item>
    
    <item>
      <title>Modular Manifolds</title>
      <link>https://thinkingmachines.ai/blog/modular-manifolds/</link>
      <pubDate>Fri, 26 Sep 2025 00:00:00 +0000</pubDate>
      
      <guid>https://thinkingmachines.ai/blog/modular-manifolds/</guid>
      <description>&lt;script src=&#34;https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js&#34;&gt;&lt;/script&gt;
&lt;p&gt;When we train large neural networks, we need to keep them healthy. We do not want the tensors in the network&amp;mdash;either the weights, activations or gradients&amp;mdash;to grow too large or too small. Very small and very large tensors cause a variety of problems not just limited to numerical underflow and overflow. For example, weight matrices changing size during training makes it harder to design training algorithms&amp;mdash;since the relative size of updates to weights has a significant impact on the speed of learning.&lt;/p&gt;
&lt;p&gt;The gold standard for keeping tensors healthy is to normalize them. Normalization is commonplace for activation vectors, where we use techniques like &lt;a href=&#34;https://arxiv.org/abs/1607.06450&#34;&gt;layer norm&lt;/a&gt; to put the activations on a good scale before passing them to the next layer. It is also commonplace to normalize gradient updates, where we can interpret fast training algorithms like the &lt;a href=&#34;https://kellerjordan.github.io/posts/muon/&#34;&gt;Muon optimizer&lt;/a&gt; as spectrally normalizing the updates. Normalization provides us with certainty about the sizes of tensors&amp;mdash;without needing to check Wandb!&amp;mdash;and when training large neural networks with many interacting components, having certainty about the network internals is valuable.&lt;/p&gt;
&lt;p&gt;Normalization is less commonly applied to weight matrices, although it is not unheard of. For example, the &lt;a href=&#34;https://github.com/NVlabs/edm2&#34;&gt;EDM2&lt;/a&gt; diffusion model codebase uses weight constraints and the authors report benefits in &lt;a href=&#34;https://arxiv.org/abs/2312.02696&#34;&gt;their paper&lt;/a&gt;. And &lt;a href=&#34;https://arxiv.org/abs/1912.11370&#34;&gt;BiT&lt;/a&gt; uses &lt;a href=&#34;https://arxiv.org/abs/1903.10520&#34;&gt;weight standardization&lt;/a&gt;. Various other techniques have been proposed but are not common practice in modern large-scale training.&lt;label for=&#34;sn1&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn1&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;For some more examples, see &lt;a href=&#34;https://arxiv.org/abs/1602.07868&#34;&gt;Salimans et al, 2016&lt;/a&gt;, &lt;a href=&#34;https://arxiv.org/abs/1802.05957&#34;&gt;Miyato et al, 2018&lt;/a&gt; and our paper &lt;a href=&#34;https://arxiv.org/abs/2102.07227&#34;&gt;Liu et al, 2021&lt;/a&gt;.&lt;/span&gt; Normalizing the weight matrices might be a good idea for a few reasons. Weight constraints make understanding the relative size of optimization updates easier. They remove the problem of weight norms exploding. They allow us to focus hyperparameter tuning effort on tensors whose size matters most. They can force matrices to have a small &lt;a href=&#34;https://en.wikipedia.org/wiki/Condition_number&#34;&gt;condition number&lt;/a&gt;, making their behaviour more predictable. And relatedly, weight constraints facilitate &lt;a href=&#34;https://theses.hal.science/tel-04674274v1&#34;&gt;Lipschitz guarantees&lt;/a&gt; for robustness to perturbations.&lt;/p&gt;
&lt;p&gt;This post covers one appealing way to constrain the weight matrices of a neural network&amp;mdash;by keeping the tensors constrained to submanifolds at each layer. This opens the door to re-thinking optimization, as we can co-design optimization algorithms with these manifold constraints. As an example, we propose&lt;label for=&#34;sna&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sna&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;This algorithm builds on work from Jianlin Su and Franz Louis Cesista, as discussed further below.&lt;/span&gt; a manifold version of the Muon optimizer whose weights are constrained to the Stiefel manifold: the manifold of matrices with unit &lt;a href=&#34;https://en.wikipedia.org/wiki/Condition_number&#34;&gt;condition number&lt;/a&gt;. We conclude the post by defining the idea of a &lt;em&gt;modular manifold&lt;/em&gt;, which is a composable manifold that attempts to make it easier to scale up and train large networks.&lt;/p&gt;
&lt;p&gt;Our goal in writing this post is to provide an introduction to a research area that we are excited about, and highlight many directions for future work. We would love to see more work from the community on the topics mentioned at the end of the post!&lt;/p&gt;
&lt;h2 id=&#34;the-shape-of-a-manifold-optimizer&#34;&gt;The shape of a manifold optimizer&lt;/h2&gt;
&lt;p&gt;This section works through the simplest example of learning on a manifold: a vector parameter constrained to a hypersphere in $\mathbb{R}^d$. The vector parameter is trained to minimize a loss function defined over the full space $\mathbb{R}^d$. This setup might be useful for, say, individual embedding vectors in a transformer model. This section will be a good warmup for the following section on &lt;a href=&#34;#muon-on-the-stiefel-manifold&#34;&gt;manifold Muon&lt;/a&gt; that considers matrix parameters.&lt;/p&gt;
&lt;p&gt;We will not be too formal about the definition of a manifold here: it is enough to understand that a manifold is a curved surface that looks flat when you zoom in close enough. The locally flat approximation at a point on the manifold is called the &lt;em&gt;tangent space&lt;/em&gt; to the manifold, as visualized in &lt;a href=&#34;#fig:tangent-space&#34;&gt;Figure &lt;span data-ref=&#34;tangent-space&#34;&gt;&lt;/span&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;figure id=&#34;fig:tangent-space&#34;&gt;
&lt;canvas id=&#34;canvas-tangent-space&#34;&gt;&lt;/canvas&gt;
&lt;script src=&#34;js/tangent.js&#34;&gt;&lt;/script&gt;
&lt;figcaption&gt;The sphere in three dimensions—or the hypersphere in higher dimensions—is a manifold. The locally flat approximation at a point on the manifold is called the tangent space to the manifold and is visualized as the red plane in the figure.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We can characterize the hypersphere in $d$ dimensions as the set of points $w \in \mathbb{R}^d$ of unit Euclidean norm. And the tangent space at a point $w$ on the hypersphere is the set of all vectors $a \in \mathbb{R}^d$ that are orthogonal to $w$.&lt;/p&gt;
&lt;p&gt;To keep the weights constrained to the manifold, we could use a non-manifold optimizer and just project the weights back to the manifold after each step. Instead, we are interested in designing methods that take steps in the tangent space. The reason is that we would like to be able to equate the learning rate of our optimizer with the actual length of the optimization step. But if the optimization steps are pointing significantly off manifold and then being projected back, this nice property does not hold. Similar motivation is given in Section 2.3 of the &lt;a href=&#34;https://arxiv.org/abs/2312.02696&#34;&gt;EDM2 paper&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before we can design a training algorithm for this manifold, something important we need to decide on is how to measure distance&lt;label for=&#34;sn3&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn3&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;For a manifold to be &amp;ldquo;Riemannian&amp;rdquo;, the distance measure must be induced by an inner product. The &lt;a href=&#34;https://en.wikipedia.org/wiki/Euclidean_distance&#34;&gt;Euclidean ($\ell_2$) norm&lt;/a&gt; is induced by an inner product, but the &lt;a href=&#34;https://en.wikipedia.org/wiki/Taxicab_geometry&#34;&gt;Manhattan ($\ell_1$) distance&lt;/a&gt; is not.&lt;/span&gt; in the tangent space. A common choice is the Euclidean distance, but we could also choose to measure distance in other ways, as visualized in &lt;a href=&#34;#fig:inscribed-norm-balls&#34;&gt;Figure &lt;span data-ref=&#34;inscribed-norm-balls&#34;&gt;&lt;/span&gt;&lt;/a&gt;. In the next section, we will talk about choosing a distance measure based on the functionality of the module.&lt;/p&gt;
&lt;figure id=&#34;fig:inscribed-norm-balls&#34;&gt;
&lt;canvas id=&#34;canvas-inscribed-norm-ball&#34;&gt;&lt;/canvas&gt;
&lt;script src=&#34;js/inscribed-norm-ball.js&#34;&gt;&lt;/script&gt;
&lt;figcaption&gt;Inscribing unit balls in the tangent space for different distance measures. The $\ell_2$ (Euclidean) unit ball is a circle while the $\ell_1$ (Manhattan) unit ball is a diamond.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Crucially, the choice of distance measure changes the direction of the best optimization step. If the distance measure is non-Euclidean, then for a fixed length step, we may be able to move further in the direction of the gradient&lt;label for=&#34;sn4&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn4&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;By gradient, we mean the partial derivative of the loss with respect to the weights. Mathematicians reserve the term gradient for &lt;a href=&#34;https://en.wikipedia.org/wiki/Gradient#Riemannian_manifolds&#34;&gt;something else&lt;/a&gt; in Riemannian geometry.&lt;/span&gt; by not following the gradient direction exactly! This concept is visualized in &lt;a href=&#34;#fig:tangent-space-with-gradient&#34;&gt;Figure &lt;span data-ref=&#34;tangent-space-with-gradient&#34;&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;figure id=&#34;fig:tangent-space-with-gradient&#34;&gt;
&lt;canvas id=&#34;canvas-tangent-with-gradient&#34;&gt;&lt;/canvas&gt;
&lt;script src=&#34;js/tangent-with-gradient.js&#34;&gt;&lt;/script&gt;
&lt;figcaption&gt;How geometry influences the direction of the best optimization step. The pink arrow represents the raw gradient—meaning the partial derivative of the loss with respect to the weights. The yellow diamond denotes the $\ell_1$ unit ball. The green arrow is the unit vector pointing furthest in the direction of the gradient. Notice how the green arrow is not parallel to the pink arrow. (In practice, the pink arrow need not lie in the tangent space, although the green arrow will do by construction.) Try dragging the pink arrow to see how the best update direction changes.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;To see how this works out in math, we can formulate the optimal update direction given a manifold constraint and a distance measure as itself solving a constrained optimization problem. We will demonstrate this for the case of the hypersphere equipped with the Euclidean norm. Letting $g$ denote the gradient, $w$ the current point on the hypersphere, $a$ the update direction and $\eta$ the learning rate, we need to solve:&lt;/p&gt;
$$\min_{a\in\mathbb{R}^d} \quad \underbrace{a^\top g\vphantom{\|a\|_2 = 1}}_{\mathclap{\text{linear change in loss}}} \quad \text{such that} \quad  \underbrace{\|a\|_2 = \eta}_{\mathclap{\text{size constraint}}} \quad \text{and} \quad \underbrace{a^\top w = 0\vphantom{\|a\|_2 = 1}}_{\mathclap{\text{tangent constraint}}}.\tag{$\star$}$$&lt;p&gt;Mapping back to the visual language of Figures &lt;a href=&#34;#fig:tangent-space&#34;&gt;&lt;span data-ref=&#34;tangent-space&#34;&gt;&lt;/span&gt;&lt;/a&gt;, &lt;a href=&#34;#fig:inscribed-norm-balls&#34;&gt;&lt;span data-ref=&#34;inscribed-norm-balls&#34;&gt;&lt;/span&gt;&lt;/a&gt; and &lt;a href=&#34;#fig:tangent-space-with-gradient&#34;&gt;&lt;span data-ref=&#34;tangent-space-with-gradient&#34;&gt;&lt;/span&gt;&lt;/a&gt;, this formula says that the green arrow (optimal value of $a$) must belong to the red tangent hyperplane ($a^\top w = 0$) and must also lie on a yellow circle of radius $\eta$ ($\|a\|_2 = \eta$). To solve $(\star)$, we can apply the &lt;a href=&#34;https://en.wikipedia.org/wiki/Lagrange_multiplier&#34;&gt;method of Lagrange multipliers&lt;/a&gt;. The relevant Lagrangian function is given by:&lt;/p&gt;
$$\mathcal{L}(a, \lambda, \mu) = a^\top g + \frac{\lambda}{2} \cdot (a^\top a - \eta) + \mu \cdot (a^\top w),$$&lt;p&gt;
where $\lambda$ and $\mu$ are Lagrange multipliers. Setting the derivative of the Lagrangian with respect to $a$ to zero and applying the constraints to solve for $\lambda$ and $\mu$, the optimal update $a_\mathrm{opt}$ ends up being given by the following formula:
&lt;/p&gt;
$$a_\mathrm{opt} = - \eta \times \frac{g - ww^\top g}{\|g-ww^\top g\|_2}.$$&lt;p&gt;In words, the optimal update is given by subtracting out the radial component from the gradient, normalizing and multiplying by the learning rate. Since this update lies in the tangent space, actually a very small&lt;label for=&#34;sn5&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn5&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;For a learning rate $\eta$, the effect of the retraction map is $\mathcal{O}(\eta^2)$ small, so the learning rate &lt;em&gt;almost&lt;/em&gt; equals the length of the step.&lt;/span&gt; correction is needed to stay on the manifold. The correction is known as a &amp;ldquo;retraction map&amp;rdquo; and is visualized in &lt;a href=&#34;#fig:retraction-map&#34;&gt;Figure &lt;span data-ref=&#34;retraction-map&#34;&gt;&lt;/span&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;figure id=&#34;fig:retraction-map&#34;&gt;
&lt;canvas id=&#34;canvas-retraction-map-2d&#34;&gt;&lt;/canvas&gt;
&lt;script src=&#34;js/retraction-map-2d.js&#34;&gt;&lt;/script&gt;
&lt;figcaption&gt;Visualizing the retraction map. The green arrow is the update taken in the tangent space. Since for large step sizes the tangent space starts to diverge from the manifold, we need to project the updated weights back to the manifold using the retraction map—illustrated by the purple arrow.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We can solve for the retraction map by applying Pythagoras&amp;rsquo; theorem to &lt;a href=&#34;#fig:retraction-map&#34;&gt;Figure &lt;span data-ref=&#34;retraction-map&#34;&gt;&lt;/span&gt;&lt;/a&gt;. For a unit hypersphere and a step of length $\eta$, the hypotenuse has length $\sqrt{1+\eta^2}$ and therefore the retraction map for the hypersphere equipped with the Euclidean norm is simply given by dividing the updated weights through by $\sqrt{1+\eta^2}$. Putting everything together, the full manifold optimization algorithm is then given by:&lt;/p&gt;
$$w \gets \frac{1}{\sqrt{1+\eta^2}} \left[w - \eta \times \frac{g - ww^\top g}{\|g-ww^\top g\|_2}\right].$$&lt;p&gt;As an exercise for the reader: try calculating the Euclidean norm of the updated weight vector and check that the updated weight vector indeed lies on the hypersphere.&lt;/p&gt;
&lt;p&gt;To summarize this section, a first-order manifold optimizer has three steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find the tangent vector of unit length that goes furthest in the gradient direction.&lt;/li&gt;
&lt;li&gt;Multiply this direction by the learning rate and subtract from the weights;&lt;/li&gt;
&lt;li&gt;Retract the updated weights back to the manifold.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are two decisions to make in applying this procedure: what manifold constraint we should use and how we should measure length. By making different decisions, we can generate different optimization algorithms as shown in the following table.&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Manifold&lt;/th&gt;
          &lt;th&gt;Norm&lt;/th&gt;
          &lt;th&gt;Optimizer&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Euclidean $\mathbb{R}^n$&lt;/td&gt;
          &lt;td&gt;Euclidean norm&lt;/td&gt;
          &lt;td&gt;vanilla gradient descent&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Euclidean $\mathbb{R}^n$&lt;/td&gt;
          &lt;td&gt;infinity norm&lt;/td&gt;
          &lt;td&gt;sign gradient descent&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;hypersphere $S^n$&lt;/td&gt;
          &lt;td&gt;Euclidean norm&lt;/td&gt;
          &lt;td&gt;hyperspherical descent&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;matrix space $\mathbb{R}^{m\times n}$&lt;/td&gt;
          &lt;td&gt;spectral norm&lt;/td&gt;
          &lt;td&gt;Muon&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Stiefel manifold $\subset\mathbb{R}^{m\times n}$&lt;/td&gt;
          &lt;td&gt;spectral norm&lt;/td&gt;
          &lt;td&gt;manifold Muon&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We will derive the final algorithm in the table, manifold Muon, in the next section. To design a manifold constraint and a distance function for a matrix parameter, we shall think carefully about the role that a weight matrix plays inside a neural network.&lt;/p&gt;
&lt;h2 id=&#34;manifold-muon&#34;&gt;Manifold Muon&lt;/h2&gt;
&lt;p&gt;A typical weight matrix $W$ in a transformer is a &amp;ldquo;vector-multiplier&amp;rdquo;, meaning that it transforms an input vector $x$ into an output vector $y = Wx$. We will design a manifold constraint and a distance function so that the matrix acts in a good way on input vectors: the matrix should not produce excessively small or large outputs, and updates to the matrix should not cause the output vector to change too much or too little.&lt;/p&gt;
&lt;p&gt;A good way to think about how a matrix acts on vectors is through the &lt;a href=&#34;https://en.wikipedia.org/wiki/Singular_value_decomposition&#34;&gt;singular value decomposition&lt;/a&gt;, illustrated in &lt;a href=&#34;#fig:svd&#34;&gt;Figure &lt;span data-ref=&#34;svd&#34;&gt;&lt;/span&gt;&lt;/a&gt;. The SVD decomposes a matrix in a way that tells us how the matrix stretches input vectors along different axes.&lt;/p&gt;
&lt;figure id=&#34;fig:svd&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/modular-manifolds/svgs/svd.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;The singular value decomposition. A matrix $M\in\mathbb{R}^{m\times n}$ of rank $k$ can always be decomposed as $M = U \Sigma V^\top$, where $U\in\mathbb{R}^{m\times k}$ and $V\in\mathbb{R}^{n\times k}$ have orthonormal columns and $\Sigma\in\mathbb{R}^{k\times k}$ is a diagonal matrix with only positive entries. The entries of $\Sigma$ are called the singular values of $M$. The singular values measure the stretching effect that the matrix has on vectors that align with the corresponding columns of $U$ and $V$.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We would like the matrix to have a stretching effect close to one, so we will choose a matrix manifold where all the singular values are exactly one. This matrix manifold is known formally as the &lt;a href=&#34;https://en.wikipedia.org/wiki/Stiefel_manifold&#34;&gt;&lt;em&gt;Stiefel manifold&lt;/em&gt;&lt;/a&gt;. We can assume without loss of generality that we are dealing with a tall matrix ($m \geq n$), and then the Stiefel manifold can be equivalently defined as the following set:&lt;/p&gt;
$$\mathsf{Stiefel}(m,n) := \left\{ W \in \mathbb{R}^{m \times n} \mid W^T W = I_n \right\}.$$&lt;p&gt;Furthermore, &lt;a href=&#34;https://cseweb.ucsd.edu/classes/sp24/cse291-e/papers/StiefelManifold/StiefelNotes.pdf&#34;&gt;one may show&lt;/a&gt; that a matrix $A \in \mathbb{R}^{m \times n}$ lies tangent&lt;label for=&#34;sn-stiefel-tangent&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-stiefel-tangent&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Notice that the Stiefel constraint $W^T W = I_n$ directly generalizes the hyperspherical constraint $w^\top w = 1$ from the previous section. Similarly, the tangent space condition generalizes the hyperspherical one that $a^\top w = 0$.&lt;/span&gt; to the Stiefel manifold at matrix $W$ if and only if:&lt;/p&gt;
$$A^\top W + W^\top A = 0.$$&lt;p&gt;To design a manifold optimizer for the Stiefel manifold, all that remains is to choose a distance function. To limit the maximum stretching effect the weight update can have on an input vector, we will choose the &lt;em&gt;spectral norm&lt;/em&gt;, which measures the largest singular value of a matrix. Although this only limits the maximum effect the update can have, since the optimizer we derive will saturate this bound, it will turn out to prevent the minimum effect of the update from being too small.&lt;label for=&#34;sn-stiefel-spectral-norm&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-stiefel-spectral-norm&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;There are some exceptions to this statement, such as when a weight matrix has a fan-out less than its fan-in, in which case we cannot escape from the matrix and its updates having a null space and mapping some inputs to zero.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The idea of doing gradient descent under a spectral norm constraint is what &lt;a href=&#34;https://jeremybernste.in/writing/deriving-muon&#34;&gt;led to&lt;/a&gt; the Muon optimizer and, when combined with the Stiefel manifold constraint, we obtain a problem that we shall call &lt;em&gt;manifold Muon&lt;/em&gt;:&lt;/p&gt;
$$\min_{A\in\mathbb{R}^{m\times n}} \quad \underbrace{\operatorname{trace}(G^T A)}_{\mathclap{\text{linear change in loss}}} \quad \text{such that} \quad \underbrace{\|A\|_{\text{spectral}} \leq \eta}_{\mathclap{\text{size constraint}}} \quad \text{and} \quad \underbrace{A^T W + W^T A = 0\vphantom{\|A\|_{\text{spectral}} = \eta}}_{\mathclap{\text{tangent constraint}}} \tag{$\dagger$}.$$&lt;p&gt;The manifold Muon problem $(\dagger)$ directly generalizes problem $(\star)$ from the previous section. Solving $(\dagger)$ is harder than solving $(\star)$, and here we will present a numerical solution inspired&lt;label for=&#34;sn6&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn6&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;I &lt;a href=&#34;https://docs.modula.systems/algorithms/manifold/orthogonal/&#34;&gt;figured out&lt;/a&gt; how to solve manifold Muon in the square case late last year, but I was unable to solve the full rectangular case and thus &lt;a href=&#34;https://docs.modula.systems/algorithms/manifold/orthogonal/#open-problem-extending-to-the-stiefel-manifold&#34;&gt;posed the problem&lt;/a&gt; as an open problem on the Modula docs. Jianlin Su &lt;a href=&#34;https://kexue.fm/archives/11221&#34;&gt;solved the problem&lt;/a&gt; this summer by taking a Lagrangian approach and working out a fixed point iteration on the optimality condition. I saw an early version of Jianlin&amp;rsquo;s work (which did not quite work yet) and also &lt;a href=&#34;https://leloykun.github.io/ponder/steepest-descent-stiefel/&#34;&gt;related work&lt;/a&gt; by Franz Louis Cesista, and I was able to work out the dual ascent algorithm presented here.&lt;/span&gt; by work done by Jianlin Su and Franz Louis Cesista.&lt;/p&gt;
&lt;p&gt;Our key insight is that $(\dagger)$ is a convex optimization problem that may be solved via a standard method known as &lt;a href=&#34;https://www.cs.cmu.edu/~pradeepr/convexopt/Lecture_Slides/dual-ascent.pdf&#34;&gt;&lt;em&gt;dual ascent&lt;/em&gt;&lt;/a&gt;. Here we will just sketch the main idea, but you can find a more detailed derivation &lt;a href=&#34;https://docs.modula.systems/algorithms/manifold/stiefel/&#34;&gt;on this page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Similar to Jianlin&amp;rsquo;s approach, we introduce a matrix of Lagrange multipliers $\Lambda\in\mathbb{R}^{n\times n}$. We then apply a series of transformations to convert the problem $(\dagger)$ from a &lt;em&gt;constrained minimization problem&lt;/em&gt; to an &lt;em&gt;unconstrained maximization problem&lt;/em&gt;:&lt;/p&gt;
$$
\begin{align}
(\dagger) &amp;= \min_{\|A\|_\mathrm{spectral} \leq \eta} \max_{\Lambda} \;\operatorname{trace} G^\top A + \operatorname{trace}\Lambda^\top (A^\top W + W^\top A) \\
&amp;= \min_{\|A\|_\mathrm{spectral} \leq \eta} \max_{\Lambda}\; \operatorname{trace}A^\top (G + 2W(\Lambda+\Lambda^\top))\\
&amp;= \max_{\Lambda} \min_{\|A\|_\mathrm{spectral} \leq \eta} \; \operatorname{trace}A^\top (G + 2W(\Lambda+\Lambda^\top))\\
&amp;= \max_{\Lambda} \; - \eta \times \|G + 2W(\Lambda+\Lambda^\top)\|_\mathrm{nuclear}.
\end{align}
$$&lt;p&gt;Equation (1) reformulates the problem as a &lt;a href=&#34;https://www-cs.stanford.edu/people/davidknowles/lagrangian_duality.pdf&#34;&gt;saddle point problem&lt;/a&gt;: the maximization over $\Lambda$ will send the objective to infinity whenever the tangent space condition is violated. Equation (2) follows by applying properties of the trace and equation (3) follows from Sion&amp;rsquo;s minimax theorem. The inner minimization in equation (3) is solved by setting $A_\mathrm{opt}(\Lambda) = - \eta \times \operatorname{msign}(G + 2W(\Lambda+\Lambda^\top))$ where $\operatorname{msign}$ is the &lt;a href=&#34;https://docs.modula.systems/algorithms/newton-schulz/&#34;&gt;matrix sign function&lt;/a&gt;.&lt;label for=&#34;sn7&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn7&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;The matrix sign function snaps the singular values of a matrix to one. It may be computed efficiently on GPUs via Newton-Schulz iteration or the recent &lt;a href=&#34;https://arxiv.org/abs/2505.16932&#34;&gt;Polar Express&lt;/a&gt; algorithm.&lt;/span&gt; And we obtain equation (4) by substituting this expression for $A_\mathrm{opt}(\Lambda)$ into equation (3). Equation (4) is known as the &amp;ldquo;dual problem&amp;rdquo; to $(\dagger)$ and we can solve it by gradient ascent. After some work, the gradient of the dual function is given by:&lt;/p&gt;
$$
\begin{align}
H(\Lambda) &amp;:= - \eta \times \nabla_\Lambda \|G + W (\Lambda+\Lambda^\top)\|_\mathrm{nuclear} \\
&amp;= - \eta \times [W^\top\mathrm{msign}(G + 2W (\Lambda+\Lambda^\top)) + \operatorname{msign}(G + 2W (\Lambda+\Lambda^\top))^\top W],
\end{align}
$$&lt;p&gt;where the nuclear norm $\|\cdot\|_\mathrm{nuclear}$ measures the sum of the singular values of a matrix.&lt;/p&gt;
&lt;p&gt;Finally, we can write down the manifold Muon algorithm:&lt;label for=&#34;sn8&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn8&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Note that this algorithm is closely related to &lt;a href=&#34;https://kexue.fm/archives/11221&#34;&gt;Jianlin Su&amp;rsquo;s solution&lt;/a&gt;. Where we run dual ascent, Jianlin&amp;rsquo;s solution amounts to solving for the maximum of the dual function $H(\Lambda)=0$ via a fixed point iteration.&lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run gradient ascent on the dual variable $\Lambda \gets \Lambda + \alpha \times H(\Lambda)$ to solve for $\Lambda_\mathrm{opt}$.&lt;/li&gt;
&lt;li&gt;Compute the update $A_\mathrm{opt} = - \eta \times \operatorname{msign}(G + 2W(\Lambda_{\mathrm{opt}}+\Lambda_\mathrm{opt}^\top))$.&lt;/li&gt;
&lt;li&gt;Apply the update to the weights $W \gets W + A_\mathrm{opt}$.&lt;/li&gt;
&lt;li&gt;Retract the weights back to the manifold $W \gets \operatorname{msign}(W)$.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We ran a very small experiment to sanity check the algorithm and provide a minimal implementation for students or researchers to play with. Each training run finishes in less than a minute. The code is &lt;a href=&#34;https://github.com/thinking-machines-lab/manifolds/&#34;&gt;here&lt;/a&gt; and see &lt;a href=&#34;#fig:cifar10&#34;&gt;Figure &lt;span data-ref=&#34;cifar10&#34;&gt;&lt;/span&gt;&lt;/a&gt; for the setup and results.&lt;/p&gt;
&lt;figure id=&#34;fig:cifar10&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/modular-manifolds/svgs/cifar10.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;Training a small MLP for 3 epochs on the CIFAR-10 dataset. The different lightly shaded blue curves show different weight decay settings for AdamW. Results were averaged over 3 random seeds. The manifold Muon optimizer attained higher train and test accuracy than AdamW. The third plot shows the final singular value distribution of the first weight matrix for the best performing learning rate: the singular values after training with manifold Muon are all close to 1. Manifold Muon increased the wall clock time per step compared to AdamW, although this could be improved by running fewer steps of dual ascent or adding momentum to the algorithm and running dual ascent online. Depending on other systems bottlenecks, the overhead may not be an issue.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;modular-manifolds&#34;&gt;Modular manifolds&lt;/h2&gt;
&lt;p&gt;So far in this post, we have discussed manifold constraints for individual parameter tensors and co-designed optimization logic for these constraints. A question we have not answered is: what happens when we combine layers to build networks? Can we think about individual layers in isolation&amp;mdash;or do we need to be careful about interactions between layers and modify the optimization logic in response? The goal of this section is to point out that there is a way to extend the reasoning we introduced in the previous two sections to the case of whole networks, and we call this &lt;em&gt;the theory of modular manifolds&lt;/em&gt;.&lt;label for=&#34;sn-modular-manifolds-1&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-modular-manifolds-1&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;The theory of modular manifolds builds on research I did with my friend Tim Large, my postdoc advisor Phillip Isola, my PhD advisor Yisong Yue and many other amazing collaborators.&lt;/span&gt; At the end of the section, we provide some links to learn more.&lt;/p&gt;
&lt;p&gt;The idea of modular manifolds is to build an abstraction that tells us how to budget learning rates across layers. The actual optimization logic in each layer ends up being the same as what we already worked out, except that the learning rate for a layer is modified depending on where the layer appears in the network. The abstraction rests upon a key observation made in our paper on the &lt;a href=&#34;https://arxiv.org/abs/2405.14813&#34;&gt;modular norm&lt;/a&gt;, that budgeting learning rates&amp;mdash;both across layers and when scaling up individual layers&amp;mdash;is intimately tied to understanding the Lipschitz sensitivity of the network output with respect to the weights. The abstraction tracks this sensitivity as we build the network, and manifold constraints help us get a much tighter understanding of this sensitivity.&lt;/p&gt;
&lt;p&gt;The starting point for the abstraction is to think of any neural network module&amp;mdash;from a layer to a whole transformer&amp;mdash;as a mathematical object with three attributes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A forward function $f:\mathcal{W} \times \mathcal{X} \to \mathcal{Y}$ that maps from a parameter space $\mathcal{W} = \mathbb{R}^d$ and an input space $\mathcal{X}$ to an output space $\mathcal{Y}$.&lt;/li&gt;
&lt;li&gt;A submanifold of the weight space $\mathcal{M}\subset\mathcal{W}$ that the weights are constrained to.&lt;/li&gt;
&lt;li&gt;A norm $\|\cdot\| : \mathcal{W} \to \mathbb{R}$ that acts as a measuring stick on weight space.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, a linear module equipped with the spectral norm and constrained to the Stiefel manifold, for which we have already worked out an optimizer, would be written:&lt;/p&gt;
$$ \mathsf{StiefelLinear} = \begin{cases}(W, x) \mapsto Wx, &amp; \text{(forward function)}\\ \mathsf{Stiefel}(m,n), &amp; \text{(manifold)}\\ \|\cdot\|_\mathrm{spectral}. &amp; \text{(norm)}\end{cases}$$&lt;p&gt;Provided that an input $x$ to the $\mathsf{StiefelLinear}$ module has unit $\ell_2$ norm, then $\mathsf{StiefelLinear}$ is Lipschitz with respect to its weights in the module&amp;rsquo;s assigned norm with Lipschitz constant one:&lt;label for=&#34;sn-stiefel-lipschitz&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn-stiefel-lipschitz&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;This argument can be extended to the RMS norm on the input and the RMS&amp;ndash;RMS operator norm on the weights.&lt;/span&gt;&lt;/p&gt;
$$\|(W + \Delta W) x - Wx\|_2 \leq \|\Delta W\|_\mathrm{spectral} \times \|x\|_2 = \|\Delta W\|_\mathrm{spectral}.$$&lt;p&gt;This type of Lipschitz statement helps us understand how to scale weight updates to this module since it gives us a bound on how much the output can change when we perturb the weights. But when we compose two modules, can we automatically compile a Lipschitz statement on the joint weight space of the new module? The answer turns out to be yes, if we follow special rules for building the new module:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The new forward function $f_3$ is given by composing the two existing forward functions $f_1$ and $f_2$:
$$f_3((w_1, w_2), x) := f_2(w_2, f_1(w_1, x)). \qquad$$&lt;/li&gt;
&lt;li&gt;The new manifold constraint $\mathcal{M}_3$ is just the Cartesian product (see &lt;a href=&#34;#fig:product-manifold&#34;&gt;Figure &lt;span data-ref=&#34;product-manifold&#34;&gt;&lt;/span&gt;&lt;/a&gt; for a fun example) of the two existing manifolds $\mathcal{M}_1$ and $\mathcal{M}_2$:
$$\mathcal{M}_3 = \mathcal{M}_1 \times \mathcal{M}_2. \qquad$$&lt;/li&gt;
&lt;li&gt;The new norm function is the max of the two existing norm functions weighted by special scalar coefficients $s_1$ and $s_2$. Letting $\|\cdot\|_1$ denote the first module&amp;rsquo;s norm and $\|\cdot\|_2$ denote the second module&amp;rsquo;s norm, the new norm $\|\cdot\|_3$ is given by:
$$\|(w_1, w_2)\|_3 := \max(s_1\cdot \|w_1\|_1, s_2\cdot \|w_2\|_2). \qquad$$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When we use this composite norm to derive optimizers&amp;mdash;following the same recipe we used in the first two sections of this post&amp;mdash;we end up deriving separate optimizers for each layer, but the scalar coefficients $s_i$ budget the learning rates across layers.&lt;/p&gt;
&lt;p&gt;We give much more detail on this construction, including extending it to other ways of combining modules, in our paper on the &lt;a href=&#34;https://arxiv.org/abs/2405.14813&#34;&gt;modular norm&lt;/a&gt;&amp;mdash;although the paper does not cover manifold optimization. You can also check out our paper on &lt;a href=&#34;https://arxiv.org/abs/2410.21265&#34;&gt;modular duality&lt;/a&gt; for more on building optimizers in the modular norm. The &lt;a href=&#34;https://modula.systems&#34;&gt;Modula project&lt;/a&gt; builds toward a programmatic implementation of this construction.&lt;/p&gt;
&lt;figure id=&#34;fig:product-manifold&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/modular-manifolds/svgs/product-manifold.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;The Cartesian product is a simple way to glue together two manifolds. For example, the product of a line and a disk is a cylinder. We get one copy of the disk at every point on the line.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;directions-for-future-work&#34;&gt;Directions for future work&lt;/h2&gt;
&lt;p&gt;We are excited about any research that tries to make neural network training as principled and automatic as the forward pass. The ideas in this post benefitted strongly from interactions with external researchers like Jianlin Su and Franz Louis Cesista. We would love to see more work on these topics from the community.&lt;/p&gt;
&lt;p&gt;Some possible directions for future work are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Modularity.&lt;/strong&gt; What manifolds should attention heads live on? Should embeddings be constrained differently than unembeddings? We can mix-and-match constraints in different parts of the network, or leave some tensors unconstrained.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Numerics.&lt;/strong&gt; Manifold constraints also place constraints on the range of values that individual weight entries can take. Does this impact numerics, or make low-precision training easier?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Convex optimization.&lt;/strong&gt; The manifold Muon algorithm involves running dual ascent. Can we apply more sophisticated convex optimization techniques to solve the dual problem faster or more reliably?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Convergence analysis.&lt;/strong&gt; How fast do these algorithms converge? Does good conditioning of the weight matrices benefit convergence? Is there more that we can say theoretically?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regularization.&lt;/strong&gt; Manifold constraints implicitly regularize the model. Could we design constraints or tune their radii to improve generalization?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Architecture-optimizer co-design.&lt;/strong&gt; While hard manifold constraints may not ultimately be the right way to constrain weight matrices, they exemplify the idea of tightly co-designing optimization algorithms with architecural components. Are there more opportunities here?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-Riemannian geometry.&lt;/strong&gt; Most work on manifold optimization works in a Riemannian world where distances are induced by inner products and norm balls are ellipsoids. But neural networks are different: matrices act as operators, and operator norms like the spectral norm do not emerge from inner products. This implies, for example, that norm balls can have sharp corners and there is no unique gradient flow. Is there more to be discovered in this non-Riemannian world?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Practical implementation.&lt;/strong&gt; Applying these techniques at scale requires efficient manifold operations on GPUs. The recent &lt;a href=&#34;https://arxiv.org/abs/2505.16932&#34;&gt;Polar Express&lt;/a&gt; paper shows promise for fast matrix sign computation. What other algorithmic innovations do we need?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;further-reading&#34;&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Manifold optimization.&lt;/strong&gt; &lt;a href=&#34;https://press.princeton.edu/absil&#34;&gt;Absil, Mahony &amp;amp; Sepulchre&lt;/a&gt;&amp;rsquo;s textbook is a standard reference. For the Stiefel manifold specifically, see &lt;a href=&#34;https://math.mit.edu/~edelman/publications/geometry_of_algorithms.pdf&#34;&gt;Edelman et al, 1998&lt;/a&gt;. These works live in a Riemannian world. Similarly most machine learning papers that consider optimization on the Stiefel manifold take a Riemannian point of view: see &lt;a href=&#34;https://arxiv.org/abs/2002.01113&#34;&gt;Li et al, 2020&lt;/a&gt;, &lt;a href=&#34;https://arxiv.org/abs/2205.14173&#34;&gt;Kong et al, 2022&lt;/a&gt; and &lt;a href=&#34;https://arxiv.org/abs/2508.17901&#34;&gt;Park et al, 2025&lt;/a&gt; for some examples.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Non-Riemannian geometry in machine learning.&lt;/strong&gt; &lt;a href=&#34;https://arxiv.org/abs/1708.00523&#34;&gt;Thomas Flynn&amp;rsquo;s paper&lt;/a&gt; from 2017 on duality structure gradient descent characterizes the neural network weight space as a &lt;em&gt;Finsler manifold&lt;/em&gt;, meaning a manifold equipped with a norm. It is well worth a read. Also see Jianlin Su&amp;rsquo;s &lt;a href=&#34;https://kexue.fm/archives/11221&#34;&gt;recent blog post&lt;/a&gt; on Stiefel Muon as well as Franz Louis Cesista&amp;rsquo;s &lt;a href=&#34;https://leloykun.github.io/ponder/steepest-descent-stiefel/&#34;&gt;blog post&lt;/a&gt; on a heuristic solution to Muon on the Stiefel manifold. Franz also wrote a &lt;a href=&#34;https://leloykun.github.io/ponder/steepest-descent-finsler/&#34;&gt;followup blog post&lt;/a&gt; generalizing the solution presented here. The &lt;a href=&#34;https://arxiv.org/abs/2502.07529&#34;&gt;Scion paper&lt;/a&gt; imposes weight constraints a different way via convex combinations and &lt;a href=&#34;https://papers.nips.cc/paper_files/paper/2015/hash/f50a6c02a3fc5a3a5d4d9391f05f3efc-Abstract.html&#34;&gt;Carlson et al, 2015&lt;/a&gt; wrote an early paper on (unconstrained) spectral descent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Modula project.&lt;/strong&gt; The goal of the Modula project is to build a library that automatically compiles steepest descent optimizers along with Lipschitz statements for general architectures. Check out the project page at &lt;a href=&#34;https://modula.systems&#34;&gt;https://modula.systems&lt;/a&gt; as well as our paper on the &lt;a href=&#34;https://arxiv.org/abs/2405.14813&#34;&gt;modular norm&lt;/a&gt; and &lt;a href=&#34;https://arxiv.org/abs/2410.21265&#34;&gt;modular duality&lt;/a&gt;. Our &lt;a href=&#34;https://arxiv.org/abs/2409.20325&#34;&gt;optimization anthology&lt;/a&gt; also provides an accessible route into this space of ideas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lipschitz-constrained deep learning.&lt;/strong&gt; There has been a lot of work on this topic. For example, check out &lt;a href=&#34;https://theses.hal.science/tel-04674274v1&#34;&gt;Louis Béthune&lt;/a&gt; and &lt;a href=&#34;https://dspace.mit.edu/bitstream/handle/1721.1/129313/1227782217-MIT.pdf?sequence=1&amp;amp;isAllowed=y&#34;&gt;Tsui-Wei Weng&amp;rsquo;s&lt;/a&gt; PhD theses. Usually work on this topic does not connect weight-Lipschitzness to optimizer design. See also &lt;a href=&#34;https://arxiv.org/abs/1811.05381&#34;&gt;Anil et al, 2018&lt;/a&gt; and our paper &lt;a href=&#34;https://arxiv.org/abs/2507.13338&#34;&gt;Newhouse et al, 2025&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;citation&#34;&gt;Citation&lt;/h2&gt;
&lt;p&gt;Please cite this work as:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Jeremy Bernstein, &amp;#34;Modular Manifolds&amp;#34;,
Thinking Machines Lab: Connectionism, Sep 2025.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or use the BibTeX citation:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;@article{bernstein2025manifolds,
  author = {Jeremy Bernstein},
  title = {Modular Manifolds},
  journal = {Thinking Machines Lab: Connectionism},
  year = {2025},
  note = {https://thinkingmachines.ai/blog/modular-manifolds/},
  doi = {10.64434/tml.20250926}
}
&lt;/code&gt;&lt;/pre&gt;&lt;script&gt;
// Auto-number figure references
document.addEventListener(&#39;DOMContentLoaded&#39;, function() {
  // Find all figures with IDs
  const figures = document.querySelectorAll(&#39;figure[id^=&#34;fig:&#34;]&#39;);
  const figureNumbers = new Map();

  // Assign numbers to figures based on document order
  figures.forEach((fig, index) =&gt; {
    const id = fig.id.replace(&#39;fig:&#39;, &#39;&#39;);
    figureNumbers.set(id, index + 1);
  });

  // Update all figure references
  document.querySelectorAll(&#39;span[data-ref]&#39;).forEach(span =&gt; {
    const ref = span.getAttribute(&#39;data-ref&#39;);
    const num = figureNumbers.get(ref);
    if (num) {
      span.textContent = num;
    }
  });
});
&lt;/script&gt;</description>
    </item>
    
    <item>
      <title>Defeating Nondeterminism in LLM Inference</title>
      <link>https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/</link>
      <pubDate>Wed, 10 Sep 2025 00:01:00 -0700</pubDate>
      
      <guid>https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/</guid>
      <description>&lt;p&gt;Reproducibility is a bedrock of scientific progress. However, it&amp;rsquo;s remarkably difficult to get reproducible results out of large language models.&lt;/p&gt;
&lt;p&gt;For example, you might observe that asking ChatGPT the same question multiple times provides different results. This by itself is not surprising, since getting a result from a language model involves “sampling”, a process that converts the language model’s output into a probability distribution and probabilistically selects a token.&lt;/p&gt;
&lt;p&gt;What might be more surprising is that even when we adjust the temperature down to 0&lt;label for=&#34;sn1&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn1&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;This means that the LLM always chooses the highest probability token, which is called greedy sampling.&lt;/span&gt; (thus making the sampling theoretically deterministic), LLM APIs are still &lt;strong&gt;not&lt;/strong&gt; deterministic in practice (see past discussions &lt;a href=&#34;https://152334h.github.io/blog/non-determinism-in-gpt-4/&#34;&gt;here&lt;/a&gt;, &lt;a href=&#34;https://community.openai.com/t/a-question-on-determinism/8185/2&#34;&gt;here&lt;/a&gt;, or &lt;a href=&#34;https://cookbook.openai.com/examples/reproducible_outputs_with_the_seed_parameter&#34;&gt;here&lt;/a&gt;). Even when running inference on your own hardware with an OSS inference library like vLLM or SGLang, sampling still isn&amp;rsquo;t deterministic (see &lt;a href=&#34;https://docs.vllm.ai/en/v0.7.0/getting_started/faq.html&#34;&gt;here&lt;/a&gt; or &lt;a href=&#34;https://docs.sglang.ai/references/faq.html&#34;&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;But why &lt;em&gt;aren’t&lt;/em&gt; LLM inference engines deterministic? One common hypothesis is that some combination of floating-point non-associativity and concurrent execution leads to nondeterminism based on which concurrent core finishes first. We will call this the “concurrency + floating point” hypothesis for LLM inference nondeterminism. For example, &lt;a href=&#34;https://arxiv.org/abs/2506.09501&#34;&gt;a recent arXiv preprint&lt;/a&gt; writes:&lt;/p&gt;
&lt;blockquote class=&#34;epigraph&#34;&gt;
&lt;p&gt;Floating-point arithmetic in GPUs exhibits non-associativity, meaning $(a + b) + c \neq a + (b + c)$ due to finite precision and rounding errors. This property directly impacts the computation of attention scores and logits in the transformer architecture, where parallel operations across multiple threads can yield different results based on execution order.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;label for=&#34;mn1&#34; class=&#34;margin-toggle&#34;&gt;&lt;/label&gt;
&lt;span class=&#34;marginnote&#34;&gt;You can also find the “concurrency + floating point” hypothesis repeated by others, like &lt;a href=&#34;https://community.openai.com/t/a-question-on-determinism/8185&#34;&gt;here&lt;/a&gt; (&lt;i&gt;&amp;ldquo;There are speed tradeoffs, and in order to make the endpoints fast GPUs are used, which do parallel [nondeterministic] calculations. Any modern GPU neural net calculations will be subject to these.&amp;quot;&lt;/i&gt;), or &lt;a href=&#34;https://x.com/hosseeb/status/1773146428594090473&#34;&gt;here&lt;/a&gt; (&lt;i&gt;&amp;ldquo;Because GPUs are highly parallelized, the ordering of additions or multiplications might be different on each execution, which can cascade into small differences in output.&amp;quot;&lt;/i&gt;).&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;While this hypothesis is not entirely wrong, it doesn&amp;rsquo;t reveal the full picture. For example, even on a GPU, running the same matrix multiplication on the same data repeatedly will always provide bitwise equal results. We&amp;rsquo;re definitely using floating-point numbers. And our GPU definitely has a lot of concurrency. Why don&amp;rsquo;t we see nondeterminism in this test?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;A&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;torch&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;randn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2048&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2048&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;device&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;cuda&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dtype&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;torch&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bfloat16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;B&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;torch&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;randn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2048&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2048&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;device&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;cuda&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dtype&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;torch&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bfloat16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;ref&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;torch&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;A&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;B&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;range&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;assert&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;torch&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;A&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;B&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ref&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;abs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;item&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To understand the true cause of LLM inference nondeterminism, we must look deeper.&lt;/p&gt;
&lt;p&gt;Unfortunately, even &lt;em&gt;defining&lt;/em&gt; what it means for LLM inference to be deterministic is difficult. Perhaps confusingly, the following statements are all simultaneously true:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Some kernels on GPUs are &lt;b&gt;nondeterministic&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;However, all the kernels used in a language model’s forward pass are &lt;b&gt;deterministic&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;Moreover, the forward pass of an LLM inference server (like vLLM) can also be claimed to be &lt;b&gt;deterministic&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;Nevertheless, from the perspective of anybody using the inference server, the results are &lt;b&gt;nondeterministic&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In this post, we will explain why the “concurrency + floating point” hypothesis misses the mark, unmask the true culprit behind LLM inference nondeterminism, and explain how to defeat nondeterminism and obtain truly reproducible results in LLM inference.&lt;/p&gt;
&lt;h2 id=&#34;the-original-sin-floating-point-non-associativity&#34;&gt;The original sin: floating-point non-associativity&lt;/h2&gt;
&lt;p&gt;Before talking about nondeterminism, it&amp;rsquo;s useful to explain why there are numerical differences at all. After all, we typically think of machine learning models as mathematical functions following structural rules such as commutativity or associativity. Shouldn’t there be a “mathematically correct” result that our machine learning libraries should provide us?&lt;/p&gt;
&lt;p&gt;The culprit is &lt;strong&gt;floating-point non-associativity.&lt;/strong&gt; That is, with floating-point numbers:&lt;/p&gt;
$$ (a + b) + c \neq a + (b + c) $$&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;0.1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;1e20&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;1e20&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;mf&#34;&gt;0.1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;1e20&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;1e20&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;0.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ironically, breaking associativity is what makes floating-point numbers useful.&lt;/p&gt;
&lt;p&gt;Floating-point numbers are useful because they allow for a “dynamic” level of precision. For the purposes of explanation, we will use base 10 (instead of binary), where floating-point numbers are in the format $\text{mantissa} * 10^\text{exponent}$. We will also use 3 digits for the mantissa and 1 digit for the exponent.&lt;/p&gt;
&lt;p&gt;For example, for the value 3450, we can represent it exactly as $3.45 * 10^3$. We can also represent much smaller values like 0.486 as $4.86 * 10^{-1}$. In this way, floating point allows us to represent both very small as well as very large values. In the sciences, we might say that floating point allows us to maintain a constant number of “significant figures”.&lt;/p&gt;
&lt;p&gt;If you add together two floating-point numbers with the same exponent, it looks similar to integer addition. For example, 123 ($1.23 * 10^2$) + 456 ($4.56 * 10^2$) results in 579 ($5.79 * 10^2$).&lt;/p&gt;
&lt;p&gt;But what happens when we add two floating-point numbers with different exponents, such as 1230 and 23.4?  In this case, the exact result is 1253.4. However, we can only maintain 3 digits of precision at a time. Floating-point addition will thus &lt;em&gt;drop&lt;/em&gt; the last 2 digits and obtain the value $1.25 * 10^3$ (or 1250).&lt;/p&gt;
&lt;figure&gt;
&lt;div class=&#34;figure-box&#34;&gt;
&lt;div class=&#34;floating-point-visualization&#34;&gt;
    &lt;div class=&#34;operation-line&#34;&gt;
        &lt;div class=&#34;number-group&#34;&gt;
            &lt;input type=&#34;number&#34; class=&#34;number-input&#34; id=&#34;num1&#34; value=&#34;1230&#34; placeholder=&#34;number A&#34;&gt;
            &lt;div class=&#34;representation&#34; id=&#34;num1-rep&#34;&gt;1.23 × 10²&lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&#34;operator&#34;&gt;+&lt;/div&gt;
        &lt;div class=&#34;number-group&#34;&gt;
            &lt;input type=&#34;number&#34; class=&#34;number-input&#34; id=&#34;num2&#34; value=&#34;23.4&#34; placeholder=&#34;number B&#34;&gt;
            &lt;div class=&#34;representation&#34; id=&#34;num2-rep&#34;&gt;3.45 × 10¹&lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&#34;operator&#34;&gt;=&lt;/div&gt;
        &lt;div class=&#34;result-representation&#34;&gt;
            &lt;div id=&#34;result-rep&#34;&gt;1.57&lt;span class=&#34;lost-digits&#34;&gt;5&lt;/span&gt; × 10²&lt;/div&gt;
            &lt;div class=&#34;exact-value&#34; id=&#34;exact-value&#34;&gt;Exact: 1575&lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;!-- javascript must go below figure html --&gt;
&lt;link rel=&#34;preload&#34; as=&#34;style&#34; href=&#34;css/floating-point-visualization.css&#34;&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;css/floating-point-visualization.css&#34; media=&#34;print&#34; onload=&#34;this.media=&#39;all&#39;&#34;&gt;
&lt;noscript&gt;&lt;link rel=&#34;stylesheet&#34; href=&#34;css/floating-point-visualization.css&#34;&gt;&lt;/noscript&gt;
&lt;script src=&#34;js/floating-point-visualization.js&#34; defer&gt;&lt;/script&gt;
&lt;figcaption&gt;We require 3 digits of precision to represent 1230 and 3 digits of precision to represent 23.4. However, adding these 2 numbers together results in a number that requires 5 digits of precision to represent (1253.4). Our floating-point format must then drop the 34 off the end. In some sense, we have effectively rounded our original 23.4 to 20.0 before adding it. &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;At this point, however, we’ve destroyed information. Note that this can happen every time we add two floating-point numbers with different “scales” (i.e. different exponents). And adding together floating-point numbers with different exponents happens all of the time. In fact, if we could guarantee that we never needed different exponents, we could just use integers!&lt;/p&gt;
&lt;p&gt;In other words, every time we add together floating-point numbers in a different order, we can get a completely different result. To take an extreme example, there are 102 possible different results for summing this array depending on the order.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;random&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;vals&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;1e-10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;1e-5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;1e-2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;vals&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;vals&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;v&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;v&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;vals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;results&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;random&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;seed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;42&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;range&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;10000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;random&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;shuffle&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;vals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;results&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;sum&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;vals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;results&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;sorted&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;results&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;There are &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;results&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; unique results: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;results&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Output:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# There are 102 unique results: [-8.326672684688674e-17, -7.45931094670027e-17, ..., 8.326672684688674e-17]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Although this is the underlying cause for non-identical outputs, it does not directly answer where the nondeterminism comes from. It doesn’t help us understand why floating-point values get added in different orders, when that happens, nor how it can be avoided.&lt;/p&gt;
&lt;p&gt;The answers lie in how kernels are implemented.&lt;/p&gt;
&lt;h2 id=&#34;why-dont-kernels-always-add-numbers-in-the-same-order&#34;&gt;Why don’t kernels always add numbers in the same order?&lt;/h2&gt;
&lt;p&gt;As mentioned above, one common explanation for why kernels add numbers in different orders is the “concurrency + floating point” hypothesis. The hypothesis states that if the order in which concurrent threads finish is nondeterministic and the accumulation order depends on the order in which concurrent threads finish (such as with an atomic add), our accumulation order will be nondeterministic as well.&lt;/p&gt;
&lt;p&gt;Confusingly, although this can lead to nondeterministic kernels, concurrency (and atomic adds) end up being completely uninvolved in LLM inference nondeterminism! To explain what the real culprit is, let’s first understand why modern GPU kernels rarely need atomic adds.&lt;/p&gt;
&lt;h2 id=&#34;when-are-atomic-adds-needed&#34;&gt;When are atomic adds needed?&lt;/h2&gt;
&lt;p&gt;Typically a GPU launches a program concurrently across many “cores” (i.e. SMs). As the cores have no inherent synchronization among them, this poses a challenge if the cores need to communicate among each other. For example, if all cores must accumulate to the same element, you can use an “atomic add” (sometimes known as a &amp;ldquo;&lt;a href=&#34;https://en.wikipedia.org/wiki/Fetch-and-add&#34;&gt;fetch-and-add&lt;/a&gt;&amp;rdquo;). The atomic add is “nondeterministic” &amp;mdash; the order in which the results accumulate is purely dependent on which core finishes first.&lt;/p&gt;
&lt;p&gt;Concretely, imagine that you are reducing a 100-element vector with 100 cores (e.g. &lt;code&gt;torch.sum()&lt;/code&gt;). Although you can load all 100 elements in parallel, we must eventually reduce down to a single element. One way to accomplish this is with some kind of “atomic add” primitive, where the hardware guarantees that all additions will be processed but does not guarantee the order.&lt;/p&gt;
&lt;figure&gt;
&lt;div class=&#34;atomic-add-viz&#34;&gt;
    &lt;canvas id=&#34;atomic-add-canvas&#34;&gt;&lt;/canvas&gt;
&lt;/div&gt;
&lt;link rel=&#34;preload&#34; as=&#34;style&#34; href=&#34;css/atomic-add-viz.css&#34;&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;css/atomic-add-viz.css&#34; media=&#34;print&#34; onload=&#34;this.media=&#39;all&#39;&#34;&gt;
&lt;noscript&gt;&lt;link rel=&#34;stylesheet&#34; href=&#34;css/atomic-add-viz.css&#34;&gt;&lt;/noscript&gt;
&lt;script src=&#34;js/atomic-add-viz-v2.js&#34; defer&gt;&lt;/script&gt;
&lt;figcaption&gt; The atomic add ensures that every core&#39;s contributions will be reflected in the final sum. However, it makes no guarantee about what &lt;i&gt;order&lt;/i&gt; the contributions will be added. The order depends entirely on which core finishes first, a nondeterministic property. Thus, executing the same parallel program multiple times can result in nondeterministic outputs. &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This is usually what folks mean by &amp;ldquo;nondeterminism&amp;rdquo; — you execute the same kernel twice with exactly the same inputs and you get a different result out. This is known as &lt;i&gt;run-to-run nondeterminism&lt;/i&gt;, where you run the same python script twice with the exact same dependencies but get a different result.&lt;/p&gt;
&lt;p&gt;Although concurrent atomic adds &lt;strong&gt;do&lt;/strong&gt; make a kernel nondeterministic, &lt;i&gt;atomic adds are not necessary for the vast majority of kernels.&lt;/i&gt; In fact, in the typical forward pass of an LLM, there is usually &lt;i&gt;not a single atomic add present.&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;This may be surprising, given that parallelizing a reduction can benefit from atomic adds. There are two main reasons why atomic adds do not end up being needed.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There is often sufficient parallelism along the “batch” dimension that we don’t need to parallelize along the reduction dimension. For example, let’s say that instead of reducing a single 100-dim vector we were reducing 500 vectors in parallel. In this case, we can reduce an entire vector in each core and allow every core to operate on a different vector.&lt;/li&gt;
&lt;li&gt;Over time, most neural network libraries have adopted a variety of strategies for achieving determinism without sacrificing performance. For example, we can perform a &amp;ldquo;split&amp;rdquo; (or tree) reduction, where we split the 100-element reduction into five 20-element reductions (thus achieving five-way parallelism). Then, to combine the remaining five elements, we can either perform a separate &amp;ldquo;clean-up&amp;rdquo; reduction (which isn&amp;rsquo;t parallelized, but operates over few enough elements to be cheap) or utilize a semaphore (which ensures that each concurrent thread-block will accumulate in a deterministic order).&lt;label for=&#34;sn2&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn2&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;The semaphore strategy can be found described &lt;a href=&#34;https://github.com/NVIDIA/cutlass/issues/1421#issuecomment-2016942675&#34;&gt;here&lt;/a&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Due to these two factors, avoiding atomics adds is a negligible performance penalty for the vast majority of neural network operations.&lt;/p&gt;
&lt;p&gt;There are still a couple of common operations that have significant performance penalties for avoiding atomics. For example, &lt;code&gt;scatter_add&lt;/code&gt; in PyTorch (&lt;code&gt;a[b] += c&lt;/code&gt;). The only one commonly used in LLMs, however, is FlashAttention backward.&lt;label for=&#34;sn3&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn3&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Fun fact: did you know that the widely used Triton implementations of FlashAttention backward actually differ algorithmically from Tri Dao&amp;rsquo;s FlashAttention-2 &lt;a href=&#34;https://arxiv.org/abs/2307.08691&#34;&gt;paper&lt;/a&gt;? The standard Triton implementation does additional recomputation in the backward pass, avoiding atomics but costing 40% more FLOPs!&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;However, the forward pass of an LLM involves &lt;i&gt;no operations that require atomic adds.&lt;/i&gt; Thus, the forward pass in an LLM is in fact “run-to-run deterministic.”&lt;/p&gt;
&lt;figure style=&#34;margin-top: 2rem; margin-bottom: 2.3rem;&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/svgs/deterministic.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;From the perspective of the inference server, it &lt;i&gt;is&lt;/i&gt; deterministic. Given the exact same user requests, it will always provide the same deterministic output. &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Wikipedia writes that &amp;ldquo;a deterministic algorithm is an algorithm that, given a particular input, will always produce the same output.&amp;rdquo; And in this case, given the exact same inputs (i.e. the exact requests the inference server is processing), the forward pass always produces the exact same outputs.&lt;/p&gt;
&lt;p&gt;However, the forward pass itself being “deterministic” is not sufficient to ensure that a system that includes it is deterministic. For example, what if our request’s output depended on the parallel user requests (e.g. batch-norm)? Since each individual request has no way of knowing what the parallel requests will be, from their perspective our overall LLM inference is also nondeterministic!&lt;/p&gt;
&lt;p&gt;As it turns out, our request’s output &lt;em&gt;does&lt;/em&gt; depend on the parallel user requests. Not because we’re somehow leaking information across batches &amp;mdash; instead, it’s because our forward pass lacks “batch invariance”, causing our request’s output to depend on the &lt;strong&gt;batch size&lt;/strong&gt; of our forward pass.&lt;/p&gt;
&lt;h3 id=&#34;batch-invariance-and-determinism&#34;&gt;Batch invariance and “determinism”&lt;/h3&gt;
&lt;p&gt;To explain batch invariance, let&amp;rsquo;s simplify the system and look solely at matmuls. You can assume that all matmul implementations are &amp;ldquo;run-to-run deterministic.&amp;quot;&lt;label for=&#34;sn4&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn4&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;This is not totally true, but most common matmul implementations do have this property.&lt;/span&gt;  However, they are not &amp;ldquo;batch-invariant.&amp;rdquo; In other words, when the batch size changes, each element in the batch can get different results.&lt;/p&gt;
&lt;p&gt;This is a fairly unusual property from a mathematical perspective. Matrix multiplication should be “independent” along every element in the batch &amp;mdash; neither the other elements in the batch nor how large the batch is should affect the computation results of a specific element in the batch.&lt;/p&gt;
&lt;p&gt;However, as we can observe empirically, this isn’t true.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;torch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;torch&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;set_default_device&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;cuda&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;B&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2048&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;D&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;4096&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;a&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;torch&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;linspace&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;B&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;D&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;reshape&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;B&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;D&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;b&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;torch&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;linspace&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;D&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;D&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;reshape&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;D&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;D&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Doing a matrix vector multiplication by taking&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# the first element of the batch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;out1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;torch&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[:&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Doing a matrix matrix multiplication and then taking&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# the first element of the batch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;out2&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;torch&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)[:&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;out1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;out2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;abs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;# tensor(1669.2500, device=&amp;#39;cuda:0&amp;#39;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that this &lt;i&gt;is&lt;/i&gt; &amp;ldquo;run-to-run deterministic.&amp;rdquo; If you run the script multiple times, it will deterministically return the same result.&lt;label for=&#34;sn5&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn5&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;It is not &amp;ldquo;hardware/software version invariant&amp;rdquo; — your GPU/PyTorch version may return a different value, but it should deterministically return the same value.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;However, when a non-batch-invariant kernel is used as part of a larger inference system, the system can become nondeterministic. When you make a query to an inference endpoint, the amount of load the server is under is effectively “nondeterministic” from the user’s perspective. The load determines the batch size that the kernels are run under, and thus changes the eventual result of each individual request!&lt;/p&gt;
&lt;figure style=&#34;margin-top: 2.3rem;&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/svgs/nondeterministic.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption style=&#34;margin-top: 3rem;&#34;&gt;Although the inference server itself can be claimed to be &#34;deterministic&#34;, the story is different for an individual user. From the perspective of an individual user, the other concurrent users are not an &#34;input&#34; to the system but rather a nondeterministic property of the system. This makes LLM inference &#34;nondeterministic&#34; from the perspective of each user.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If you compose some property under which the kernel is not invariant (i.e. batch-size) with nondeterminism of that property (i.e. the load the server is under), you get a nondeterministic system.&lt;/p&gt;
&lt;p&gt;In other words, &lt;b&gt;the primary reason nearly all LLM inference endpoints are nondeterministic is that the load (and thus batch-size) nondeterministically varies!&lt;/b&gt; This nondeterminism is not unique to GPUs — LLM inference endpoints served from CPUs or TPUs will also have this source of nondeterminism.&lt;/p&gt;
&lt;p&gt;So, if we’d like to avoid nondeterminism in our inference servers, we must achieve batch invariance in our kernels. In order to understand how that can be achieved, let’s first take a look at why kernels don’t have batch invariance in the first place.&lt;/p&gt;
&lt;h2 id=&#34;how-do-we-make-kernels-batch-invariant&#34;&gt;How do we make kernels batch-invariant?&lt;/h2&gt;
&lt;p&gt;In order to make a transformer implementation batch-invariant, we must make every kernel batch-invariant. Luckily, we can assume that every pointwise operation is batch-invariant.&lt;label for=&#34;sn6&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn6&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Although this is true for all kernels in say, PyTorch, it&amp;rsquo;s not inherently true. For example, there are some kernel implementations on CPU that will use vectorized intrinsics on some parts of the array and non-vectorized intrinsics on other parts, and these intrinsics don&amp;rsquo;t necessarily always have bitwise identical numerics.&lt;/span&gt; Thus, we only need to worry about the 3 operations that involve reductions — RMSNorm, matrix multiplication, and attention.&lt;label for=&#34;sn7&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn7&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;Reductions related to parallelism are out of the scope of this discussion, but the same principles apply. One factoid that may be useful is that NVLink-Sharp in-switch reductions are deterministic on Blackwell as well as Hopper with CUDA 12.8+. As is the case with many things, this information can be found on NCCL&amp;rsquo;s &lt;a href=&#34;https://github.com/NVIDIA/nccl/issues/1497#issuecomment-3210819243&#34;&gt;github issues&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Conveniently, these are also ordered in ascending levels of difficulty. Each one requires some additional considerations to achieve batch invariance with reasonable performance. Let’s talk about RMSNorm first.&lt;/p&gt;
&lt;h3 id=&#34;batch-invariant-rmsnorm&#34;&gt;Batch-invariant RMSNorm&lt;/h3&gt;
&lt;figure class=&#34;half-width&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/svgs/rmsnorm-01.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;&lt;b&gt;Data Parallel RMSNorm&lt;/b&gt; Ideally, we&#39;d like to avoid communication between cores in our parallelization strategy. One way to achieve that is by assigning one batch-element to each core, thus guaranteeing that each reduction is done entirely within a single core. This is what&#39;s known as a &#34;data-parallel&#34; strategy, since we&#39;re simply parallelizing along a dimension that doesn&#39;t require communication. In this example, we have four rows and four cores, saturating our cores. &lt;/figcaption&gt;
&lt;/figure&gt;
RMSNorm can be implemented as:
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# x: [batch_size, hidden_dim]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# weight: [hidden_dim]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;rms_norm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;weight&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;x&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;torch&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rsqrt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;torch&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mean&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;x&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;**&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dim&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=-&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;keepdim&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;weight&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The requirement for batch invariance is that the &lt;b&gt;reduction order for each element must be fixed regardless of the batch-size of the kernel.&lt;/b&gt; Note that this doesn’t mean we must always use the same reduction strategy. For example, if we change the number of elements we’re reducing over, we can still be batch-invariant even if our reduction strategy changes.&lt;label for=&#34;sn8&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn8&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://github.com/Dao-AILab/quack/blob/main/media/2025-07-10-membound-sol.md&#34;&gt;The Quack&lt;/a&gt; blog post has some nice examples showing the hierarchy of various reduction strategies you can do (e.g. thread reduction, warp reduction, block reduction, cluster reduction).&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Thus, we only break batch invariance when our batch-size affects the reduction strategy.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at the standard parallelism strategy for RMSNorm. Generally, parallel algorithms benefit from minimizing communication across cores. &lt;label for=&#34;sn9&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn9&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;For the purpose of this discussion you can assume that when we refer to “cores” we mean SMs. More specifically, the property here that’s important is that the # of threadblocks our kernel launches is greater than the # of SMs.&lt;/span&gt; So, one strategy we can start with is to assign each batch element to one core, as seen in the above figure.&lt;/p&gt;
&lt;p&gt;Increasing our batch size doesn&amp;rsquo;t affect our reduction strategy; if a batch size of 200 provides sufficient parallelism to our kernel then a batch size of 2000 will &lt;em&gt;definitely&lt;/em&gt; provide sufficient parallelism.&lt;/p&gt;
&lt;figure class=&#34;half-width&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/svgs/rmsnorm-02.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;&lt;b&gt;Data Parallel RMSNorm for larger batches&lt;/b&gt; Extending the data-parallel strategy to larger batches is fairly straightforward --- instead of having each core handle one row you allow each core to handle different rows sequentially. This &lt;i&gt;preserves batch invariance&lt;/i&gt; as the reduction strategy for each batch element remains identical. &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;On the other hand, decreasing the batch size can pose challenges. Because we assign each batch element to one core, decreasing our batch size will eventually lead to having more cores than batch elements, leaving some cores idle.&lt;/p&gt;
&lt;p&gt;Upon encountering this situation, a good kernel engineer would reach for one of the solutions mentioned in the prior section (atomic adds or split reductions), maintaining good parallelism and thus, good performance. Unfortunately, this changes the reduction strategy, preventing this kernel from being batch-invariant.&lt;/p&gt;
&lt;figure class=&#34;half-width&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/svgs/rmsnorm-03.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;&lt;b&gt;Split-Reduction RMSNorm&lt;/b&gt; If we have a small batch size, our data-parallel strategy may no longer have sufficient parallelism to saturate our cores. In this case, it may be more efficient to &#34;split&#34; a reduction among multiple cores, allowing us to fully utilize our GPU. However, this &lt;i&gt;loses&lt;/i&gt; batch invariance, as we are no longer reducing each element in the same order.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The easiest solution is to simply ignore these cases altogether. This is not completely &lt;em&gt;unreasonable&lt;/em&gt; &amp;mdash; a small batch size means that the kernel is likely to execute quickly anyways, and so a slowdown may not be catastrophic.&lt;/p&gt;
&lt;p&gt;If we &lt;em&gt;were&lt;/em&gt; compelled to optimize this use case, one approach would be to consistently use a reduction strategy that has enough parallelism even for very small batch sizes. Such a reduction strategy would lead to an excess amount of parallelism for larger batch sizes but would allow us to achieve decent (but not peak) performance across the entire range of sizes.&lt;/p&gt;
&lt;h3 id=&#34;batch-invariant-matrix-multiplication&#34;&gt;Batch-invariant matrix multiplication&lt;/h3&gt;
&lt;figure class=&#34;half-width matmul&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/svgs/matmul-01.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;&lt;b&gt;Data Parallel Matmul&lt;/b&gt; Similar to RMSNorm, the standard parallelism strategy for matmuls is a &#34;data-parallel&#34; strategy, keeping the entire reduction in one core. It is most straightforward to think about splitting the output tensor into 2D tiles and assigning each tile to a different core. Each core then computes the dot products that belong to that tile, once again performing the entire reduction within one core.
&lt;p&gt;Unlike for RMSNorm, additional constraints around arithmetic intensity and utilizing tensorcores force us to split 2D tiles instead of individual output elements for efficient matmul kernels.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;At its core, you can view matrix multiplication as simply a pointwise operation followed by a reduction. Then, if we parallelize our matrix multiplication by chunking the &lt;strong&gt;output&lt;/strong&gt; into tiles, we have an analogous &amp;ldquo;data-parallel&amp;rdquo; kernel strategy that keeps each reduction within one core.&lt;/p&gt;
&lt;p&gt;Also similar to RMSNorm, it is possible for our &amp;ldquo;batch&amp;rdquo; dimensions (M and N) to become too small, forcing us to split along the reduction dimension (K). Despite having two &amp;ldquo;batch&amp;rdquo; dimensions, matmuls also require us to have much more &amp;ldquo;work&amp;rdquo; per core in order to leverage tensorcores effectively. For example, if you have a [1024, K] x [K, 1024] matmul and a standard 2D tile size of [128, 128], a data-parallel strategy would only be able to split this matmul into 64 cores, insufficient to saturate the GPU.&lt;/p&gt;
&lt;p&gt;Splitting along the reduction dimension in a matmul is known as a &lt;a href=&#34;https://github.com/NVIDIA/cutlass/blob/main/media/docs/cpp/efficient_gemm.md#parallelized-reductions&#34;&gt;Split-K Matmul&lt;/a&gt;. And just like RMSNorm, using this strategy breaks batch invariance.
&lt;label for=&#34;sn10&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn10&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;
Another interesting parallelism strategy for matmuls is stream-k. Stream-k is interesting because it has even &lt;i&gt;less&lt;/i&gt; invariance than typical matmuls. As discussed, most matmul libraries are not batch-invariant, but they&amp;rsquo;re at least what you could call batch-position-invariant (i.e. changing the position of the element &lt;em&gt;within&lt;/em&gt; the batch does not affect numerics). However, stream-k is not batch-position-invariant either! Its core insight is that you can get cleaner load-balancing by splitting along k in different ways for different output tiles, but taking advantage of this makes our kernel not batch-position-invariant either. &lt;/span&gt;&lt;/p&gt;
&lt;figure class=&#34;half-width matmul&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/svgs/matmul-03.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;&lt;b&gt;Split-K Matmul&lt;/b&gt; If our batch dimension is fairly small we may not have enough parallelism and require a split-k matmul. In this example, we split each reduction across two cores, which would accumulate separately and then combine their results at the end. However, splitting each reduction across two cores allows us to still leverage eight cores.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;!-- Todo (lowpri): Clean up writing here a bit more --&gt;
&lt;p&gt;There’s an additional complexity with matmuls &amp;mdash; tensor core instructions. Whereas with reductions we could simply operate on one row at a time, efficient matrix multiplication kernels must operate on an entire “tile” at a time.&lt;/p&gt;
&lt;p&gt;Each tensor-core instruction (like say, &lt;a href=&#34;https://docs.nvidia.com/cuda/parallel-thread-execution/#asynchronous-warpgroup-level-matrix-instructions-wgmma-mma&#34;&gt;&lt;code&gt;wgmma.mma_async.sync.aligned.m64n128k16&lt;/code&gt;&lt;/a&gt;) may have a different reduction order internally. One reason to use a different tensor-core instruction might be that the batch size is very small. For example, if we use a tensor-core PTX instruction that operates on a tile of length 256 but the batch size is only 32, we’re wasting almost all of that compute! At a batch-size of 1, the fastest kernels usually don’t use tensor cores at all.&lt;/p&gt;
&lt;figure class=&#34;half-width matmul&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/svgs/matmul-02.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;&lt;b&gt;Padded Tensor-Core Instructions&lt;/b&gt; If the batch size is too small, we may be in our situation where we can&#39;t fit even one of our 2D tiles in the output. In this case, it is most efficient to switch to a smaller tensor-core instruction or eschew tensor-cores altogether! However, both of these options prevent our kernel from being batch-invariant.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;So, the easiest way to ensure batch invariance for matmuls is to compile one kernel configuration and use that for all shapes. Although we will lose some performance, this isn&amp;rsquo;t typically disastrous in LLM inference. In particular, split-k is most needed when &lt;strong&gt;both&lt;/strong&gt; M and N are small, and luckily in our case, N (i.e. the model dim) is usually pretty large!&lt;/p&gt;
&lt;figure class=&#34;chart-line&#34;
        data-src=&#34;https://thinkingmachines.ai/data/batch-vs-cublas.txt&#34;
        data-seriesmeta=&#39;{&#34;names&#34;:[&#34;CuBLAS&#34;,&#34;Batch-Invariant&#34;],&#34;colors&#34;:[&#34;var(--blue)&#34;,&#34;#f59e0b&#34;]}&#39;
        data-show-points=&#34;false&#34;
        data-smooth=&#34;5&#34;
        data-x=&#39;{&#34;label&#34;:&#34;Batch-size&#34;,&#34;ticks&#34;:[0,1000,2000,3000,4000,5000,6000,7000,8200]}&#39;
        data-y=&#39;{&#34;label&#34;:&#34;TFLOPs&#34;,&#34;ticks&#34;:[0,100,200,300,400,500,600,700]}&#39;
        data-labels=&#39;{&#34;CuBLAS&#34;:{&#34;x&#34;:7700,&#34;y&#34;:668,&#34;dx&#34;:-4,&#34;dy&#34;:-8},&#34;Batch-Invariant&#34;:{&#34;x&#34;:7700,&#34;y&#34;:590,&#34;dx&#34;:-4,&#34;dy&#34;:-8}}&#39;
        data-series=&#39;[{&#34;lineWidth&#34;:1.4},{&#34;lineWidth&#34;:1.4}]&#39;&gt;
  &lt;figcaption&gt;Despite obtaining batch invariance, we only lose about 20% performance compared to cuBLAS. Note that this is not an optimized Triton kernel either (e.g. no TMA). However, some of the patterns in performance are illustrative of where our batch-invariant requirement loses performance. First, note that we lose a significant amount of performance at very small batch sizes due to an overly large instruction and insufficient parallelism. Second, there is a &#34;jigsaw&#34; pattern as we increase the batch-size that is caused by quantization effects (both tile and wave) that are typically ameliorated through changing tile sizes. You can find more on these quantization effects &lt;a href=&#34;https://www.thonking.ai/p/what-shapes-do-matrix-multiplications&#34;&gt;here&lt;/a&gt;.&lt;/figcaption&gt;
  &lt;svg viewBox=&#34;0 0 800 520&#34; width=&#34;100%&#34; height=&#34;auto&#34;&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;h3 id=&#34;batch-invariant-attention&#34;&gt;Batch-invariant attention&lt;/h3&gt;
&lt;figure class=&#34;half-width&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/svgs/attention-01.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;&lt;b&gt;FlashAttention2 Strategy&lt;/b&gt; We parallelize along Q, and reduce along K/V simultaneously. This means that our entire reduction can be kept within a single core, making it another data-parallel strategy.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;After obtaining batch invariance for matmuls, attention introduces two additional wrinkles &amp;mdash; fittingly,  because it contains two matmuls.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;As opposed to only reducing over the feature dimension like both RMSNorm and matmuls, we now reduce over the feature dimension &lt;em&gt;and&lt;/em&gt; sequence dimension.&lt;/li&gt;
&lt;li&gt;Due to the above, attention must deal with a variety of inference optimizations that affect how sequences get processed (chunked prefill, prefix caching, etc.).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Thus, to achieve determinism in LLM inference our numerics must be invariant to both how many requests are processed at once &lt;strong&gt;and&lt;/strong&gt; how each request gets sliced up in the inference engine.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s first walk through the standard parallelism strategy for attention, first introduced in FlashAttention2. Similar to RMSNorm and Matmul, the default strategy is a &amp;ldquo;data-parallel&amp;rdquo; strategy. Since we reduce along the key/value tensors, a data-parallel strategy can only parallelize along the query tensor.&lt;/p&gt;
&lt;p&gt;For example, depending on the inference engine’s choices, it’s possible that a sequence might get processed in several parts (such as in chunked prefill) or perhaps all at once (if the prefill isn’t split up). In order to achieve “batch invariance”, it’s necessary that the &lt;em&gt;reduction order for a given token does not depend on how many other tokens from its sequence are being simultaneously processed&lt;/em&gt;. If you reduce over the K/V values in the KV cache separately from the K/V values in the current tokens being processed (like in vLLM’s &lt;a href=&#34;https://github.com/vllm-project/vllm/blob/0ae43dbf8cb28a299ae724fc742b0c5bcddea868/vllm/attention/ops/prefix_prefill.py#L36&#34;&gt;Triton attention kernel&lt;/a&gt;), this can&amp;rsquo;t be achieved. For example, when processing the 1000th query token in a sequence, the reduction order must be identical regardless of whether 0 tokens are in the KV cache (prefill) or 999 tokens are in the KV cache (decoding).&lt;/p&gt;
&lt;figure class=&#34;half-width&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/svgs/attention-02.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;&lt;b&gt;FlashAttention with a KV Cache&lt;/b&gt; The reason why explicitly handling the KV cache separately from the current KV values breaks batch invariance is a bit subtle and is related to &#34;boundary conditions&#34;. In particular, imagine your block size is 32 but we currently have 80 elements in our KV cache. We then compute an additional 48 elements that aren&#39;t cached. In this case, we need three blocks (two full and one masked) to compute &#34;P cache&#34; and another two blocks (one full and one masked) to compute &#34;P&#34;. This is therefore five total blocks to compute our reduction when we only have four total blocks (i.e. 128) of elements to compute, which will definitely change our reduction order. 
&lt;p&gt;For example, if we instead had no elements in our KV Cache and were processing 128 elements altogether, we need to have identical numerics in both of these situations to ensure &amp;ldquo;batch invariance&amp;rdquo; for attention.&lt;/p&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;To resolve this, we can just update the KV cache and page table before the attention kernel itself, ensuring that our keys and values are always consistently laid out regardless of how many tokens are being processed.&lt;/p&gt;
&lt;p&gt;With this additional detail (as well as all the things mentioned in the previous section, like consistent tile sizes), we are able to achieve a batch-invariant attention implementation!&lt;/p&gt;
&lt;p&gt;However, there is a significant problem here. Unlike with matrix multiplication, the attention shapes we see in LLM inference often do require a split-reduction kernel, often known as Split-KV or FlashDecoding. This is because if we don’t parallelize along the reduction, we can only parallelize along the batch dimension, head dimension, and “query length” dimension. In the decode stage of attention, query length is very small, and so unless we have a very large batch size we are often unable to saturate the GPU.&lt;/p&gt;
&lt;p&gt;Unfortunately, it’s not as easy to ignore this case as it was for RMSNorm and Matmuls. For example, if you have a very long KV cache, the attention kernel may take a very long time despite only processing one request.&lt;/p&gt;
&lt;figure class=&#34;half-width&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/svgs/attention-03.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;&lt;b&gt;Fixed # Split-KV Strategy (i.e. FlashDecode)&lt;/b&gt; If our query length becomes very small (like it does during decoding), we may end up in a situation where there is very little parallelism in our kernel at all. In these cases, we&#39;ll need to once again split along the reduction dimension --- the KV dimension this time. The typical strategy for how to split along the KV dimension is to figure out how much parallelism we need and then divide the KV dimension evenly. For example, if our KV length was 1000 and we needed 4 splits, each core would handle 250 elements.
&lt;p&gt;This unfortunately also breaks batch invariance, as our precise reduction strategy depends on how many query tokens from the sequence we&amp;rsquo;re processing in any given request.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Furthermore, the split-reduction strategies commonly used for attention also pose challenges for batch invariance. For example, FlashInfer’s “balanced scheduling algorithm” chooses the largest split-size that can still saturate all the GPU’s cores, thus making the reduction strategy not “batch-invariant”. However, unlike with RMSNorm/Matmuls, it’s not sufficient to choose a fixed number of splits regardless of the batch size.&lt;/p&gt;
&lt;p&gt;Instead, to achieve batch invariance, we must adopt a “fixed split-size” strategy. In other words, instead of fixing the # of splits, we fix the size of each split and then end up with a varying number of splits. In this manner, we can guarantee that regardless of how many tokens we’re processing, we always perform the identical reduction order. &lt;label for=&#34;sn11&#34; class=&#34;sidenote-number&#34;&gt;&lt;/label&gt;&lt;input type=&#34;checkbox&#34; id=&#34;sn11&#34; class=&#34;margin-toggle&#34;/&gt;&lt;span class=&#34;sidenote&#34;&gt;This requires some internal FlexAttention changes that are not included in our code release. We will upstream them in the near future!&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&#34;half-width&#34;&gt;
&lt;img data-zoomable src=&#34;https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/svgs/attention-04.svg&#34; style=&#34;display: block; width: 100%; max-width: 800px; height: auto; margin: 0 auto;&#34; loading=&#34;lazy&#34; alt=&#34;&#34;&gt;
&lt;figcaption&gt;&lt;b&gt;Fixed Size Split-KV Strategy&lt;/b&gt; 
The only difference between this strategy and the previous strategy is that our splits are now &#34;fixed size&#34;. For example, if our KV length was 1000, instead of splitting it into four even length 250 splits, we would split it into three fixed-size length 256 splits and one length 232 split.
&lt;p&gt;This allows us to &lt;em&gt;preserve&lt;/em&gt; batch invariance as our reduction strategy is no longer dependent on how many query tokens we&amp;rsquo;re processing at once!&lt;/p&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;implementation&#34;&gt;Implementation&lt;/h2&gt;
&lt;p&gt;We provide a demonstration of deterministic inference on top of vLLM by leveraging its FlexAttention backend as well as torch.Library.
Through torch.Library, we’re able to substitute out most of the relevant PyTorch operators in an unintrusive way. You can find the library of “batch-invariant” kernels at &lt;a href=&#34;https://github.com/thinking-machines-lab/batch_invariant_ops&#34;&gt;thinking-machines-lab/batch-invariant-ops&lt;/a&gt;, as well as the vLLM example of running in &amp;ldquo;deterministic&amp;rdquo; mode.&lt;/p&gt;
&lt;h2 id=&#34;experiments&#34;&gt;Experiments&lt;/h2&gt;
&lt;h3 id=&#34;how-nondeterministic-are-completions&#34;&gt;How nondeterministic are completions?&lt;/h3&gt;
&lt;p&gt;We use &lt;code&gt;Qwen/Qwen3-235B-A22B-Instruct-2507&lt;/code&gt; and sample 1000 completions at temperature 0 with the prompt &amp;ldquo;Tell me about Richard Feynman&amp;rdquo; (non-thinking mode), generating 1000 tokens each. Surprisingly, we generate &lt;em&gt;80&lt;/em&gt; unique completions, with the most common of these occuring 78 times.&lt;/p&gt;
&lt;p&gt;Looking at where the completions differ, we see that the completions are actually identical for the first 102 tokens! The first instance of diverging completions occurs at the 103rd token. All completions generate the sequence &amp;ldquo;Feynman was born on May 11, 1918, in&amp;rdquo; However, 992 of the completions go on to generate &amp;ldquo;Queens, New York&amp;rdquo; whereas 8 of the completions generate &amp;ldquo;New York City&amp;rdquo;.&lt;/p&gt;
&lt;!-- At this point, we can also inspect the logprobs of each generation. # TODO: Fill this out a little more. --&gt;
&lt;p&gt;On the other hand, when we enable our batch-invariant kernels, all of our 1000 completions are identical. This is what we would mathematically expect from our sampler, but we aren&amp;rsquo;t able to achieve deterministic results without our batch-invariant kernels.&lt;/p&gt;
&lt;!-- ### Deterministic evals
One implication of deterministic completions is deterministic evals.
One benefit we’ve found of fully deterministic LLM inference is that we can also get deterministic evals. This can be advantageous for experiment reproducibility, validating small performance improvements, or simply writing reliable unit tests.

Running X eval, we can see that there is a Y variance in eval scores normally. However, when run in deterministic mode, we get exactly the same eval scores every time. --&gt;
&lt;h3 id=&#34;performance&#34;&gt;Performance&lt;/h3&gt;
&lt;p&gt;We have not put a significant effort into optimizing the performance of the batch-invariant kernels here. However, let&amp;rsquo;s run some experiments to verify that our performance remains usable.&lt;/p&gt;
&lt;p&gt;We will set up an API server with one GPU running Qwen-3-8B, and request 1000 sequences with an output length of between 90 and 110.&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Configuration&lt;/th&gt;
          &lt;th style=&#34;text-align: center&#34;&gt;Time (seconds)&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;vLLM default&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;26&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Unoptimized Deterministic vLLM&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;55&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;+ Improved Attention Kernel&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;42&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Much of the slowdown comes from the fact that the FlexAttention integration in vLLM has not been heavily optimized yet. Nevertheless, we see that performance is not &lt;em&gt;disastrous&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id=&#34;true-on-policy-rl&#34;&gt;True on-policy RL&lt;/h3&gt;
&lt;p&gt;As &lt;a href=&#34;https://fengyao.notion.site/off-policy-rl&#34;&gt;researchers have noted&lt;/a&gt;, the different numerics between training and inference implicitly turns our on-policy RL into off-policy RL.&lt;/p&gt;
&lt;p&gt;Of course, it is impossible to get bitwise identical results between training and inference if we can’t even get bitwise identical results from two identical inference requests. Then, deterministic inference enables us to also modify our training stack to obtain bitwise identical results between sampling and training, thus resulting in true on-policy RL.&lt;/p&gt;
&lt;p&gt;We run experiments in a RLVR setup on &lt;a href=&#34;https://arxiv.org/abs/2502.17387&#34;&gt;Bigmath&lt;/a&gt; with the RL policy initialized from the Qwen 2.5-VL instruct 8B with a max rollout length of 4096.&lt;/p&gt;
&lt;p&gt;If we train without off-policy correction (i.e. importance weighting), our reward collapses partway through training, whereas adding an off-policy correction term  allows training to proceed smoothly. But, if we achieve bitwise identical results between our sampler and trainer, we are fully on policy (i.e. 0 KL divergence) and can also train smoothly.&lt;/p&gt;
&lt;p&gt;We can also plot the KL-divergence in logprobs between our sampler and trainer, where all 3 runs have notably different behavior. When running with importance weighting, it stays around 0.001 with occasional spikes. However, running &lt;em&gt;without&lt;/em&gt; importance weighting eventually leads to a spike in KL-divergence around the same time that reward crashes. And, of course, when running &amp;ldquo;True On-Policy RL&amp;rdquo;, our KL-divergence stays flat at 0, indicating that there is &lt;em&gt;no&lt;/em&gt; divergence between the training policy and sampling policy.&lt;/p&gt;
&lt;!-- &lt;figure class=&#34;chart-line&#34;
        data-src=&#34;https://thinkingmachines.ai/data/wandb_export_2025-09-08T11_16_51.917-07_00.csv&#34;
        data-seriesmeta=&#39;{&#34;names&#34;:[&#34;With importance weighting&#34;,&#34;No importance weighting&#34;],&#34;colors&#34;:[&#34;#8ac926&#34;,&#34;#007f86&#34;]}&#39;
        data-show-points=&#34;false&#34;
        data-smooth=&#34;35&#34;
        data-x=&#39;{&#34;label&#34;:&#34;Step&#34;,&#34;ticks&#34;:[0,100,200,400,600,800,900,1000,1100]}&#39;
        data-y=&#39;{&#34;label&#34;:&#34;Mean_reward&#34;,&#34;ticks&#34;:[-0.1,0.0,0.1,0.2,0.3,0.4,0.5]}&#39;
        data-labels=&#39;{&#34;With Importance Weighting&#34;:{&#34;x&#34;:850,&#34;y&#34;:0.5,&#34;dx&#34;:-8,&#34;dy&#34;:-6},&#34;No Importance Weighting&#34;:{&#34;x&#34;:850,&#34;y&#34;:-0.03,&#34;dx&#34;:-8,&#34;dy&#34;:-6}}&#39;
        data-series=&#39;[{&#34;lineWidth&#34;:1.6},{&#34;lineWidth&#34;:1.6}]&#39;&gt;
  &lt;figcaption&gt;Without off-policy correction like importance weighting, RL is prone to catastrophic failure due to the &#34;off-policyness&#34; induced by numerical mismatches between the trainer and the sampler. However, if we obtain bitwise equivalence between the trainer and the sampler, we don&#39;t need any off-policy correction to train smoothly.&lt;/figcaption&gt;
  &lt;svg viewBox=&#34;0 0 800 520&#34; width=&#34;100%&#34; height=&#34;auto&#34;&gt;&lt;/svg&gt;
  
&lt;/figure&gt; --&gt;
&lt;!-- &lt;figure class=&#34;chart-line&#34;
        data-srcs=&#39;[&#34;/data/reward.csv&#34;,&#34;/data/kl.csv&#34;]&#39;
        data-seriesmeta=&#39;{&#34;colors&#34;:[&#34;#1f78b4&#34;,&#34;#e31a1c&#34;,&#34;#33a02c&#34;],&#34;names&#34;:[&#34;True On-Policy&#34;,&#34;No Importance Weighting&#34;,&#34;With Importance Weighting&#34;]}&#39;
        data-show-points=&#34;false&#34;
        data-smooth=&#34;0.1&#34;
        data-x=&#39;{&#34;label&#34;:&#34;Step&#34;,&#34;ticks&#34;:[0,50,100,150,200,250,300,318],&#34;domain&#34;:[0,319]}&#39;
        data-y=&#39;{&#34;label&#34;:&#34;Mean reward&#34;,&#34;ticks&#34;:[0.0,0.1,0.2,0.3,0.4,0.5,0.6], &#34;domain&#34;:[-0.1,0.6]}&#39;
        data-y2=&#39;{&#34;label&#34;:&#34;KL-Divergence&#34;,&#34;ticks&#34;:[0.0,0.001,0.002,0.003,0.004,0.005,0.0055],&#34;domain&#34;:[-0.001,0.0055]}&#39;
        data-labels=&#39;{&#34;True On-Policy&#34;:{&#34;x&#34;:215,&#34;y&#34;:0.32,&#34;dx&#34;:-16,&#34;dy&#34;:-4},&#34;No Importance Weighting&#34;:{&#34;x&#34;:215,&#34;y&#34;:0.27,&#34;dx&#34;:-16,&#34;dy&#34;:-4},&#34;With Importance Weighting&#34;:{&#34;x&#34;:215,&#34;y&#34;:0.22,&#34;dx&#34;:-16,&#34;dy&#34;:-4}}&#39;
        data-series=&#39;[{&#34;lineWidth&#34;:1.5},{&#34;lineWidth&#34;:1.4},{&#34;lineWidth&#34;:2.4}]&#39;&gt;
  &lt;figcaption&gt;Note that the run without importance weighting has a significant loss spike around Step 318, and this comes with a corresponding spike in KL-divergence of logprobs. Meanwhile, either using an off-policy correction or running with &#34;True On-Policy&#34; allows RL to continue smoothly. The blue line showing &#34;True On-Policy&#34; is not a bug --- it&#39;s just a flat line at 0. &lt;/figcaption&gt;
  &lt;svg viewBox=&#34;0 0 800 520&#34; width=&#34;100%&#34; height=&#34;auto&#34;&gt;&lt;/svg&gt;
  
&lt;/figure&gt; --&gt;
&lt;figure class=&#34;chart-line-2pane&#34;
        data-srcs=&#39;[&#34;/data/reward.csv&#34;,&#34;/data/kl.csv&#34;]&#39;
        data-seriesmeta=&#39;{&#34;colors&#34;:[&#34;#f4a261&#34;,&#34;#007f86&#34;,&#34;#8ac926&#34;],&#34;names&#34;:[&#34;True on-policy&#34;,&#34;No importance weighting&#34;,&#34;With importance weighting&#34;]}&#39;
        data-show-points=&#34;false&#34;
        data-smooth=&#34;0.1&#34;
        data-x=&#39;{&#34;label&#34;:&#34;Step&#34;,&#34;ticks&#34;:[0,50,100,150,200,250,300,318],&#34;domain&#34;:[0,319]}&#39;
        data-top-y=&#39;{&#34;label&#34;:&#34;Mean reward&#34;,&#34;ticks&#34;:[0.0,0.1,0.2,0.3,0.4,0.5,0.6], &#34;domain&#34;:[-0.1,0.6]}&#39;
        data-bottom-y=&#39;{&#34;label&#34;:&#34;KL-Divergence&#34;,&#34;ticks&#34;:[0.0,0.001,0.002,0.003,0.004,0.005],&#34;domain&#34;:[-0.001,0.0055]}&#39;
        data-labels=&#39;{&#34;True on-policy&#34;:{&#34;x&#34;:215,&#34;y&#34;:0.32,&#34;dx&#34;:-16,&#34;dy&#34;:-4},&#34;No importance weighting&#34;:{&#34;x&#34;:215,&#34;y&#34;:0.27,&#34;dx&#34;:-16,&#34;dy&#34;:-4},&#34;With importance weighting&#34;:{&#34;x&#34;:215,&#34;y&#34;:0.22,&#34;dx&#34;:-16,&#34;dy&#34;:-4}}&#39;
        data-top-series=&#39;[{&#34;lineWidth&#34;:1.5},{&#34;lineWidth&#34;:1.4},{&#34;lineWidth&#34;:2.4}]&#39;
        data-bottom-series=&#39;[{&#34;lineWidth&#34;:1.5},{&#34;lineWidth&#34;:1.4},{&#34;lineWidth&#34;:2.4}]&#39;&gt;
  &lt;figcaption&gt;Note that the run without importance weighting has a significant loss spike around Step 318, and this comes with a correspond ing spike in KL-divergence of logprobs. Meanwhile, either using an off-policy correction or running with &#34;True On-Policy&#34; allows RL to continue smoothly. The blue line showing &#34;True On-Policy&#34; is not a bug - it&#39;s just a flat line at 0. &lt;/figcaption&gt;
  &lt;svg viewBox=&#34;0 0 800 520&#34; width=&#34;100%&#34; height=&#34;auto&#34;&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Modern software systems contain many layers of abstractions. In machine learning, when we run into nondeterminism and subtle numerical differences it can often be tempting to paper over them. After all, our systems are already &amp;ldquo;probabilistic&amp;rdquo;, so what&amp;rsquo;s wrong with a little more nondeterminism? What&amp;rsquo;s wrong with bumping up the atol/rtol on the failing unit test? The difference in logprobs between the trainer and the sampler probably isn&amp;rsquo;t a real bug, right?&lt;/p&gt;
&lt;p&gt;We reject this defeatism. With a little bit of work, we &lt;em&gt;can&lt;/em&gt; understand the root causes of our nondeterminism and even solve them! We hope that this blog post provides the community with a solid understanding of how to resolve nondeterminism in our inference systems and inspires others to obtain a full understanding of their systems.&lt;/p&gt;
&lt;h2 id=&#34;citation&#34;&gt;Citation&lt;/h2&gt;
&lt;p&gt;Please cite this work as:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;He, Horace and Thinking Machines Lab, &amp;#34;Defeating Nondeterminism in LLM Inference&amp;#34;, 
Thinking Machines Lab: Connectionism, Sep 2025.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or use the BibTeX citation:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;@article{he2025nondeterminism,
  author = {Horace He and Thinking Machines Lab},
  title = {Defeating Nondeterminism in LLM Inference},
  journal = {Thinking Machines Lab: Connectionism},
  year = {2025},
  note = {https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/},
  doi = {10.64434/tml.20250910}
}
&lt;/code&gt;&lt;/pre&gt;</description>
    </item>
    
  </channel>
</rss>
