{
    "version": "https://jsonfeed.org/version/1",
    "title": "Cocoa with Love",
    "home_page_url": "https://www.cocoawithlove.com/",
    "feed_url": "https://www.cocoawithlove.com/feed.json",
    
    "author": {
        "name": "Matt Gallagher"
    },
    "items": [
         {
            "title": "Training an LLM in Swift, Part 2: macOS built-in frameworks",
            "id": "https://www.cocoawithlove.com/blog/macos-ml-frameworks.html",
            "url": "https://www.cocoawithlove.com/blog/macos-ml-frameworks.html",
            "content_html": "\u003cp\u003eIn this article, I\u0026rsquo;m going to look at some of the frameworks that are built into macOS for numerical algorithms. Fitting with the theme of this series, I\u0026rsquo;ll mostly be looking at frameworks that can also train an ML model. But there\u0026rsquo;s a \u003cem\u003elot\u003c/em\u003e of different approaches – Accelerate (BLAS), BNNS, CoreML, MPSGraph – and the real challenge is knowing which one to use – if they are even usable for training.\u003c/p\u003e\n\u003c!-- TOC --\u003e\n\u003ch2 id=\"what-on-earth-is-fchgelu\"\u003eWhat on earth is \u0026ldquo;fchGelu\u0026rdquo;?\u003c/h2\u003e\n\u003cp\u003eAs with last time, I\u0026rsquo;ll be talking about code in examples but I won\u0026rsquo;t really be explaining the mechanics of LLMs or the terminology. I\u0026rsquo;m really here to talk about the macOS frameworks, not the models. I know, it\u0026rsquo;s pretty cryptic but this isn\u0026rsquo;t a beginner\u0026rsquo;s course. If you want to learn more about the terminology, try watching Andrej Karpathy\u0026rsquo;s \u003ca href=\"https://www.youtube.com/watch?v=l8pRSuU81PU\"\u003eLet\u0026rsquo;s reproduce GPT-2 (124M)\u003c/a\u003e where he goes through everything.\u003c/p\u003e\n\u003ch2 id=\"accelerate-blas\"\u003eAccelerate (BLAS)\u003c/h2\u003e\n\u003cp\u003eThe first macOS library for machine learning that I want to talk about is the Accelerate framework. Accelerate is really an umbrella framework for a handful of smaller libraries. Accelerate is a critical library in macOS but one that you can spend a whole career without directly using. It\u0026rsquo;s been around since Mac OS X 10.2 Jaguar as separate vecLib, BLAS and LAPACK libraries and then in Mac OS X 10.3 Panther was unified into Accelerate. Remember the big cat names? Fun times.\u003c/p\u003e\n\u003cp\u003eIn a general sense, Accelerate contains reusable algorithms optimized for SIMD vector instructions. In Swift, we don\u0026rsquo;t strictly \u003cem\u003eneed\u003c/em\u003e the Accelerate framework for SIMD vectorization (in the previous article, I used \u003ccode\u003eRelaxed.multiplyAdd\u003c/code\u003e and Swift\u0026rsquo;s autovectorization to get excellent SIMD vectorization) but it\u0026rsquo;s still really helpful to use Accelerate when you don\u0026rsquo;t \u003cem\u003ewant\u003c/em\u003e to stare at your own assembly.\u003c/p\u003e\n\u003cp\u003eAs an example, I recently \u003ca href=\"https://github.com/mattgallagher/CwlPdfLib\"\u003eadded rendering to a simple PDF parser library\u003c/a\u003e and used the following code based on Accelerate\u0026rsquo;s \u003ccode\u003evImage\u003c/code\u003e to apply an image mask:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ematte\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eguard\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003ematteBuffer\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e?\u003c/span\u003e \u003cspan class=\"n\"\u003evImage_Buffer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewidth\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ewidth\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eheight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eheight\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebitsPerPixel\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e8\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edefer\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003ematteBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efree\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003evImageBufferFill_ARGB8888\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u0026amp;\u003c/span\u003e\u003cspan class=\"n\"\u003ematteBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ematte\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ematte\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eg\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ematte\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003evImage_Flags\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ekvImageNoFlags\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003evImageAlphaBlend_ARGB8888\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u0026amp;\u003c/span\u003e\u003cspan class=\"n\"\u003ebaseBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"n\"\u003ematteBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"n\"\u003ebaseBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003evImage_Flags\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ekvImageNoFlags\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003ewhich ended up being about 5 times faster than the raw pixel iteration that I was using before (and about 20 times faster in Debug builds).\u003c/p\u003e\n\u003cp\u003eYou might think you could do this by drawing into a \u003ccode\u003eCGContext\u003c/code\u003e, and you\u0026rsquo;d be correct but guess what that uses internally? Same functions. All I\u0026rsquo;m doing here is cutting out the middleman and giving myself a little more direct control.\u003c/p\u003e\n\u003cp\u003eGetting back to the matrix multiplication topic from the previous article, Accelerate offers us its BLAS \u003ccode\u003esgemm\u003c/code\u003e implementation. BLAS stands for \u0026ldquo;Basic Linear Algebra Subprograms\u0026rdquo; and \u003ccode\u003esgemm\u003c/code\u003e stands for \u0026ldquo;Single precision GEneral Matrix Multiplication\u0026rdquo;. Having someone else optimize matrix multiplication is good but Accelerate BLAS offers another key advantage: it lets us access the Apple Silicon AMX unit without the ugly hacks that I needed in the previous article.\u003c/p\u003e\n\u003cp\u003eTo see how it works, let\u0026rsquo;s consider the basic (naïve) matrix multiplication kernel in Swift from last time:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003ematmul_forward\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kr\"\u003einout\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e]?,\u003c/span\u003e \u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003et\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ebt\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003et\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003eval\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e?[\u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e??\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"n\"\u003eval\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eval\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eDoing the same thing with BLAS looks like this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003ematmul_forward\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kr\"\u003einout\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e]?,\u003c/span\u003e \u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ecblas_sgemm\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eCblasColMajor\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eCblasTrans\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eCblasNoTrans\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eguard\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003ebias\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ewithUnsafeMutableBufferPointer\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003eoutBuffer\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eguard\u003c/span\u003e \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eoutBase\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eoutBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ebaseAddress\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003ecblas_saxpy\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eoutBase\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eadvanced\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eby\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe \u003ccode\u003ematmul_forward\u003c/code\u003e function is almost exactly the same as a typical \u003ccode\u003esgemm\u003c/code\u003e function, just fused with an additional bias step.\u003c/p\u003e\n\u003cp\u003eIgnoring all the other optimizations we needed in the previous article, just using \u003ccode\u003ecblas_sgemm\u003c/code\u003e in the 9 places in the \u0026ldquo;Basic Swift\u0026rdquo; implementation where it applies, gives us:\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining iterations/s\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eBasic Swift\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.054\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.014\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eAMX\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e5.884\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1.678\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eAccelerate BLAS\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e8.086\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e2.015\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eThis \u003cem\u003eone\u003c/em\u003e change (in 9 places) made the \u0026ldquo;Basic Swift\u0026rdquo; implementation 144 times faster. It\u0026rsquo;s even 1.2 times faster than my hacky attempt to use the AMX unit directly. The Accelerate BLAS implementation is really impressive. And it\u0026rsquo;s not just raw speed. Here\u0026rsquo;s a graph of the CPU usage running:\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/amx-versus-blas.png\"\n    alt=\"Instruments CPU usage graph for AMX (left half) and BLAS (right half) models\" width=\"500px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eInstruments CPU usage graph for AMX (left half) and BLAS (right half) models\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eThe Accelerate framework manages matrix multiplication that is both fast and very low CPU usage. The only thing that beats it is my \u0026ldquo;Tiled Metal\u0026rdquo; implementation running on the GPU.\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining iterations/s\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eAccelerate BLAS\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e8.086\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e2.015\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eTiled Metal\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e15.049\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e3.371\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eThat\u0026rsquo;s not really surprising. The GPU is practically made for high-performance matrix math and I\u0026rsquo;m running on an M3 Max which has quite a lot of GPU cores.\u003c/p\u003e\n\u003cp\u003eYou\u0026rsquo;d have to be crazy to think the CPU would beat that GPU score.\u003c/p\u003e\n\u003ch2 id=\"accelerate-bnns\"\u003eAccelerate (BNNS)\u003c/h2\u003e\n\u003cp\u003eLet\u0026rsquo;s get a little crazy.\u003c/p\u003e\n\u003cp\u003eRemember when I said that BLAS stands for \u0026ldquo;Basic Linear Algebra Subprograms\u0026rdquo;? Based on the same 1970s naming scheme, Accelerate also offers BNNS or \u0026ldquo;Basic Neural Network Subprograms\u0026rdquo;. Catchy. I\u0026rsquo;m going to pronounce it in my head as \u0026ldquo;buns\u0026rdquo;.\u003c/p\u003e\n\u003cp\u003eBNNS has had a few iterations. There are:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003ethe \u0026ldquo;Classic BNNS API\u0026rdquo; which includes \u003ccode\u003eBNNSNDArrayDescriptor\u003c/code\u003e stuff (deprecated)\u003c/li\u003e\n\u003cli\u003ethe \u003ccode\u003eBNNS.\u003c/code\u003e namespace (maybe the semi-Classic API because it\u0026rsquo;s also deprecated)\u003c/li\u003e\n\u003cli\u003ethe ML Compute framework which was based on at least one of the older BNNS APIs (and guess what: deprecated)\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eBNNSGraph\u003c/code\u003e ← this is the good one and includes \u003ccode\u003eBNNSTensor\u003c/code\u003e, \u003ccode\u003eBNNSGraph.Builder\u003c/code\u003e, \u003ccode\u003eBNNSGraph.Builder.Tensor\u003c/code\u003e and \u003ccode\u003eBNNSGraph.Context\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eFair warning: searching the documentation for BNNS is a mess. Xcode\u0026rsquo;s documentation has many flaws and trying to find the non-deprecated BNNS APIs runs into a few of them.\u003c/p\u003e\n\u003cp\u003eAll of the neural network implementations I\u0026rsquo;ve shown so far have been imperative. This means that they\u0026rsquo;ve been made of a set of operations that are (ignoring minor changes made by the compiler) executed from start to finish, without possibility of reinterpretation (since the computer doesn\u0026rsquo;t know where it\u0026rsquo;s going until the end).\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eBNNSGraph\u003c/code\u003e is, as the name implies, a \u0026ldquo;graph\u0026rdquo; API (a kind of declarative dataflow API). This means that you declare the entire journey and the API builds a set of instructions, but the API is allowed to make all kinds of optimizations by tracing backwards from the destination to see what is required.\u003c/p\u003e\n\u003cp\u003eThe general pattern for using \u003ccode\u003eBNNSGraph\u003c/code\u003e is to load your Swift \u003ccode\u003eArray\u0026lt;Float\u0026gt;\u003c/code\u003e data into \u003ccode\u003eBNNSGraph.Builder.Tensor\u0026lt;Float\u0026gt;\u003c/code\u003e values, interpret those values as different shapes, apply different mathematical functions, and then declare some values as the output to ultimately be written into different \u003ccode\u003eArray\u0026lt;Float\u0026gt;\u003c/code\u003e values at the end.\u003c/p\u003e\n\u003cp\u003eIt\u0026rsquo;s really not very different from what we\u0026rsquo;ve been doing up to this point but it replaces \u003ccode\u003efor\u003c/code\u003e loops with \u003ccode\u003ereshape\u003c/code\u003e (to change how subsequent functions stride through the values) and with some slightly bigger functions than the simple \u003ccode\u003e*\u003c/code\u003e, \u003ccode\u003e+\u003c/code\u003e and \u003ccode\u003eexp\u003c/code\u003e (or even \u003ccode\u003ecblas_sgemm\u003c/code\u003e) that we\u0026rsquo;ve relied upon to this point.\u003c/p\u003e\n\u003cp\u003eFor example, here\u0026rsquo;s an implementation of \u003ccode\u003ematmul_forward\u003c/code\u003e in \u003ccode\u003eBNNSGraph\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003ematmul_forward\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eTensor\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ereshape\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eto\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elinear\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ereshape\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eto\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn this case \u003ccode\u003elinear\u003c/code\u003e is essentially a multiplication by a transposed matrix (same as \u003ccode\u003ecblas_sgemm(CblasColMajor, CblasTrans, CblasNoTrans)\u003c/code\u003e from the BLAS example was for our row-major matrix) but in this case, it also includes a \u003ccode\u003ebias\u003c/code\u003e parameter to include what was a messy post-step in our BLAS implementation.\u003c/p\u003e\n\u003cp\u003eBut what I want to draw attention to in this case are the \u003ccode\u003ereshape\u003c/code\u003e calls on either side. The important point to note is that in a graph API like this, a reshape or a gather or some other traversal operator doesn\u0026rsquo;t necessarily get translated to a function. Operations like \u003ccode\u003ereshape\u003c/code\u003e tell subsequent operations that they might need to iterate in a particular order but otherwise, they don\u0026rsquo;t necessarily do any work. Furthermore, if the full output of this function isn\u0026rsquo;t needed (maybe it\u0026rsquo;s causally masked or otherwise removed from the output) then even \u003ccode\u003elinear\u003c/code\u003e might not do everything it would do in an imperative implementation.\u003c/p\u003e\n\u003cp\u003eThis is where a declarative API can optimize: it can roll operations together, skip unnecessary work, and can work out the best order to traverse memory (so there\u0026rsquo;s much less of the tiling and threading that filled the end of the previous article).\u003c/p\u003e\n\u003cp\u003eAnother point to notice is that we have a function named \u003ccode\u003elinear\u003c/code\u003e for performing the application of weights with a bias. That\u0026rsquo;s a pretty neural-network-specific task. While \u003ccode\u003eBNNSGraph\u003c/code\u003e could be used for other numerical processing, it\u0026rsquo;s very clearly catering to a neural network audience.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s our \u003ccode\u003eattention_forward\u003c/code\u003e block in \u003ccode\u003eBNNSGraph\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eattention_forward\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003emask\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003evalidAttention\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eBoolTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eNH\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eHS\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003escale\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eAttentionForwardResult\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eq\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eNH\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eHS\u003c/span\u003e\u003cspan class=\"p\"\u003e].\u003c/span\u003e\u003cspan class=\"n\"\u003esqueeze\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eaxis\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"n\"\u003etranspose\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eaxes\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ek\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mf\"\u003e1.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eNH\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eHS\u003c/span\u003e\u003cspan class=\"p\"\u003e].\u003c/span\u003e\u003cspan class=\"n\"\u003esqueeze\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eaxis\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"n\"\u003etranspose\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eaxes\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ev\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mf\"\u003e2.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eNH\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eHS\u003c/span\u003e\u003cspan class=\"p\"\u003e].\u003c/span\u003e\u003cspan class=\"n\"\u003esqueeze\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eaxis\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"n\"\u003etranspose\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eaxes\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003epreatt\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ematmul\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eother\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003escale\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003esavedPreatt\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003evalidAttention\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eselect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epreatt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eatt\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epreatt\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003emask\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"n\"\u003esoftmax\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eaxis\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eout\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eatt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ematmul\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eother\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ev\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etranspose\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eaxes\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ereshape\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eto\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eAttentionForwardResult\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epreatt\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esavedPreatt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eatt\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eatt\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI\u0026rsquo;m not even going to copy-paste the previous implementations of \u003ccode\u003eattention_forward\u003c/code\u003e that I\u0026rsquo;ve been using: they\u0026rsquo;re around 70 lines long. The ability to issue \u003ccode\u003esqueeze\u003c/code\u003e, \u003ccode\u003ematmul\u003c/code\u003e, \u003ccode\u003eselect\u003c/code\u003e and \u003ccode\u003esoftmax\u003c/code\u003e as single commands is a huge simplicity gain.\u003c/p\u003e\n\u003cp\u003eAnd the performance is something I never expected to see from the CPU:\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining iterations/s\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eTiled Metal\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e15.049\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e3.371\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eAccelerate BNNS\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e53.696\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e3.997\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eThat\u0026rsquo;s the CPU (including AMX) outperforming a high-end GPU (admittedly the GPU algorithm was only coarsely tuned). And yes, it is just the CPU. If you run the engines in Instruments\u0026rsquo; CoreML profiling tool, you can observe CPU, GPU and ANE usage and confirm that this engine is training 20% faster than a GPU algorithm on the CPU alone and 3 times faster for inference.\u003c/p\u003e\n\u003cp\u003eHowever, there are some downsides to \u003ccode\u003eBNNSGraph\u003c/code\u003e on the specific topic of training LLMs.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eit\u0026rsquo;s really not intended for training\u003c/li\u003e\n\u003cli\u003eIt\u0026rsquo;s not really intended for LLMs\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eAddressing the second point, first: \u003ccode\u003esqueeze\u003c/code\u003e, \u003ccode\u003ematmul\u003c/code\u003e, \u003ccode\u003eselect\u003c/code\u003e and \u003ccode\u003esoftmax\u003c/code\u003e are nice but LLM-targeted frameworks would have \u003ccode\u003escaledDotProductAttention\u003c/code\u003e (dramatically simplifying our \u003ccode\u003eattention_forward\u003c/code\u003e function) so it still feels like we\u0026rsquo;re building fundamental blocks from primitives.\u003c/p\u003e\n\u003cp\u003eAddressing the first point, I\u0026rsquo;d like to highlight the fact that \u003cem\u003eusually\u003c/em\u003e for a graph like this, you\u0026rsquo;d expect the only outputs to be the \u003ccode\u003elogits\u003c/code\u003e (the predictions for output tokens). However, in this case, I\u0026rsquo;ve had to output a \u003cem\u003elot\u003c/em\u003e more: I\u0026rsquo;ve had to output every single projection from every layer.\u003c/p\u003e\n\u003cp\u003eHalf the file is taken up by a sea of these outputs appended to the graph:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eln1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eln1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emean\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eln1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erstd\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eqkv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ereshape\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eto\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebatchSize\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esequenceLength\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e]))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eattention\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ereshape\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eto\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebatchSize\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esequenceLength\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e]))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eattention\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epreatt\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eattention\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eatt\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eattproj\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eresidual2\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eln2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eln2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emean\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eln2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erstd\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efch\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efchGelu\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efcproj\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoutputs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eresidual\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAnd this repeats for every layer.\u003c/p\u003e\n\u003cp\u003eWhy so many outputs? Because on each training iteration, I need to build the backward pass using all these outputs as inputs, so I can update the weights to progress the training. There\u0026rsquo;s not really an optimized way to update weights. All the engines I\u0026rsquo;ve shown so far have needed to hold onto projection values for later updates so needing to do this might not seem strange. But this is a graph API and graph APIs can do better.\u003c/p\u003e\n\u003ch2 id=\"mpsgraph\"\u003eMPSGraph\u003c/h2\u003e\n\u003cp\u003eLet\u0026rsquo;s jump to \u003ccode\u003eMPSGraph\u003c/code\u003e. \u0026ldquo;MPS\u0026rdquo; stands for \u0026ldquo;Metal Performance Shaders\u0026rdquo; so it won\u0026rsquo;t be a surprise to hear that it\u0026rsquo;s going to run in Metal on the GPU.\u003c/p\u003e\n\u003cp\u003eIn general, there\u0026rsquo;s a lot in common between \u003ccode\u003eMPSGraph\u003c/code\u003e and \u003ccode\u003eBNNSGraph\u003c/code\u003e: same tensors, same reshaping, and most of the same \u003ccode\u003egather\u003c/code\u003e and \u003ccode\u003esoftMax\u003c/code\u003e functions.\u003c/p\u003e\n\u003cp\u003eCompared to the Metal implementation I presented in the previous article – which used my own handwritten (but ultimately, only lightly tuned) matrix multiplication kernels – this API is heavily tuned for Metal performance (oh, hey, they put it in the name). And furthermore, it\u0026rsquo;s a graph API so it has scope for optimizing operations based on dependencies throughout the graph.\u003c/p\u003e\n\u003cp\u003eThe end result is the fastest API built into macOS:\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining iterations/s\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eAccelerate BNNS\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e53.696\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e3.997\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eMPSGraph\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e159.706\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e13.357\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eThat\u0026rsquo;s 3.3 times faster training than the already quite fast \u003ccode\u003eBNNSGraph\u003c/code\u003e. You can see why this is the library that PyTorch uses on macOS.\u003c/p\u003e\n\u003cp\u003eBut speed is not the only reason to choose \u003ccode\u003eMPSGraph\u003c/code\u003e for training. Look through the code and you\u0026rsquo;ll see that this is the first implementation where there are no functions for the backward pass. While there\u0026rsquo;s a certain symmetry between the forward and backward pass, the backward pass is usually much harder to write but here it is in \u003ccode\u003eMPSGraph\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003egradTensors\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003egraph\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egradients\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eof\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003emeanLoss\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ewith\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003evariablesMomentumsVelocities\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"bp\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003evariable\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThat one line computes the entire backward pass. This feature (auto-differentiation) is probably the key feature that defines frameworks that specialize in training since it cuts the difficulty of writing code by 50%-70%. The code knows (from the shape of the graph) how to generate the backward pass.\u003c/p\u003e\n\u003cp\u003eOkay, those were the good points. Unfortunately, there are quite a few negatives here, too.\u003c/p\u003e\n\u003cp\u003eFirst (on the topic of backward passes): \u003ccode\u003eMPSGraph\u003c/code\u003e contains a handful of functions that we can\u0026rsquo;t use because they fail, at runtime, to generate gradients. In particular, the \u003ccode\u003escaledDotProductAttention\u003c/code\u003e – which would considerably simplify our \u003ccode\u003eattention_forward\u003c/code\u003e function – seems to break the gradients (although the error it gives is at an unrelated point in the graph). Was I holding it wrong? I don\u0026rsquo;t know. It\u0026rsquo;s pretty hard to tell, when the error \u003ccode\u003eMPSGraphAutomaticDifferentiation.mm, line 53: error 'Couldn't get gradient Tensor for tensor of op : wpe'\u003c/code\u003e doesn\u0026rsquo;t even refer to a line in \u003cem\u003emy\u003c/em\u003e code.\u003c/p\u003e\n\u003cp\u003eI also encountered issues with the somewhat innocuous-looking \u003ccode\u003evariable\u003c/code\u003e function giving \u003ccode\u003eerror: failed to legalize operation 'mpsx.var_handle'\u003c/code\u003e (whatever that means).\u003c/p\u003e\n\u003cp\u003eThese are just 2 examples of problems that I wasn\u0026rsquo;t able to resolve but there were dozens and dozens of issues that came up while I was writing this code where some incomprehensible error message would be raised and I\u0026rsquo;d have to try replacing lines of code at random until the problem went away. In short, my experience with \u003ccode\u003eMPSGraph\u003c/code\u003e was filled with a litany of inexplicable and incomprehensible error messages. I nearly gave up on this implementation a few times – honestly it was just infuriating.\u003c/p\u003e\n\u003cp\u003eAnd then there\u0026rsquo;s the ergonomic issues. For a comparison, let\u0026rsquo;s look at \u003ccode\u003eBNNSGraph\u003c/code\u003e, a sane, Swift-feeling library:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eencoder_forward\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eBNNSGraph\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eBuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nb\"\u003eInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;,\u003c/span\u003e \u003cspan class=\"n\"\u003ewte\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ewpe\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epositionIndices\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eBNNSGraph\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eBuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nb\"\u003eInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;)\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eTensor\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003etokenEmbedding\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ewte\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egather\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eindices\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eaxis\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebatchDimensionCount\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003epositionEmbedding\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ewpe\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egather\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eindices\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003epositionIndices\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eaxis\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebatchDimensionCount\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003etokenEmbedding\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003epositionEmbedding\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eFairly tidy, not too complicated. Here\u0026rsquo;s the same code in \u003ccode\u003eMPSGraph\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eencoder_forward\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003egraph\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMPSGraph\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMPSGraphTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ewte\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMPSGraphTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ewpe\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMPSGraphTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eMPSGraphTensor\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eindices\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003egraph\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecast\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eto\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eint32\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;inputIndices\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003etokenEmbedding\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003egraph\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egather\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewithUpdatesTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ewte\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eindicesTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"bp\"\u003eindices\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eaxis\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebatchDimensions\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;tokenEmbedding\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ewpeSlice\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003egraph\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esliceTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewpe\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edimension\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;wpeSlice\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ebtcZeros\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003egraph\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econstant\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eshape\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eNSNumber\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003edataType\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efloat32\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003epositionEmbedding\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003egraph\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eaddition\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003egraph\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ereshape\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewpeSlice\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eshape\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eNSNumber\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;expandedWpe\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"n\"\u003ebtcZeros\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;positionEmbedding\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003egraph\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eaddition\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etokenEmbedding\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epositionEmbedding\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;encoder_forward\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eFilled with \u003ccode\u003e[NSNumber]\u003c/code\u003e, unneeded prepositions like \u003ccode\u003ewithUpdatesTensor\u003c/code\u003e, needlessly type-safe (a graph API should be able to pick the appropriate types automatically) and filled with the most cumbersome constants like \u003ccode\u003egraph.constant(0, shape: [B, T, C] as [NSNumber], dataType: .float32)\u003c/code\u003e where \u003ccode\u003eBNNSGraph\u003c/code\u003e uses simply \u003ccode\u003e0\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eMPSGraph\u003c/code\u003e is one of the most frustrating APIs I\u0026rsquo;ve ever used on macOS, seemingly pulling together the worst of Objective-C aesthetics, the incomprehensibility of C++ template errors and adventure game moon logic for constants and casting. Sorry to be so harsh on \u003ccode\u003eMPSGraph\u003c/code\u003e – getting API aesthetics and ergonomics right is hard – but yeah, I didn\u0026rsquo;t have fun writing this one.\u003c/p\u003e\n\u003cp\u003eOh, and there\u0026rsquo;s something that the training and inference numbers don\u0026rsquo;t show:\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/mpsgraph-versus-bnnsgraph.png\"\n    alt=\"Loss over time for MPSGraph and BNNSGraph\" width=\"500px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eLoss over time for MPSGraph and BNNSGraph\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eYes, \u003ccode\u003eMPSGraph\u003c/code\u003e is fast once it gets going. But this graph shows a 2-second delay before it\u0026rsquo;s able to emit a single token. Two seconds is a \u003cem\u003elong\u003c/em\u003e time to wait. Meanwhile, \u003ccode\u003eBNNSGraph\u003c/code\u003e has already performed its first 7 iterations.\u003c/p\u003e\n\u003cp\u003eOh, and take a close look at the loss curve on \u003ccode\u003eMPSGraph\u003c/code\u003e: it\u0026rsquo;s noticeably different at the 4th and 10th iterations (it\u0026rsquo;s like the first and second humps are convex instead of concave). Of all the libraries I used, \u003ccode\u003eMPSGraph\u003c/code\u003e is the only one that seems to either be using slightly different implementations of \u003ccode\u003eadamW\u003c/code\u003e or failing to preserve the same accuracy. I never really got to the bottom of this but it\u0026rsquo;s another one of those points that makes me question what\u0026rsquo;s going on with \u003ccode\u003eMPSGraph\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003ePlenty of negatives to consider for \u003ccode\u003eMPSGraph\u003c/code\u003e but you can\u0026rsquo;t deny this: it has the fastest matrix multiplication of any library that ships with macOS. In fact, in the previous article I showed a \u0026ldquo;Metal\u0026rdquo; implementation with \u0026ldquo;my own Metal matrix multiplication code\u0026rdquo;. The original code \u003ca href=\"https://github.com/regrettable-username/llm.metal\"\u003ellm.metal\u003c/a\u003e that I based that \u0026ldquo;Metal\u0026rdquo; implementation on used \u003ccode\u003eMPSMatrixMultiplication\u003c/code\u003e and was far faster than my implementation because of it.\u003c/p\u003e\n\u003ch2 id=\"coreml\"\u003eCoreML?\u003c/h2\u003e\n\u003cp\u003eLast but sadly least, is Apple\u0026rsquo;s favorite machine-learning framework on macOS: \u003ccode\u003eCoreML\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eHere are the problems with \u003ccode\u003eCoreML\u003c/code\u003e, when viewed from the perspective of someone who wants to write Swift code and train neural networks:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eYou can\u0026rsquo;t create \u003ccode\u003eCoreML\u003c/code\u003e programs from Swift\u003c/li\u003e\n\u003cli\u003eIt can\u0026rsquo;t train neural networks\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eI really don\u0026rsquo;t understand why there\u0026rsquo;s no way to create a \u003ccode\u003eCoreML\u003c/code\u003e .mlpackage file from Swift. Surely, we should be able to create a \u003ccode\u003eBNNSGraph\u003c/code\u003e or \u003ccode\u003eMPSGraph\u003c/code\u003e and then export to \u003ccode\u003eCoreML\u003c/code\u003e? But no, that\u0026rsquo;s not offered. The only way Apple offers to create a \u003ccode\u003eCoreML\u003c/code\u003e file is by starting in Python.\u003c/p\u003e\n\u003cp\u003eWhy would I want to use \u003ccode\u003eCoreML\u003c/code\u003e? Because it\u0026rsquo;s the only way to program the Apple Neural Engine (ANE).\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s ignore the entire premise of training a neural network in Swift for a moment and follow Apple\u0026rsquo;s suggested instructions for \u003ccode\u003eCoreML\u003c/code\u003e: create a PyTorch script similar to our LLM and convert it to a \u003ccode\u003eCoreML\u003c/code\u003e .mlpackage. The script I used for this is in the Tools directory of the repository, along with instructions in the README.md in that folder.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eI really hate having to do this because it involves spinning up a completely different technology stack compared to the tooling used for every other part of macOS development. It puts a needless barrier between app developers and ML engineers. And it\u0026rsquo;s a \u003cem\u003efussy\u003c/em\u003e technology stack: it needs to be a specific version of Python, PyTorch and NumPy just to support the specific version of coremltools that happens to exist, which is an incredibly thorny experience for someone just lightly dipping their toes in.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eMPSGraph\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e172.706\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eCoreML\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e176.816\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eFor inference, \u003ccode\u003eCoreML\u003c/code\u003e is faster than anything else we\u0026rsquo;ve tried. Admittedly, these numbers change by 2% or 3% every time I rerun them and sometimes \u003ccode\u003eMPSGraph\u003c/code\u003e is faster. And \u003ccode\u003eCoreML\u003c/code\u003e is running at \u003ccode\u003eFloat16\u003c/code\u003e (compared to the \u003ccode\u003eFloat32\u003c/code\u003e on the GPU). But despite these caveats, that\u0026rsquo;s \u003cem\u003efast\u003c/em\u003e. So it\u0026rsquo;s pretty annoying that we can\u0026rsquo;t use \u003ccode\u003eCoreML\u003c/code\u003e from Swift or for training.\u003c/p\u003e\n\u003cp\u003eOkay, that\u0026rsquo;s not entirely true. There are two different ways that we could \u003cem\u003ealmost\u003c/em\u003e do this.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eA \u003ccode\u003eCoreML\u003c/code\u003e .mlpackage contains the \u003ccode\u003eweight.bin\u003c/code\u003e as a separate file (we could update this as an ad hoc training step)\u003c/li\u003e\n\u003cli\u003eWe could use \u003ccode\u003eCoreML\u003c/code\u003e\u0026rsquo;s MLTensor\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eI did try updating the weights but because it takes 2 seconds to reload an .mlpackage, the performance is staggeringly slow and I abandoned the effort.\u003c/p\u003e\n\u003cp\u003eI also tried using \u003ccode\u003eMLTensor\u003c/code\u003e. It offers similar operators to \u003ccode\u003eBNNSGraph\u003c/code\u003e and really looks similar. Scroll back and look at the \u003ccode\u003eBNNSGraph\u003c/code\u003e \u003ccode\u003eattention_forward\u003c/code\u003e function that I showed and then look at this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eattention_forward\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMLTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003emask\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMLTensor\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eNH\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eHS\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003escale\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eAttentionForwardResult\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eq\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e..,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e..,\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e..,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e..].\u003c/span\u003e\u003cspan class=\"n\"\u003etransposed\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epermutation\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ek\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e..,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e..,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e..,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e..].\u003c/span\u003e\u003cspan class=\"n\"\u003etransposed\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epermutation\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ev\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e..,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e..,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e..,\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e..].\u003c/span\u003e\u003cspan class=\"n\"\u003etransposed\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epermutation\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003epreatt\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ematmul\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003escale\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eatt\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epreatt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ereplacing\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewith\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003einfinity\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"k\"\u003ewhere\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003emask\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"n\"\u003esoftmax\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eout\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eatt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ematmul\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ev\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etransposed\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epermutation\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ereshaped\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eto\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eAttentionForwardResult\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epreatt\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003epreatt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eatt\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eatt\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThey\u0026rsquo;re not line-for-line identical but they\u0026rsquo;re similar enough that you could confuse them for each other.\u003c/p\u003e\n\u003cp\u003eBut don\u0026rsquo;t be fooled, \u003ccode\u003eMLTensor\u003c/code\u003e is not like \u003ccode\u003eBNNSGraph\u003c/code\u003e. \u003ccode\u003eMLTensor\u003c/code\u003e isn\u0026rsquo;t really a graph API. It runs the operations immediately (no graph compile, no inputs, no outputs).\u003c/p\u003e\n\u003cp\u003eWhat\u0026rsquo;s the result?\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining iterations/s\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eAccelerate BNNS\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e53.696\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e3.997\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eMLTensor\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e3.305\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1.423\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eIt\u0026rsquo;s much slower.\u003c/p\u003e\n\u003cp\u003eDespite being \u0026ldquo;CoreML\u0026rdquo;, \u003ccode\u003eMLTensor\u003c/code\u003e does not run on the ANE. \u003ccode\u003eMLTensor\u003c/code\u003e runs on the GPU. In fact it seems to be a wrapper around different \u003ccode\u003eMPSGraph\u003c/code\u003e operations internally. But because each operation is dispatched separately (no batching, no optimization), it\u0026rsquo;s pretty slow.\u003c/p\u003e\n\u003ch2 id=\"download-all-the-code\"\u003eDownload all the code\u003c/h2\u003e\n\u003cp\u003eYou can download all the code used in this article, including the test harness app, from \u003ca href=\"https://github.com/mattgallagher/CwlLlmSwift\"\u003eCwlLlmSwift\u003c/a\u003e.\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusion\u003c/h2\u003e\n\u003cp\u003eThe ecosystem of machine learning APIs on macOS is a bit of a mess.\u003c/p\u003e\n\u003cp\u003eThe obvious primary API is \u003ccode\u003eMPSGraph\u003c/code\u003e but while \u003ccode\u003eMPSGraph\u003c/code\u003e is the fastest training API built into macOS, it\u0026rsquo;s so unpleasant to use and slow to compile that I don\u0026rsquo;t really want to use it again. \u003ccode\u003eMLTensor\u003c/code\u003e in CoreML is even harder to explain – it\u0026rsquo;s supposed to be used for glue steps between precompiled CoreML programs but it\u0026rsquo;s so slow that its use cases are going to be pretty narrow.\u003c/p\u003e\n\u003cp\u003eI find the Accelerate options easier to justify. Accelerate BLAS is a nice option if you just want to write everything yourself in plain code but you want to drop one change in to easily improve things. It\u0026rsquo;s a good distillation of the Accelerate framework\u0026rsquo;s role, in general.\u003c/p\u003e\n\u003cp\u003eAccelerate \u003ccode\u003eBNNSGraph\u003c/code\u003e is super impressive. While it lacks any help for training, it\u0026rsquo;s fairly low latency and extremely quick for something that\u0026rsquo;s entirely on CPU. It\u0026rsquo;s also a really nice API to use – easily the nicest of the APIs built into macOS.\u003c/p\u003e\n\u003cp\u003eWhat\u0026rsquo;s bizarre about \u003ccode\u003eBNNSGraph\u003c/code\u003e is that I can\u0026rsquo;t target the CPU or GPU as my needs dictate. While there are definitely differences between CPU and GPU storage and commands, to me, these feel like the sort of differences that could be handled at graph compile time. The entire point of declarative APIs is that they are \u003cem\u003enot\u003c/em\u003e generally coupled to explicit execution strategies. I really don\u0026rsquo;t understand why macOS needs separate \u003ccode\u003eBNNSGraph\u003c/code\u003e, \u003ccode\u003eMPSGraph\u003c/code\u003e and \u003ccode\u003eMLTensor\u003c/code\u003e APIs (along with the 3 or 4 other deprecated APIs) instead of having just a single, highly polished graph API that has separate compilation back-ends for CPU, GPU or ANE.\u003c/p\u003e\n\u003cp\u003eOn that topic, \u003ccode\u003eCoreML\u003c/code\u003e and the ANE are \u003cem\u003eamazing\u003c/em\u003e for inference performance but training or even building a CoreML program from Swift remains out of reach. Building this entire ecosystem around Python with no real interface for regular Swift app developers feels like a middle finger, gate-keeping us away from using \u003ccode\u003eCoreML\u003c/code\u003e and adding tooling and build environment complexity.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m writing this *\u003cem\u003echecks watch\u003c/em\u003e* the day before WWDC 2026, so who knows: maybe all this will change tomorrow with a big \u0026ldquo;AI, for reals this time\u0026rdquo; reveal. However, I doubt it. Apple are under so much scrutiny for the undelivered user-facing features of \u0026ldquo;Apple Intelligence\u0026rdquo; that I doubt API ergonomics are going to be their biggest focus.\u003c/p\u003e\n\u003cp\u003eBut even if macOS doesn\u0026rsquo;t change, there\u0026rsquo;s another option that I\u0026rsquo;ll look at in the third part of this series.\u003c/p\u003e\n",
            "date_published": "2026-06-08T10:54:02+10:00"
        }, {
            "title": "Training an LLM in Swift, Part 1: Taking matrix multiplication from Gflop/s to Tflop/s",
            "id": "https://www.cocoawithlove.com/blog/matrix-multiplications-swift.html",
            "url": "https://www.cocoawithlove.com/blog/matrix-multiplications-swift.html",
            "content_html": "\u003cp\u003eIn this article, I try to get my own handwritten matrix multiplication code running as fast as possible for training a Large Language Model (LLM) in Swift. The aim is to give some insight into the key steps for optimizing mathematics code in Swift. I also hope that these examples will offer a sense of scale about the capabilities of the different units on Apple Silicon – CPU, SIMD, AMX and GPU.\u003c/p\u003e\n\u003cp\u003eThis will be the first in a series where I look at training neural networks in Swift on Apple Silicon. Future articles will look at the \u003cem\u003emaybe-too-many\u003c/em\u003e frameworks Apple offer for machine learning on the Mac. Those established frameworks are what you should really use for matrix multiplication and machine learning (they\u0026rsquo;ve spent a few more years optimizing matrix kernels than I have).\u003c/p\u003e\n\u003cp\u003eBut until then, I\u0026rsquo;m having fun writing everything for myself in a \u0026ldquo;no frameworks, no libraries\u0026rdquo; plain code approach.\u003c/p\u003e\n\u003cp\u003eAnd I\u0026rsquo;m not just writing matrix multiplication kernels. The sample app will use these kernels as part of a full LLM implementation and the numbers I\u0026rsquo;ll quote will be for entire forward and backward training iterations. The reference implementation for this series will be Andrej Karpathy\u0026rsquo;s \u003ca href=\"https://github.com/karpathy/llm.c\"\u003ellm.c\u003c/a\u003e (a plain C implementation of a GPT2-compatible model). It\u0026rsquo;s a fairly basic model but it does contain all the necessary components and is representative of real-world workloads.\u003c/p\u003e\n\u003cp\u003eThat means it’s time for my favorite game: optimize Swift until it’s faster than C.\u003c/p\u003e\n\u003c!-- TOC --\u003e\n\u003ch2 id=\"backstory\"\u003eBackstory\u003c/h2\u003e\n\u003cp\u003eAbout two years ago, I dug up my engineering thesis from the early 2000s. It\u0026rsquo;s an image recognizer written in C++ that uses a neural network for classifying images. I wanted to get my old code running again but I hadn\u0026rsquo;t worked on ML code in a long time. It got annoying and I gave up.\u003c/p\u003e\n\u003cp\u003eFor all the discussion around LLMs in early 2024, it felt like no one was \u003cem\u003etraining\u003c/em\u003e neural networks on the Mac. At least, not in languages like Swift. I played with some Python libraries like PyTorch and TensorFlow but Python never does the calculations itself – it operates more like an orchestrator of another computational engine under the hood – and the separation left me feeling like I wasn\u0026rsquo;t in control.\u003c/p\u003e\n\u003cp\u003eA month later, Andrej Karpathy released \u003ca href=\"https://github.com/karpathy/llm.c\"\u003ellm.c\u003c/a\u003e. This reached me in a way that other machine learning content didn\u0026rsquo;t because nothing is hidden. It is around 1000 lines of plain C and (although it\u0026rsquo;s filled with some pretty cryptic variable names) it\u0026rsquo;s relatively readable.\u003c/p\u003e\n\u003cp\u003eSo naturally, I immediately rewrote it in Swift. And it was a lot of fun to play with.\u003c/p\u003e\n\u003cp\u003eOf course, playing with the code required some work to make it run fast. Some foreshadowing, here: the initial Swift implementation was really super slow. But optimization is a constant process: there\u0026rsquo;s always something more you can try.\u003c/p\u003e\n\u003cp\u003eWhich finally brings me to this article: I\u0026rsquo;m going to walk through the different explorations I wrote then (and a couple I\u0026rsquo;ve added in the last week) to make an LLM train \u003cem\u003efairly\u003c/em\u003e quickly \u003cstrong\u003ewithout\u003c/strong\u003e resorting to using a library. Most of the code will be in Swift (although I\u0026rsquo;ll show a Metal implementation at the end).\u003c/p\u003e\n\u003cp\u003eBy the way, \u003cstrong\u003eI will not be explaining how a neural network or an LLM works\u003c/strong\u003e. If you\u0026rsquo;re interested, Karpathy\u0026rsquo;s video \u003ca href=\"https://www.youtube.com/watch?v=kCc8FmEb1nY\"\u003eLet\u0026rsquo;s build GPT: from scratch, in code, spelled out.\u003c/a\u003e is practically the definitive guide to learning how GPT-like LLMs work and his earlier series starting with \u003ca href=\"https://youtu.be/PaCmpygFfXo?si=SQmmakwYc2B3BfMv\"\u003eThe spelled-out intro to language modeling: building makemore\u003c/a\u003e covers plenty of introductory concepts in a 5 video series if you want a more introductory lesson. Of course, both are in Python, so please come back here when you\u0026rsquo;re ready to see how we can do things in Swift.\u003c/p\u003e\n\u003cdiv class=\"centeraside\"\u003e\u003ciframe\n    src=\"https://www.youtube.com/embed/kCc8FmEb1nY?si=6d6iAffDXfie0jxr\"\n    title=\"YouTube video player\"\n    frameborder=\"0\"\n    allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n    referrerpolicy=\"strict-origin-when-cross-origin\"\n    allowfullscreen\n    style=\"display: block; width: 100%; max-width: 560px; height: auto; aspect-ratio: 16 / 9; margin: 0 auto;\"\n\u003e\u003c/iframe\u003e\n\u003c/div\u003e\n\u003ch2 id=\"llmc\"\u003ellm.c\u003c/h2\u003e\n\u003cp\u003eMachine learning is essentially the application of model weights to input data (called the forward pass, a.k.a. inference), then the calculation of error gradients and an update to those weights (the backward pass).\u003c/p\u003e\n\u003cp\u003eWe typically package these calculations together and try to make them run as fast as possible. These packages of operations might be called: \u0026ldquo;linear tensor projection\u0026rdquo;, \u0026ldquo;matrix multiplication\u0026rdquo;, or even a series of \u0026ldquo;vector dot products\u0026rdquo; (depending on how big or small you slice the units of work). It\u0026rsquo;s ultimately a loop that performs \u003ccode\u003ez += x * y\u003c/code\u003e a \u003cem\u003elot\u003c/em\u003e of times.\u003c/p\u003e\n\u003cp\u003eSince these matrix multiplications represent so much of the work in machine learning, I\u0026rsquo;m going to focus on the code that does this. I will be updating the rest of the implementation as I go, but only using the same improvements I\u0026rsquo;m showing to matrix multiplication.\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s start by looking at the \u003ccode\u003ematmul_forward\u003c/code\u003e from llm.c which is the core matrix multiplication used on the forward pass. It iterates over the input (\u003ccode\u003einp\u003c/code\u003e), multiplies by model weights (\u003ccode\u003eweight\u003c/code\u003e), and adds the result to the running total (\u003ccode\u003eval\u003c/code\u003e).\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e \u003cspan class=\"nf\"\u003ematmul_forward\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003efloat\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003et\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003et\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e \u003cspan class=\"n\"\u003eval\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebias\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"nb\"\u003eNULL\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e?\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.0f\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"n\"\u003eval\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eval\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe four layers of loops add some visual complexity but in reality, that \u003ccode\u003eval += inp[bt * C + i] * weight[o*C + i];\u003c/code\u003e line is the heart of a neural network. Like I said: \u003ccode\u003ez += x * y\u003c/code\u003e a \u003cem\u003elot\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eHow much?\u003c/p\u003e\n\u003cp\u003eThe \u003ccode\u003eval\u003c/code\u003e line contains 2 floating point operations but Karpathy says the number of floating point operations in a full training iteration should be roughly \u003ccode\u003e6 x N x D\u003c/code\u003e where \u003ccode\u003eN\u003c/code\u003e is the number of weights in the model (124,439,808 in our case) and \u003ccode\u003eD\u003c/code\u003e is \u003ccode\u003eB * T = 4 * 64 = 256\u003c/code\u003e for our app. So we\u0026rsquo;re talking about 6 x 124,439,808 x 256 ≈ 1.911×10¹¹ ≈ 0.2 trillion floating point operations per training iteration.\u003c/p\u003e\n\u003cp\u003eSo it\u0026rsquo;s got to run quick.\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining iterations/s\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003ellm.c\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.92\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.174\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eThe plain C code runs easily in a Swift Package. I\u0026rsquo;ve fixed the C implementation to always run at \u003ccode\u003e-O3\u003c/code\u003e optimization level (regardless of Xcode settings). Even at this optimization level, the C implementation manages just one training iteration every 7 seconds and inference at less than 1 token per second. A wonderful proof of concept but 10 times slower than would ever be useful.\u003c/p\u003e\n\u003ch2 id=\"basic-swift\"\u003eBasic Swift\u003c/h2\u003e\n\u003cp\u003eI\u0026rsquo;ve tried my best to keep the basic Swift version as true to the C version as possible:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003ematmul_forward\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kr\"\u003einout\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e]?,\u003c/span\u003e \u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003et\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ebt\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003et\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003evalue\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e?[\u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e??\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"n\"\u003evalue\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003evalue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSince the C code is inherently \u0026ldquo;unsafe\u0026rdquo;, I went ahead and gave the Swift code the same advantage by setting it to run with \u003ccode\u003e-remove-runtime-asserts\u003c/code\u003e (removing the runtime checking on array indices) and made sure to always run the app in \u0026ldquo;Release\u0026rdquo; configuration. So the Swift and C implementations should be fairly equivalent, right?\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eDon\u0026rsquo;t run in Debug.\u003c/strong\u003e I will only be quoting Release configuration numbers. While I have run \u003cem\u003esections\u003c/em\u003e of this in Debug, I\u0026rsquo;ve never waited around for a full 20 iteration training run in Debug. I usually keep the Scheme in Xcode set to \u0026ldquo;Release\u0026rdquo; – even during debugging.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eIf you read the backstory, I\u0026rsquo;ve already mentioned: this was \u0026ldquo;extremely slow\u0026rdquo;.\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining iterations/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining versus llm.c\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003ellm.c\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.926\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.175\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e100%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eBasic Swift\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.054\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.014\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e7.3%\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eThe Swift code is between 15 and 20 times slower. That\u0026rsquo;s an LLM producing 1 token every 19 seconds. Running 20 training iterations on this engine takes nearly 30 minutes. What on Earth is going on?\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThis performance represents about 2.8 Gflop/s. In 1999 \u003ca href=\"https://www.youtube.com/watch?v=OoxvLq0dFvw\"\u003eApple ran ads for the PowerMax G4 claiming its 1 Gflop/s capability made it a weapon in the eyes of the US military\u003c/a\u003e. Now 2.8 Gflop/s is completely unacceptable.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"span-egg-and-span\"\u003eSpan, Egg and Span\u003c/h2\u003e\n\u003cp\u003eInspecting in Instruments, by far the biggest performance cost in the previous run is \u003ccode\u003e_ArrayBuffer.beginCOWMutation()\u003c/code\u003e. Swift thinks someone else could be using our \u003ccode\u003eArray\u003c/code\u003es and even though they are unique (so we\u0026rsquo;re not getting array copies), the uniqueness checks alone are our biggest overhead.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eHuh?\u003c/strong\u003e Sometimes you run into issues that might just be a bug. This might be one of them. When I first worked on this code in 2024, my memory is that this wasn\u0026rsquo;t a problem. I don\u0026rsquo;t know if there\u0026rsquo;s been a regression or a safety hole was covered over leading to \u003ccode\u003e_ArrayBuffer.beginCOWMutation()\u003c/code\u003e clogging up performance. This problem also moves around when I disable function inlining with \u003ccode\u003einline(none)\u003c/code\u003e so it feels like the optimizer is just not able to do its job properly.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eIn any case, we can\u0026rsquo;t use \u003ccode\u003eArray\u003c/code\u003e and get the performance we need. Fortunately, Swift 6.2 gives us a reliable fix with basically zero overhead: \u003ccode\u003eMutableSpan\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003ematmul_forward\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kr\"\u003einout\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e]?,\u003c/span\u003e \u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003eout\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emutableSpan\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003et\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ebt\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003et\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003evalue\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e?[\u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e??\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"n\"\u003evalue\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003evalue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAll I\u0026rsquo;ve added is the \u003ccode\u003evar out = out.mutableSpan\u003c/code\u003e line at the top, shadowing \u003ccode\u003eout\u003c/code\u003e with its own \u003ccode\u003emutableSpan\u003c/code\u003e. I\u0026rsquo;ve also applied the same pattern throughout the file.\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining iterations/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining versus llm.c\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003ellm.c\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.926\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.175\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e100%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eBasic Swift\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.054\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.014\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e7.3%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eBasic Swift + mutableSpan\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.056\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.042\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e24.0%\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eCuriously, while this change didn\u0026rsquo;t have much impact on the forward pass, it did make the training iterations (forward + backward + update) more than 3 times faster.\u003c/p\u003e\n\u003ch2 id=\"chill-vibes\"\u003eChill vibes\u003c/h2\u003e\n\u003cp\u003eBut we need to turn our attention to why the forward pass is slow. Instruments confirms what we already know: the hottest line on the forward pass is the center of our loop \u003ccode\u003evalue += inp[bt * C + i] * weight[o * C + i]\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eIt\u0026rsquo;s time to face a hard truth: C has some compiler optimization flags that Swift doesn\u0026rsquo;t have. In this specific case, C has \u003ccode\u003e-ffast-math\u003c/code\u003e which allows C to use fused-multiply-addition (FMA) commands which perform a floating-point multiply and addition in a single command and generally doesn\u0026rsquo;t worry too much about precise correctness.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-asm\" data-lang=\"asm\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003exa34\u003c/span\u003e   \u003cspan class=\"no\"\u003efmadd\u003c/span\u003e               \u003cspan class=\"no\"\u003es0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es16\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003exa38\u003c/span\u003e   \u003cspan class=\"no\"\u003eldr\u003c/span\u003e                 \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"no\"\u003ex20\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"c1\"\u003e#0x4]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003exa3c\u003c/span\u003e   \u003cspan class=\"no\"\u003efmadd\u003c/span\u003e               \u003cspan class=\"no\"\u003es7\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es16\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es7\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003exa40\u003c/span\u003e   \u003cspan class=\"no\"\u003eldr\u003c/span\u003e                 \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"no\"\u003ex21\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"c1\"\u003e#0x4]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003exa44\u003c/span\u003e   \u003cspan class=\"no\"\u003efmadd\u003c/span\u003e               \u003cspan class=\"no\"\u003es4\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es16\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003exa48\u003c/span\u003e   \u003cspan class=\"no\"\u003eldr\u003c/span\u003e                 \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"no\"\u003ex22\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"c1\"\u003e#0x4]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003exa4c\u003c/span\u003e   \u003cspan class=\"no\"\u003efmadd\u003c/span\u003e               \u003cspan class=\"no\"\u003es6\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es16\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es6\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003exa50\u003c/span\u003e   \u003cspan class=\"no\"\u003eldr\u003c/span\u003e                 \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"no\"\u003ex23\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"c1\"\u003e#0x4]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003exa54\u003c/span\u003e   \u003cspan class=\"no\"\u003efmadd\u003c/span\u003e               \u003cspan class=\"no\"\u003es1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es16\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003exa58\u003c/span\u003e   \u003cspan class=\"no\"\u003eldr\u003c/span\u003e                 \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"no\"\u003ex24\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"c1\"\u003e#0x4]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003exa5c\u003c/span\u003e   \u003cspan class=\"no\"\u003efmadd\u003c/span\u003e               \u003cspan class=\"no\"\u003es2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es16\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003exa60\u003c/span\u003e   \u003cspan class=\"no\"\u003eldr\u003c/span\u003e                 \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"no\"\u003ex25\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"c1\"\u003e#0x4]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003exa64\u003c/span\u003e   \u003cspan class=\"no\"\u003efmadd\u003c/span\u003e               \u003cspan class=\"no\"\u003es3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es16\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es3\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003exa68\u003c/span\u003e   \u003cspan class=\"no\"\u003eldr\u003c/span\u003e                 \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"no\"\u003ex26\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"c1\"\u003e#0x4]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003exa6c\u003c/span\u003e   \u003cspan class=\"no\"\u003efmadd\u003c/span\u003e               \u003cspan class=\"no\"\u003es5\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es16\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es5\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe C inner loop is mostly just 8 unrolled applications of \u003ccode\u003efmadd\u003c/code\u003e (the fused-multiply-add instruction).\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eFun fact\u003c/strong\u003e: we live in the future, so if things like assembly language don\u0026rsquo;t make sense to you, you can drop them into your favorite LLM and it will translate it for you.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eWe don\u0026rsquo;t have \u003ccode\u003e-ffast-math\u003c/code\u003e in Swift, so instead we get separate multiplies and adds.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-asm\" data-lang=\"asm\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex164\u003c/span\u003e   \u003cspan class=\"no\"\u003efmul.4s\u003c/span\u003e             \u003cspan class=\"no\"\u003ev1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev5\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex168\u003c/span\u003e   \u003cspan class=\"no\"\u003emov\u003c/span\u003e                 \u003cspan class=\"no\"\u003es5\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev1\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex16c\u003c/span\u003e   \u003cspan class=\"no\"\u003emov\u003c/span\u003e                 \u003cspan class=\"no\"\u003es17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev1\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex170\u003c/span\u003e   \u003cspan class=\"no\"\u003emov\u003c/span\u003e                 \u003cspan class=\"no\"\u003es18\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev1\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex174\u003c/span\u003e   \u003cspan class=\"no\"\u003efmul.4s\u003c/span\u003e             \u003cspan class=\"no\"\u003ev2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev6\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex178\u003c/span\u003e   \u003cspan class=\"no\"\u003emov\u003c/span\u003e                 \u003cspan class=\"no\"\u003es6\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev2\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e/*...*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex1d0\u003c/span\u003e   \u003cspan class=\"no\"\u003efadd\u003c/span\u003e                \u003cspan class=\"no\"\u003es0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es7\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex1d4\u003c/span\u003e   \u003cspan class=\"no\"\u003efadd\u003c/span\u003e                \u003cspan class=\"no\"\u003es0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex1d8\u003c/span\u003e   \u003cspan class=\"no\"\u003efadd\u003c/span\u003e                \u003cspan class=\"no\"\u003es0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es24\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex1dc\u003c/span\u003e   \u003cspan class=\"no\"\u003efadd\u003c/span\u003e                \u003cspan class=\"no\"\u003es0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es23\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex1e0\u003c/span\u003e   \u003cspan class=\"no\"\u003efadd\u003c/span\u003e                \u003cspan class=\"no\"\u003es0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003es16\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSwift is trying to do a 4x loop unroll – that \u003ccode\u003efmul.4s\u003c/code\u003e is a SIMD operation for performing 4 multiplies – but all those \u003ccode\u003emov\u003c/code\u003e instructions and the separate additions at the end are hurting us.\u003c/p\u003e\n\u003cp\u003eWe need to use fused-multiply-add, like C is using.\u003c/p\u003e\n\u003cp\u003eFortunately, we have \u003ca href=\"https://github.com/apple/swift-numerics\"\u003eSwift-Numerics\u003c/a\u003e which offers \u003ccode\u003eRelaxed\u003c/code\u003e for our own version of fast math. Like the \u0026ldquo;Don\u0026rsquo;t panic\u0026rdquo; on the Hitchhiker\u0026rsquo;s Guide to the Galaxy, I\u0026rsquo;m sure the word \u0026ldquo;Relax\u0026rdquo; is just here to make us all calmer and enjoy chill vibes. Or it lets us relax the rules around rounding results so FMA is possible.\u003c/p\u003e\n\u003cp\u003eAll of our \u003ccode\u003ea += b * c\u003c/code\u003e and \u003ccode\u003ex = y + z\u003c/code\u003e operations can be improved with \u003ccode\u003eRelaxed.multiplyAdd\u003c/code\u003e and \u003ccode\u003eRelaxed.sum\u003c/code\u003e but I will specifically avoid applying these functions to the \u003ccode\u003egelu_backward\u003c/code\u003e function since the C implementation explicitly disables \u003ccode\u003e-ffast-math\u003c/code\u003e around that function.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003ematmul_forward\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kr\"\u003einout\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e]?,\u003c/span\u003e \u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003eout\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emutableSpan\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003et\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ebt\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003et\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003eval\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e?[\u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e??\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"n\"\u003eval\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eRelaxed\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emultiplyAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003eval\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eval\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe one change is there in the middle: \u003ccode\u003eval = Relaxed.multiplyAdd(inp[bt * C + i], weight[o * C + i], val)\u003c/code\u003e. Not as pretty to read but let\u0026rsquo;s see the performance.\u003c/p\u003e\n\u003cp\u003eNow our assembly looks like this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-asm\" data-lang=\"asm\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex178\u003c/span\u003e   \u003cspan class=\"no\"\u003efmla.4s\u003c/span\u003e             \u003cspan class=\"no\"\u003ev1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev16\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex17c\u003c/span\u003e   \u003cspan class=\"no\"\u003efmla.4s\u003c/span\u003e             \u003cspan class=\"no\"\u003ev0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev17\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev5\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex180\u003c/span\u003e   \u003cspan class=\"no\"\u003efmla.4s\u003c/span\u003e             \u003cspan class=\"no\"\u003ev2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev18\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev6\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex184\u003c/span\u003e   \u003cspan class=\"no\"\u003efmla.4s\u003c/span\u003e             \u003cspan class=\"no\"\u003ev3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev19\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev7\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex188\u003c/span\u003e   \u003cspan class=\"no\"\u003eadd\u003c/span\u003e                 \u003cspan class=\"no\"\u003ex6\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ex6\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"c1\"\u003e#0x40\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex18c\u003c/span\u003e   \u003cspan class=\"no\"\u003eadd\u003c/span\u003e                 \u003cspan class=\"no\"\u003ex30\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ex30\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"c1\"\u003e#0x40\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex190\u003c/span\u003e   \u003cspan class=\"no\"\u003esubs\u003c/span\u003e                \u003cspan class=\"no\"\u003ex13\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ex13\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"c1\"\u003e#0x10\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex194\u003c/span\u003e   \u003cspan class=\"no\"\u003eb.ne\u003c/span\u003e                \u003cspan class=\"err\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"no\"\u003especialized\u003c/span\u003e \u003cspan class=\"no\"\u003estatic\u003c/span\u003e \u003cspan class=\"no\"\u003eLLMBasicSwift.matmul_forward\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"no\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"no\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"no\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"no\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"no\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"no\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"no\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"no\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e:)\u003c/span\u003e\u003cspan class=\"err\"\u003e+\u003c/span\u003e\u003cspan class=\"mi\"\u003e0x168\u003c/span\u003e\u003cspan class=\"err\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex198\u003c/span\u003e   \u003cspan class=\"no\"\u003efadd.4s\u003c/span\u003e             \u003cspan class=\"no\"\u003ev0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex19c\u003c/span\u003e   \u003cspan class=\"no\"\u003efadd.4s\u003c/span\u003e             \u003cspan class=\"no\"\u003ev0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex1a0\u003c/span\u003e   \u003cspan class=\"no\"\u003efadd.4s\u003c/span\u003e             \u003cspan class=\"no\"\u003ev0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex1a4\u003c/span\u003e   \u003cspan class=\"no\"\u003efaddp.4s\u003c/span\u003e            \u003cspan class=\"no\"\u003ev0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e+0\u003c/span\u003e\u003cspan class=\"nf\"\u003ex1a8\u003c/span\u003e   \u003cspan class=\"no\"\u003efaddp.2s\u003c/span\u003e            \u003cspan class=\"no\"\u003es0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"no\"\u003ev0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eInterestingly, Swift is choosing the SIMD vectorized version of \u003ccode\u003efmadd\u003c/code\u003e (\u003ccode\u003efmla\u003c/code\u003e) but the premise is the same.\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining iterations/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining versus llm.c\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003ellm.c\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.926\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.175\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e100%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eBasic Swift\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.054\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.014\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e7.3%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eBasic Swift + mutableSpan\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.056\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.042\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e24.0%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eBasic Swift + mutableSpan + Relaxed\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.533\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.148\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e85.1%\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eThat\u0026rsquo;s a nearly 10x speed increase in tokens per second. But our training performance remains 15% slower than C. How can we close the final gap?\u003c/p\u003e\n\u003ch2 id=\"they-see-me-rollin\"\u003eThey see me rollin'\u003c/h2\u003e\n\u003cp\u003eTime to admit something: I\u0026rsquo;ve been showing the \u0026ldquo;naive\u0026rdquo; version of matrix multiplication from C. The real C function is a little uglier as it strides over the outer loop by 8 steps at a time, hoping the compiler will unroll the innermost loop. And C\u0026rsquo;s \u003ccode\u003e-O3\u003c/code\u003e obliges by delivering 8x loop unrolling.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eobt\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003eobt\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eB\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003eobt\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"n\"\u003eLOOP_UNROLL\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eLOOP_UNROLL\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eibt\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003eibt\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eLOOP_UNROLL\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003eibt\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eibt\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebias\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"nb\"\u003eNULL\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e?\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.0f\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e \u003cspan class=\"n\"\u003ew\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eibt\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003eibt\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eLOOP_UNROLL\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003eibt\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eobt\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eibt\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eibt\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003ew\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eibt\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003eibt\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eLOOP_UNROLL\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003eibt\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eobt\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eibt\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eibt\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt might not seem obvious but the critical line here in the C implementation is the \u003ccode\u003efloat result[LOOP_UNROLL];\u003c/code\u003e buffer to store 8 result values.\u003c/p\u003e\n\u003cp\u003ePreviously we simply couldn\u0026rsquo;t do this in Swift. Allocating an \u003ccode\u003eArray\u0026lt;Float\u0026gt;\u003c/code\u003e in the loop was simply too high a cost. In my 2024 implementation, all I could do was manually unroll the loop 8 times (something that\u0026rsquo;s fairly hideous to read).\u003c/p\u003e\n\u003cp\u003eHowever, Swift 6.2 has given us another useful feature here: \u003ccode\u003eInlineArray\u003c/code\u003e which finally matches C stack allocated arrays.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eobt\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"bp\"\u003estride\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efrom\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eto\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eBT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eby\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eLOOP_UNROLL\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003eresult\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eInlineArray\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"mi\"\u003e8\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;(\u003c/span\u003e\u003cspan class=\"n\"\u003erepeating\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e?[\u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e??\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ebt\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003espan\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eextracting\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edroppingFirst\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eobt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ew\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003espan\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eextracting\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edroppingFirst\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003er\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"bp\"\u003eindices\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eRelaxed\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emultiplyAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ew\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003er\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"bp\"\u003eindices\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[(\u003c/span\u003e\u003cspan class=\"n\"\u003eobt\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe assembly is just the same \u003ccode\u003efmla.4s\u003c/code\u003e and \u003ccode\u003efadd.4s\u003c/code\u003e instructions as the last example but more of them so there are slightly fewer branches and other overheads. Importantly: we\u0026rsquo;re now as similar as we can be to the C implementation.\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining iterations/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining versus llm.c\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003ellm.c\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.926\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.175\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e100%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eBasic Swift + mutableSpan + Relaxed\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.533\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.148\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e85.1%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eFast Swift\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.918\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.189\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e106.6%\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eC and Swift are essentially the same speed for inference and Swift is now slightly faster at training.\u003c/p\u003e\n\u003cp\u003eWhile Swift is faster here, I\u0026rsquo;m sure you could shake a few more flags at the C compiler and get it to spit out SIMD instructions and C would jump ahead again. The key point is that the two are now roughly equivalent.\u003c/p\u003e\n\u003ch2 id=\"multi-threaded\"\u003eMulti-threaded\u003c/h2\u003e\n\u003cp\u003eThe llm.c code contains many \u003ccode\u003e#pragma\u003c/code\u003e annotations like \u003ccode\u003e#pragma omp parallel for\u003c/code\u003e ahead of loops.\u003c/p\u003e\n\u003cp\u003eWhile it doesn\u0026rsquo;t run in the regular clang-compiled output used by the Swift package manager, these annotations are for OpenMP (Open Multi-Processing). OpenMP usually requires a modified C compiler but that\u0026rsquo;s how the llm.c code is intended to be run.\u003c/p\u003e\n\u003cp\u003eThis is where I think Swift finally loses to C in terms of readability. Not only is there no easy way to tag a loop for parallelism but there\u0026rsquo;s no Swift 6 safe way to slice an Array and concurrently work on the separate slices. Approaches like \u003ccode\u003eMutableSpan\u003c/code\u003e will complain that you\u0026rsquo;re concurrently accessing variables (even if they\u0026rsquo;re made from separate slices).\u003c/p\u003e\n\u003cdiv class=\"aside\"\u003e\u003cstrong\u003eWhy not Swift concurrency?\u003c/strong\u003e I did try \u003ccode\u003eTaskGroup\u003c/code\u003e. It is about the same speed (despite anecdotes claiming it would be slower). But it requires the \u003ccode\u003eout.withUnsafeMutableBufferPointer\u003c/code\u003e parameter escape its closure (theoretical safety violation) and offers no benefits.\u003c/div\u003e\n\u003cp\u003eWhich means that all our mutable arrays will need to resort to using \u003ccode\u003ewithUnsafeMutableBufferPointer\u003c/code\u003e and \u003ccode\u003e@unchecked Sendable\u003c/code\u003e wrappers so the compiler doesn\u0026rsquo;t complain about what we\u0026rsquo;re trying to do.\u003c/p\u003e\n\u003cp\u003eOur concurrency of choice will be \u003ccode\u003eDispatchQueue.concurrentPerform\u003c/code\u003e. This is important because its closure is not marked as \u003ccode\u003e@escaping\u003c/code\u003e so we can pass our immutable arrays as \u003ccode\u003eSpan\u003c/code\u003e without incurring copying, reference count checks or other overhead.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003etileCount\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eBT\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"n\"\u003eLOOP_UNROLL\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eworkerCount\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"bp\"\u003emax\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eProcessInfo\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eprocessInfo\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eactiveProcessorCount\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003echunkSize\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"bp\"\u003emax\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etileCount\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eworkerCount\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"n\"\u003eworkerCount\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003echunkCount\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etileCount\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003echunkSize\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"n\"\u003echunkSize\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ebias\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e?.\u003c/span\u003e\u003cspan class=\"n\"\u003espan\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003einp\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003espan\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eweight\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003espan\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ewithUnsafeMutableBufferPointer\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003eoutBuffer\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eoutStorage\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eSendableUnsafeMutableBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebaseAddress\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eoutBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ebaseAddress\u003c/span\u003e\u003cspan class=\"p\"\u003e!)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eDispatchQueue\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econcurrentPerform\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eiterations\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003echunkCount\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003echunk\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003estartTile\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003echunk\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003echunkSize\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eendTile\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"bp\"\u003emin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etileCount\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003estartTile\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003echunkSize\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003etile\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003estartTile\u003c/span\u003e\u003cspan class=\"p\"\u003e..\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eendTile\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eobt\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etile\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eLOOP_UNROLL\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003eresult\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eInlineArray\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"mi\"\u003e8\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;(\u003c/span\u003e\u003cspan class=\"n\"\u003erepeating\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e?[\u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e??\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ebt\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eextracting\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edroppingFirst\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eobt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ew\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eextracting\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edroppingFirst\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003er\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"bp\"\u003eindices\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eRelaxed\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emultiplyAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ew\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003er\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"bp\"\u003eindices\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"n\"\u003eoutStorage\u003c/span\u003e\u003cspan class=\"p\"\u003e[(\u003c/span\u003e\u003cspan class=\"n\"\u003eobt\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt\u0026rsquo;s not literally the worst code ever but:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003ethere\u0026rsquo;s a lot of overhead calculating the tiles and chunks and shadowing with \u003ccode\u003espan\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003ethe \u003ccode\u003eout.withUnsafeMutableBufferPointer\u003c/code\u003e scope and the \u003ccode\u003eDispatchQueue.concurrentPerform\u003c/code\u003e scope definitely add visual clutter\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThis is the iteration where I think: the C code looks better. It would be a lot nicer to have a slice-and-perform-concurrently operation on \u003ccode\u003eRangeReplaceableCollection\u003c/code\u003e that made this look as simple as replacing the \u003ccode\u003efor\u003c/code\u003e loop line.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ve applied this pattern to the four hottest loops in the training iterations: \u003ccode\u003ematmul_forward\u003c/code\u003e, \u003ccode\u003ematmul_backward\u003c/code\u003e, \u003ccode\u003eattention_forward\u003c/code\u003e and \u003ccode\u003eattention_backward\u003c/code\u003e.\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining iterations/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining versus llm.c\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003ellm.c\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.926\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.175\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e100%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003ellm.c (omp)\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003en/a\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.776\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e446%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eFast Swift\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.918\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.189\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e106.6%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eMultithreaded Swift\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e4.356\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1.014\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e558.5%\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cdiv class=\"centeraside\"\u003eI didn\u0026rsquo;t run llm.c through OMP so the training performance is taken from those quoted by Karpathy on the llm.c GitHub page (we\u0026rsquo;re both running M3 Max CPUs so it should be approximately comparable).\u003c/div\u003e\n\u003cp\u003eA 5.4x improvement. Pretty good, although I have 16 cores in my CPU so a mere 5x improvement for multi-threading isn\u0026rsquo;t perfect utilization. We\u0026rsquo;re likely getting constrained by our memory traversal, now.\u003c/p\u003e\n\u003cp\u003eThe biggest problem I see is that between loop unrolling, unsafe pointers, unchecked sendable wrappers and tracking the iterations for concurrent performing, the code is now so hideous it\u0026rsquo;s very unwieldy.\u003c/p\u003e\n\u003cp\u003eWe can do better.\u003c/p\u003e\n\u003ch2 id=\"top-secret-hacks\"\u003eTop-secret hacks\u003c/h2\u003e\n\u003cp\u003eI think we\u0026rsquo;ve done admirably. But it\u0026rsquo;s time to accept that if we want the fastest CPU-based matrix multiplication on Macs, we\u0026rsquo;re going to need more than the basic SIMD performance optimization that \u003ccode\u003eRelaxed.multiplyAdd\u003c/code\u003e and the underlying \u003ccode\u003efmla.4s\u003c/code\u003e instructions get us.\u003c/p\u003e\n\u003cp\u003eSo\u0026hellip; what\u0026rsquo;s the fastest CPU instruction on Apple Silicon for matrix multiplication?\u003c/p\u003e\n\u003cp\u003eThat\u0026rsquo;s where we arrive at a strange position: it\u0026rsquo;s a secret.\u003c/p\u003e\n\u003cp\u003eApple Silicon contains a unit called the AMX (Apple Matrix Coprocessor, no relation to Intel\u0026rsquo;s AMX). I\u0026rsquo;m not sure this is the official name of this unit because Apple have never officially called it anything other than \u0026ldquo;machine learning accelerators\u0026rdquo;. The only public way to access AMX instructions is through the Accelerate framework\u0026rsquo;s BLAS implementation but using a library conflicts with this article\u0026rsquo;s premise of framework-free implementations.\u003c/p\u003e\n\u003cp\u003eFortunately, people have \u003ca href=\"https://github.com/corsix/amx\"\u003ereverse engineered how the AMX unit works\u003c/a\u003e so based on some of the instructions from there, let\u0026rsquo;s see if we can write a faster matrix multiplication implementation.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eI\u0026rsquo;m having fun here but it should be clear: \u003cstrong\u003edon\u0026rsquo;t use Apple\u0026rsquo;s AMX instructions directly. Go through the Accelerate framework\u003c/strong\u003e for your own apps. Apple keep this \u0026ldquo;undocumented\u0026rdquo; so they can break binary compatibility at any time. The Accelerate framework will continue to work but this code will fail. And spoilers for the next article: Apple\u0026rsquo;s implementation is about 20% faster than mine, anyway.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eThe core instruction we need is \u003ccode\u003eAMX_MATFP\u003c/code\u003e. This instruction multiplies each element in a 16 element vector by each element in another 16 element vector, producing a 16 x 16 tile which is accumulated in the output tile. Doing this 16 times with appropriate input vectors can multiply an entire 16 x 16 matrix. We need to load the inputs so we also have \u003ccode\u003eAMX_LDX\u003c/code\u003e and  \u003ccode\u003eAMX_LDY\u003c/code\u003e and at the end, we emit the 16 x 16 accumulated tile using \u003ccode\u003eAMX_STZ\u003c/code\u003e. Along with \u003ccode\u003eAMX_LDZ\u003c/code\u003e (used to reset the accumulator tiles to a predefined zero tile) gives us a way to handle the inner loop of a tiled matrix multiply.\u003c/p\u003e\n\u003cp\u003eThat means that this algorithm doesn\u0026rsquo;t show the whole loop: it\u0026rsquo;s just the inner loop and an outer loop is needed to prepare the data and unify the result. I\u0026rsquo;m just going to show the inner loop.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eamxF32_16x64\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eoutTiles\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eUnsafeMutablePointer\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elhsPanel\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eUnsafePointer\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003erhsPanels\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eUnsafePointer\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003einnerCount\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ezeroTileRow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ewithUnsafeBufferPointer\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003ezeroBuffer\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eguard\u003c/span\u003e \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ezeroBase\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ezeroBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ebaseAddress\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003etile\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eaccumulatorCount\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003erow\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003etileRows\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eamx_ldz\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ezeroBase\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eamxZOperand\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eUInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etile\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eaccumulatorCount\u003c/span\u003e\u003cspan class=\"p\"\u003e))))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003einnerCount\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003elhsBase\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003elhsPanel\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003etileRows\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eamx_ldx\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elhsBase\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eamxXYOperand\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003etile\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eaccumulatorCount\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003erhsBase\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erhsPanels\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etile\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003einnerCount\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003etileRows\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003etileRows\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eamx_ldy\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erhsBase\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eamxXYOperand\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eamx_matfp\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eamxMatFPF32\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eUInt64\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etile\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e20\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003etile\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eaccumulatorCount\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003etileBase\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eoutTiles\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etile\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003etileRows\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003etileRows\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003erow\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003etileRows\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003erowBase\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eUnsafePointer\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;(\u003c/span\u003e\u003cspan class=\"n\"\u003etileBase\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003etileRows\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eamx_stz\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erowBase\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eamxZOperand\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eUInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etile\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eaccumulatorCount\u003c/span\u003e\u003cspan class=\"p\"\u003e))))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOnce again, it\u0026rsquo;s not \u003cem\u003ethat\u003c/em\u003e different in shape to the \u003ccode\u003eLOOP_UNROLL\u003c/code\u003e implementations that I called \u0026ldquo;Fast Swift\u0026rdquo;.\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining iterations/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining versus llm.c\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003ellm.c\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.926\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.175\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e100%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eMultithreaded Swift\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e4.356\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1.014\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e558.5%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eAMX\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e5.884\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1.678\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e958.8%\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eAnother 1.67 times faster on training. Tiling requires a lot of packing and scattering of data to get our row-major matrices into the required tile shape and I\u0026rsquo;m pretty sure I could be doing a better job, there. If I was more efficient with this, it would be easily over 2 times faster.\u003c/p\u003e\n\u003ch2 id=\"maximum-power-draw\"\u003eMaximum power draw\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe Metal code in \u003cstrong\u003ethis implementation is derived from an implementation by James Thompson\u003c/strong\u003e in \u003ca href=\"https://github.com/regrettable-username/llm.metal\"\u003ellm.metal\u003c/a\u003e. However, they used a library for matrix multiplication, so I\u0026rsquo;ve written my own Metal matrix multiplication code to keep the framework-free approach going.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eIn the previous section, I was careful to say \u0026ldquo;the fastest \u003cem\u003eCPU\u003c/em\u003e instruction on Apple Silicon for matrix multiplication\u0026rdquo; because, of course, we also have the GPU.\u003c/p\u003e\n\u003cp\u003eWhat does Metal code look like for matrix multiplication? Unlike the C and Swift code, it\u0026rsquo;s in two parts: the inner kernel (which we write in Metal/C++) and the outer invocation machinery (which stays on the Swift side).\u003c/p\u003e\n\u003cp\u003eFirst, the inner kernel:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ekernel\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e \u003cspan class=\"nf\"\u003ematmul_forward_kernel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003edevice\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e \u003cspan class=\"p\"\u003e[[\u003c/span\u003e\u003cspan class=\"n\"\u003ebuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)]],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"n\"\u003edevice\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e \u003cspan class=\"na\"\u003e[[buffer(1)]]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"n\"\u003edevice\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e \u003cspan class=\"na\"\u003e[[buffer(2)]]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"n\"\u003edevice\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e \u003cspan class=\"na\"\u003e[[buffer(3)]]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003econstant\u003c/span\u003e \u003cspan class=\"n\"\u003euint\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e \u003cspan class=\"n\"\u003eBT\u003c/span\u003e \u003cspan class=\"na\"\u003e[[buffer(4)]]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003econstant\u003c/span\u003e \u003cspan class=\"n\"\u003euint\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"na\"\u003e[[buffer(5)]]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003econstant\u003c/span\u003e \u003cspan class=\"n\"\u003euint\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"na\"\u003e[[buffer(6)]]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003euint2\u003c/span\u003e \u003cspan class=\"n\"\u003egid\u003c/span\u003e \u003cspan class=\"na\"\u003e[[thread_position_in_grid]]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003euint\u003c/span\u003e \u003cspan class=\"n\"\u003eoc\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003egid\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003euint\u003c/span\u003e \u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003egid\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e \u003cspan class=\"n\"\u003eBT\u003c/span\u003e \u003cspan class=\"o\"\u003e||\u003c/span\u003e \u003cspan class=\"n\"\u003eoc\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e \u003cspan class=\"n\"\u003esum\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eoc\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euint\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003esum\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eoc\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eoc\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esum\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSkipping over the somewhat heavy parameter block, you can see that all this really contains is the innermost loop of the four loops in the C and Basic Swift \u003ccode\u003ematmul_forward\u003c/code\u003e. Instead of loops over \u003ccode\u003eB\u003c/code\u003e, \u003ccode\u003eT\u003c/code\u003e, \u003ccode\u003eOC\u003c/code\u003e and \u003ccode\u003eC\u003c/code\u003e, we just have the loop over \u003ccode\u003eC\u003c/code\u003e here.\u003c/p\u003e\n\u003cp\u003eIterating over \u003ccode\u003eB * T\u003c/code\u003e and \u003ccode\u003eOC\u003c/code\u003e is handled in the outer machinery:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003ematmul_forward\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMTLBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMTLBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMTLBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMTLBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ectx\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMetalCommandContext\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ectx\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecompute\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecompute2D\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003epipelines\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ematmulForward\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ewidth\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eheight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eB\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ethreadsPerThreadgroup\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMTLSize\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewidth\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eheight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edepth\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ebuffers\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebias\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003euints\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eUInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"nb\"\u003eUInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"nb\"\u003eUInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e)]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ectx\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eendCompute\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003ewhere I\u0026rsquo;ve implemented compute2D as a helper that looks like this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003ecompute2D\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kc\"\u003e_\u003c/span\u003e \u003cspan class=\"n\"\u003epipeline\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMTLComputePipelineState\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ewidth\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eheight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ethreadsPerThreadgroup\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMTLSize\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ebuffers\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eMTLBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003euints\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eUInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003esetComputePipelineState\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epipeline\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebuf\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003ebuffers\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenumerated\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003esetBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebuf\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eoffset\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eindex\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003euintsCopy\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003euints\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003euintsCopy\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"bp\"\u003ecount\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003esetBytes\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u0026amp;\u003c/span\u003e\u003cspan class=\"n\"\u003euintsCopy\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMemoryLayout\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nb\"\u003eUInt32\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;.\u003c/span\u003e\u003cspan class=\"bp\"\u003estride\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eindex\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ebuffers\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"bp\"\u003ecount\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003edispatchThreads\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eMTLSize\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewidth\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ewidth\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eheight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eheight\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edepth\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ethreadsPerThreadgroup\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ethreadsPerThreadgroup\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eDo you remember when I said, about multi-threaded Swift: \u0026ldquo;It would be a lot nicer to have a slice-and-perform-concurrently operation\u0026rdquo;? That\u0026rsquo;s really what this is: carving up the \u003ccode\u003ebuffers\u003c/code\u003e into a set of tiles that can be concurrently acted upon. The \u003ccode\u003ewidth\u003c/code\u003e and \u003ccode\u003eheight\u003c/code\u003e parameters here get translated into the \u003ccode\u003egid.x\u003c/code\u003e and \u003ccode\u003egid.y\u003c/code\u003e parameters in the kernel code, allowing us to index into the \u003ccode\u003einp\u003c/code\u003e and \u003ccode\u003eweight\u003c/code\u003e buffers, in an almost identical fashion to the Basic Swift code.\u003c/p\u003e\n\u003cp\u003eYou can see that we\u0026rsquo;re invoking the \u003ccode\u003ematmulForward\u003c/code\u003e pipeline as a two dimensional set of \u0026ldquo;threads\u0026rdquo; (not CPU threads but GPU workers). If you imagine the \u003ccode\u003eB\u003c/code\u003e and \u003ccode\u003eT\u003c/code\u003e loops from the Basic Swift implementation flattened into a single \u003ccode\u003eB * T\u003c/code\u003e then we\u0026rsquo;re literally doing the same work, just on power-hungry GPU, not the CPU.\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining iterations/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining versus llm.c\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003ellm.c\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.926\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.175\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e100%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eAMX\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e5.884\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1.678\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e958.8%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eBasic Metal\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e4.29\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1.841\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1052.0%\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eIt\u0026rsquo;s not a huge improvement over the AMX implementation. Training is slightly faster but inference is slower. For a power-hungry, high end GPU, it\u0026rsquo;s not much of an improvement.\u003c/p\u003e\n\u003cp\u003eAs with everything else, we don\u0026rsquo;t magically get top-tier performance with the simplest, most naive approach.\u003c/p\u003e\n\u003cp\u003eOf course, adding \u0026ldquo;threading\u0026rdquo; on the GPU is much simpler than it was on the CPU. Doing nothing more than changing that \u003ccode\u003ethreadsPerThreadgroup\u003c/code\u003e parameter to \u003ccode\u003eMTLSize(width: 16, height: 16, depth: 1)\u003c/code\u003e will bump our times up to\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining iterations/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining versus llm.c\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003ellm.c\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.926\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.175\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e100%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eAMX\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e5.884\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1.678\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e958.8%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eBasic Metal\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e4.29\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1.841\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1052.0%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eThreaded Metal\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e6.211\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e2.431\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1389.1%\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eAn easy win, if not a huge improvement.\u003c/p\u003e\n\u003cp\u003eWhat we really need to do is start tiling the matrix – similar to how the AMX solution worked – so we\u0026rsquo;re accessing memory in a more-local way (rather than traversing the entire long rows on each inner loop).\u003c/p\u003e\n\u003cp\u003eThere are people who love writing tight kernels and tile packing and scattering, but it\u0026rsquo;s not really my strength. Here\u0026rsquo;s my best effort at a Metal tiling kernel:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ekernel\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e \u003cspan class=\"nf\"\u003ematmul_forward_kernel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003edevice\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e \u003cspan class=\"p\"\u003e[[\u003c/span\u003e\u003cspan class=\"n\"\u003ebuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)]],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"n\"\u003edevice\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e \u003cspan class=\"na\"\u003e[[buffer(1)]]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"n\"\u003edevice\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e \u003cspan class=\"na\"\u003e[[buffer(2)]]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003econstant\u003c/span\u003e \u003cspan class=\"n\"\u003euint\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e \u003cspan class=\"n\"\u003eBT\u003c/span\u003e \u003cspan class=\"na\"\u003e[[buffer(3)]]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003econstant\u003c/span\u003e \u003cspan class=\"n\"\u003euint\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"na\"\u003e[[buffer(4)]]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003econstant\u003c/span\u003e \u003cspan class=\"n\"\u003euint\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"na\"\u003e[[buffer(5)]]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003euint2\u003c/span\u003e \u003cspan class=\"n\"\u003egid\u003c/span\u003e \u003cspan class=\"na\"\u003e[[thread_position_in_grid]]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003euint2\u003c/span\u003e \u003cspan class=\"n\"\u003elid\u003c/span\u003e \u003cspan class=\"na\"\u003e[[thread_position_in_threadgroup]]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ethreadgroup\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e \u003cspan class=\"n\"\u003einpTile\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eMATMUL_TILE\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003eMATMUL_TILE\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ethreadgroup\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e \u003cspan class=\"n\"\u003eweightTile\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eMATMUL_TILE\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003eMATMUL_TILE\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003euint\u003c/span\u003e \u003cspan class=\"n\"\u003eoc\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003egid\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003euint\u003c/span\u003e \u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003egid\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kt\"\u003ebool\u003c/span\u003e \u003cspan class=\"n\"\u003einBounds\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eBT\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"n\"\u003eoc\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kt\"\u003efloat\u003c/span\u003e \u003cspan class=\"n\"\u003esum\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.0f\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euint\u003c/span\u003e \u003cspan class=\"n\"\u003ekBase\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003ekBase\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003ekBase\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"n\"\u003eMATMUL_TILE\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003euint\u003c/span\u003e \u003cspan class=\"n\"\u003einpK\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ekBase\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003elid\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003euint\u003c/span\u003e \u003cspan class=\"n\"\u003eweightK\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ekBase\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003elid\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003einpTile\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003elid\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003elid\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eBT\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"n\"\u003einpK\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e?\u003c/span\u003e \u003cspan class=\"n\"\u003einp\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003einpK\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.0f\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eweightTile\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003elid\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003elid\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eoc\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"n\"\u003eweightK\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e?\u003c/span\u003e \u003cspan class=\"n\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eoc\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eweightK\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.0f\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ethreadgroup_barrier\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emem_flags\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"n\"\u003emem_threadgroup\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euint\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eMATMUL_TILE\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003esum\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"n\"\u003einpTile\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003elid\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eweightTile\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003elid\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ethreadgroup_barrier\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emem_flags\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"n\"\u003emem_threadgroup\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einBounds\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ebt\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eOC\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eoc\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esum\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAs we\u0026rsquo;ve seen in previous attempts to optimize: this is much harder to read but it does provide another incremental improvement.\u003c/p\u003e\n\u003cp\u003eSince this is as far as I\u0026rsquo;m going to go with it, let\u0026rsquo;s take a look at the complete table:\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: right\"\u003eModel\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTokens/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining iterations/s\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eTraining versus llm.c\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003ellm.c\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.926\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.175\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e100%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003ellm.c (omp)\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003en/a\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.776\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e446%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eBasic Swift\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.054\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.014\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e7.3%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eBasic Swift + mutableSpan\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.056\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.042\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e24.0%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eBasic Swift + mutableSpan + Relaxed\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.533\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.148\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e85.1%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eFast Swift\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.918\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e0.189\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e106.6%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eMultithreaded Swift\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e4.356\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1.014\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e558.5%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eAMX\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e5.884\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1.678\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e958.8%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eBasic Metal\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e4.29\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1.841\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1052.0%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eThreaded Metal\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e6.211\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e2.431\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1389.1%\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: right\"\u003eTiled Metal\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e11.123\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e3.371\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e1926.3%\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eThis is around 650 Gflop/s \u003cem\u003e[Edit: yeah, sorry, this is a correction down from the 1 Tflop/s in the title]\u003c/em\u003e but even if I generously round back up to the nearest 1 significant figure, is 1 Tflop/s good? Theoretically, the GPU on my M3 Max is capable of around 15 Tflop/s. The real ceiling for this kind of task is going to be 3-5 Tflop/s (for a number of different reasons) – definitely more than the performance I\u0026rsquo;m getting out of my handwritten Metal matrix code.\u003c/p\u003e\n\u003cp\u003eThere\u0026rsquo;s an interesting question here: I have a high-end GPU. On a base M-series chip, would the performance ratio between AMX unit and GPU change?\u003c/p\u003e\n\u003ch2 id=\"test-harness\"\u003eTest harness\u003c/h2\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/cwlllmswift.png\"\n    alt=\"CwlLlmSwift showing comparison between all engines on the TinyShakespeare training set\" width=\"800px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eCwlLlmSwift showing comparison between all engines on the TinyShakespeare training set\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eYou can download the code and test harness app from \u003ca href=\"https://github.com/mattgallagher/CwlLlmSwift\"\u003eCwlLlmSwift\u003c/a\u003e. You will also need to download the \u003ca href=\"https://huggingface.co/datasets/karpathy/llmc-starter-pack\"\u003ellmc-starter-pack\u003c/a\u003e used by Andrej Karpathy\u0026rsquo;s llm.c. \u003cstrong\u003eNOTE that cloning llmc-starter-pack correctly requires \u003ccode\u003egit-xet\u003c/code\u003e installed\u003c/strong\u003e:\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003ebrew install git-xet\ngit xet install\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eWithout git set, the files in the starter pack won\u0026rsquo;t download correctly and selecting them in the app will give errors about the training file not being UInt16 weights.\u003c/p\u003e\n\u003cdiv class=\"aside\"\u003e\u003cstrong\u003eWARNING:\u003c/strong\u003e I\u0026rsquo;ve already recommended to always run the app in \u0026ldquo;Release\u0026rdquo; configuration but even in \u0026ldquo;Release\u0026rdquo;, the 20 iteration training comparison for \u0026ldquo;Basic Swift\u0026rdquo; takes around 30 minutes to run (you can see the \u0026ldquo;Total\u0026rdquo; times in that screenshot, above). Most of the time you\u0026rsquo;ll want to disable the \u0026ldquo;Basic Swift\u0026rdquo; engine and just view the others.\u003c/div\u003e\n\u003cp\u003eWhen you start the app for the first time, it\u0026rsquo;ll show an open dialog but you\u0026rsquo;ll want to hit the \u0026ldquo;New document\u0026rdquo; button. This will show a blank document that will require you to select the following documents from the llmc-starter-pack:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003egpt2_124M.bin (checkpoint)\u003c/li\u003e\n\u003cli\u003etiny_shakespeare_train.bin (training data)\u003c/li\u003e\n\u003cli\u003etiny_shakespeare_val.bin (validation data)\u003c/li\u003e\n\u003cli\u003egpt2_tokenizer.bin (tokenizer)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThen hit the \u0026ldquo;Create\u0026rdquo; button to create the dataset. Once the dataset is created, you can run training, inference or comparisons and save the dataset as a document.\u003c/p\u003e\n\u003cp\u003eTo be clear: the actual GPT-2 style LLM is pretty mediocre (it is only a 124-million-parameter model, where most LLMs start in the billions of parameters). Here\u0026rsquo;s an example of the model at the gpt2_124M.bin checkpoint (before running any post-training):\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003ePrompt:\u003c/strong\u003e The quick brown fox \u003cbr\u003e\n\u003cstrong\u003eGeneration:\u003c/strong\u003e would register the decision to slowdown his steps as he held out his right paw to hold up place. As Cas mornarye yelped as the fox clutched at his paw his temper ground up. \u0026ldquo;You sure you\u0026rsquo;re not scared of birds?\u0026rdquo;\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eAnd after 500 iterations of post-training on Shakespeare:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003ePrompt:\u003c/strong\u003e The quick brown fox\u003cbr\u003e\n\u003cstrong\u003eGeneration:\u003c/strong\u003e  charged,\u003cbr\u003e\nBarking as he did,\u003cbr\u003e\nTo fight withanna in his bosom,\u003cbr\u003e\nOn one occasion dying:\u003cbr\u003e\nNo fault is his life, for there blood being out. \u0026rsquo;tis I;\u003cbr\u003e\nI did mean death, though it be by the water.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eBoth examples look okay from a distance but are somewhat incoherent in the details. Not exactly AGI.\u003c/p\u003e\n\u003cp\u003eFor comparison, here\u0026rsquo;s what I get out of \u003ccode\u003eLanguageModelSession\u003c/code\u003e, Apple\u0026rsquo;s Foundation model built into iOS/macOS 26, where the only \u0026ldquo;training\u0026rdquo; needed is the prompt you give it:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003ePrompt:\u003c/strong\u003e Turn the prompt \u0026ldquo;The quick brown fox\u0026rdquo; into a few lines of verse in Shakespearean language and meter.\u003cbr\u003e\n\u003cstrong\u003eGeneration:\u003c/strong\u003e Upon the heath, a swift fox doth roam,\u003cbr\u003e\nHis coat of brown, like autumn\u0026rsquo;s golden hue,\u003cbr\u003e\nWith nimble steps, he darts through the foam,\u003cbr\u003e\nA creature swift, both bold and true.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eI don\u0026rsquo;t think this model has really nailed iambic pentameter but it\u0026rsquo;s at least coherent. If you need a quick and readily available LLM on macOS, use Apple\u0026rsquo;s Foundation model. But the point of this series isn\u0026rsquo;t to show how to create the best model, it\u0026rsquo;s merely to explore how to run a model and perform post-training.\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusion\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThis article was about looking at how to optimize code and gaining appreciation for the work that needs to go into optimizing the matrix multiplications in machine learning libraries. \u003cstrong\u003eNone of this code should be considered a good choice for production\u003c/strong\u003e. In the next article, I\u0026rsquo;ll look at BLAS, BNNS, CoreML, MPSGraph and other high performance libraries built into macOS. These libraries are all either more efficient or more performant than the options I\u0026rsquo;ve shown here.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eWe took a basic Swift implementation of matrix multiplication and made it 232 times faster, taking it from 2.8 Gflop/s to 650 Gflop/s. In the process, we\u0026rsquo;ve seen the scaling capability from unoptimized code, slightly improved code, to SIMD optimized code, to multithreaded code, to running on special units, to GPU compute shaders.\u003c/p\u003e\n\u003cp\u003eThe first 72 times increase occurred due to the basic Swift optimizations you should consider for any algorithm:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eAvoid \u003ccode\u003eArray\u003c/code\u003e copy-on-write/reference-check overhead (Swift should have applied this automatically; it is maybe just a bug)\u003c/li\u003e\n\u003cli\u003eUse some SIMD optimized fused-multiply-add instructions in Swift (by \u0026ldquo;relaxing\u0026rdquo; constraints using the Numerics library)\u003c/li\u003e\n\u003cli\u003eRestructured loops so the traversal is more efficient and SIMD pipelining works better\u003c/li\u003e\n\u003cli\u003eParallelize using \u003ccode\u003eDispatchQueue.concurrentPerform\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eAs always, high performance Swift has plenty of curve-balls to throw but it\u0026rsquo;s always possible to get Swift as fast as C. This time, Swift ended up faster due to Swift choosing slightly better SIMD instructions (although I\u0026rsquo;m sure there\u0026rsquo;s a way to get C to use the same SIMD versions of FMA for the same performance).\u003c/p\u003e\n\u003cp\u003eThe question is never whether Swift can be fast, but rather: will Swift look better when you get there? I think Swift held up well until the multi-threaded implementation. Using \u003ccode\u003ewithUnsafeMutableBuffer\u003c/code\u003e ruins the aesthetics of any function. I wish Swift had functions for performing mutations over subranges of an array to do this elegantly, safely and with good performance.\u003c/p\u003e\n\u003cp\u003eMoving beyond typical Swift optimization, using AMX instructions got us another 70% faster using operations on Apple Silicon designed for larger matrix operations. The final boost came from moving to the graphics card. In both these cases, you\u0026rsquo;d get much better results from a developer skilled in tiled matrix operations. My efforts were \u0026ldquo;okay\u0026rdquo; but I know that performance could be 30% better if I really knew what I was doing.\u003c/p\u003e\n\u003cp\u003eBut 382 times faster still isn\u0026rsquo;t fast enough. Even this example gave us \u003cstrong\u003eless than 12 tokens per second\u003c/strong\u003e – a speed which might be barely passable in some cases but for a model this small is laughably slow.\u003c/p\u003e\n\u003cp\u003eClearly, we\u0026rsquo;ve got further to go.\u003c/p\u003e\n\u003cp\u003eApple have lots of other frameworks for training neural networks and the final implementation in this series will knock out more than 20 training iterations before most of the implementations in this article have spat out their first.\u003c/p\u003e\n",
            "date_published": "2026-04-18T11:00:00+10:00"
        }, {
            "title": "Have LLMs improved for Swift coding in the last 12 months?",
            "id": "https://www.cocoawithlove.com/blog/llms-twelve-months-later.html",
            "url": "https://www.cocoawithlove.com/blog/llms-twelve-months-later.html",
            "content_html": "\u003cp\u003eIn the \u003ca href=\"copilot-raindrop-generator.html\"\u003eprevious article\u003c/a\u003e, I struggled through getting GitHub Copilot (at the time based on GPT 4o) to generate a small Swift/SwiftUI/AVFoundation application. The app was about 400 lines long but Copilot struggled to handle more than a hundred lines or so at a time. Essentially every line in the app required manual rewriting.\u003c/p\u003e\n\u003cp\u003eTwelve months later, I wanted to check back in to see if it is finally possible to have fun, \u003ca href=\"https://en.wikipedia.org/wiki/Vibe_coding\"\u003evibe coding\u003c/a\u003e as Andrej Karpathy coined it, in Swift? More specifically, can I, as an experienced Swift programmer, feel like an LLM is not just helping but doing \u003cem\u003emost\u003c/em\u003e of the work in a trivial (under 500 line) app?\u003c/p\u003e\n\u003cp\u003eOr does LLM output continue to make a mess and leave me feeling like I need to throw everything out and rewrite myself?\u003c/p\u003e\n\u003c!-- TOC --\u003e\n\u003ch2 id=\"in-the-last-12-months\"\u003eIn the last 12 months\u003c/h2\u003e\n\u003ch3 id=\"did-i-use-llms-after-my-last-article\"\u003eDid I use LLMs after my last article?\u003c/h3\u003e\n\u003cp\u003eLast year, I wrote about GPT 4o via GitHub Copilot in Visual Studio Code. Outside the article, I had also tested Claude Sonnet 3.5 in Cursor.\u003c/p\u003e\n\u003cp\u003eTo be blunt: after testing them out, I have \u003cem\u003enot\u003c/em\u003e used LLMs for programming for the rest of the year. Attempting to use an LLM in that way was simply too frustrating. I don\u0026rsquo;t enjoy cleaning up flawed approaches and changing every single line. I do regularly ask ChatGPT how to use specific APIs, but I\u0026rsquo;m really just using it as a better documentation search or asking for sample code that is missing from Apple\u0026rsquo;s documentation. I\u0026rsquo;m not directly using any of the code ChatGPT writes in any of my apps.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eUpdate:\u003c/strong\u003e Two weeks after writing this article, my team at work changed our approach and now we\u0026rsquo;re involving LLMs different aspects of our workflow and using them for about half our programming tasks. Oh well, some comments age like milk.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eIn the meantime, I have watched plenty of presentations about letting Claude Code, and other tools, completely build an \u0026ldquo;app\u0026rdquo; but the successful presentations have usually focussed on JavaScript web apps or Python wrappers around small command-line tools. The two times this year that I\u0026rsquo;ve watched developers try the same with Swift apps has been a somewhat random affair – sometimes reaching a conclusion and sometimes getting stuck on non-working solutions and excuses claiming it does sometimes work if left to run for another 20 minutes.\u003c/p\u003e\n\u003ch3 id=\"versions-go-up\"\u003eVersions go up\u003c/h3\u003e\n\u003cp\u003eThis time around, I want to test a little more broadly. Looking at the biggest three non-local (frontier) coding models, they are now:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eGPT 5.2 (up from 4.0)\u003c/li\u003e\n\u003cli\u003eGemini 3 (up from 1.5)\u003c/li\u003e\n\u003cli\u003eClaude 4.5 (up from 3.5)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eA number of local (edge) LLMs have also released this year offering supposed coding capabilities in less than 16GB RAM (so you can still have Xcode open on a 32GB system), including:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eQwen3-Coder-30B\u003c/li\u003e\n\u003cli\u003eGPT-OSS-20B\u003c/li\u003e\n\u003cli\u003eI also briefly tested: DeepSeek-R2-Lite, Devstral Small 2, Nemotron 3 Nano\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eI\u0026rsquo;m going to throw a Swift coding challenge at the free chat versions of all these – plus the same GPT 4o model as last year – and see where things stand. Feel free to claim that paid versions or agentic models would be better but that\u0026rsquo;s not what I\u0026rsquo;m testing.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eI wish Apple\u0026rsquo;s Swift Assist was one of the models on this list but that didn\u0026rsquo;t happen. Performance was rumored to be poor but it would have been interesting to see if Apple could keep a model trained on up-to-date Swift. Most LLMs are trained on data that is 2+ years old and their Swift style often feels older still. In writing this article, I encountered LLMs adding \u003ccode\u003e#availability\u003c/code\u003e checks for iOS 13, zero knowledge of async/await, no use of iOS/macOS features from the last 3 years and no idea what Swift 6 is.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"attempting-to-prompt-the-entire-app\"\u003eAttempting to prompt the entire app\u003c/h2\u003e\n\u003cp\u003eLast time, I used pretty simple prompts and incrementally built the app piece by piece. It was frustrating and worked poorly. I\u0026rsquo;m just here for the vibes and gibes so I\u0026rsquo;m not going as slow this time.\u003c/p\u003e\n\u003cp\u003eThis time, let\u0026rsquo;s go with a significant prompt since I\u0026rsquo;m looking purely for models that can one-shot the whole thing:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eCreate a macOS app written in Swift, using SwiftUI and AVFoundation, that synthesizes rain sounds.\u003c/p\u003e\n\u003cp\u003eThe audio engine should manage a number of raindrops. The raindrops should be created with some minor randomization of timing but the average rate should be controllable using a slider in the user-interface so that average rates between 20 per minute and 4000 per minute are possible.\u003c/p\u003e\n\u003cp\u003eEach raindrop should be synthesised using AVAudioSourceNode and the generation should involve a separate \u0026ldquo;impact\u0026rdquo; sound, followed by a small delay, followed by an exponentially decaying \u0026ldquo;bubble\u0026rdquo; oscillation. The user interface should include sliders to adjust the impact amplitude, the delay duration, the bubble amplitude, the bubble frequency and the bubble decay rate. As with the timing, each of these parameters should have minor randomisation so the drops do not all sound mechanically identical. As the sliders are adjusted, a SwiftUI Chart should show a sample waveform for the raindrop based on the current settings.\u003c/p\u003e\n\u003cp\u003eTo fill out the overall \u0026ldquo;rain\u0026rdquo; soundscape, the audio engine should also implement pink, brown and white noise generators whose amplitude can also be controlled via sliders in the user interface.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eThat\u0026rsquo;s pretty close to a description of the entire app from the previous article.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m returning to this app because I like the mix of overall technologies involved. There\u0026rsquo;s some basic SwiftUI, some really annoying to configure AVAudioEngine stuff, some \u003ccode\u003eFloat\u003c/code\u003e/\u003ccode\u003eDouble\u003c/code\u003e conversions which are always tricky for LLMs primarily trained on less-type-safe languages. Finally, there\u0026rsquo;s a need to coordinate concepts from audio rendering through to onscreen display with Swift Charts.\u003c/p\u003e\n\u003cp\u003eThere\u0026rsquo;s nothing in this prompt that would require Model abstraction, persistence, state restoration or networking – but in a vibe-coded app, maybe that\u0026rsquo;s for the best.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m going to be generous to all the models and allow 15 minutes cleanup after generation, to see if I can get failing efforts running. Yeah, I know in advance that there\u0026rsquo;ll be issues. I\u0026rsquo;ll give a score out of ten, mostly by deducting points based on how many compile and runtime issues the code has and whether there are any missing features.\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s see how it goes.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eClarification:\u003c/strong\u003e I didn\u0026rsquo;t make it clear in the original text of this article but I will not be prompting inside an agent for these tasks. This is an effort to see the raw one-shot ability of different models so this will be conducted in single API calls.\u003c/br\u003eI am looking for the raw strength of the models for Swift programming, \u003cem\u003eNOT\u003c/em\u003e the capability of an agent to complete the task even when the model is clumsy and mistake-prone. Obviously, in an agentic harness with tool calling and more, these models are capable of much more.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"gpt-4o\"\u003eGPT 4o\u003c/h2\u003e\n\u003cp\u003eSince this prompt is a different approach than last year, I wanted to starting by running the prompt against the same model I tried last year\u003c/p\u003e\n\u003cp\u003eAnd the result is terrible.\u003c/p\u003e\n\u003cp\u003eXcode is showing 21 errors over the 383 lines and 5 files generated (AudioEngine, ContentView, NoiseGenerator, Raindrop and WaveformChartView).\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eA new default Xcode setting for Mac app projects (SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY=YES) now makes it mandatory to \u003ccode\u003eimport Combine\u003c/code\u003e before you can use \u003ccode\u003eObservableObject\u003c/code\u003e. Since this is a new change and \u003cem\u003eall\u003c/em\u003e the solutions generated make use of \u003ccode\u003eObservableObject\u003c/code\u003e, this means that most models (all except one) had a compilation error on this point. It feels like it might be a Swift/Xcode/AppKit issue but even if it\u0026rsquo;s not, LLMs are slow to pick up on changes like this.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eI can easily fix simple errors like unhandled conversion issues (\u003ccode\u003eInt32\u003c/code\u003e to \u003ccode\u003eInt\u003c/code\u003e).\u003c/p\u003e\n\u003cp\u003eThe bigger problems start with the noise generator which contains duplicated properties for each of white, brown and pink shoved into a single type for no reason. You still need to create 3 instance for each type of noise so having all the properties in one is just wasted effort. But there\u0026rsquo;s no way to instantiate each type of noise because the \u003ccode\u003etype\u003c/code\u003e property is missing (even though the \u003ccode\u003eAudioEngine\u003c/code\u003e tries to call \u003ccode\u003eNoiseGenerator(type:)\u003c/code\u003e – that function that doesn\u0026rsquo;t exist). It\u0026rsquo;s a complete mess.\u003c/p\u003e\n\u003cp\u003eThe \u003ccode\u003eWaveformChartView\u003c/code\u003e was constructed with 5 parameters (\u003ccode\u003eimpactAmplitude\u003c/code\u003e, \u003ccode\u003edelayDuration\u003c/code\u003e, \u003ccode\u003ebubbleAmplitude\u003c/code\u003e, \u003ccode\u003ebubbleFrequency\u003c/code\u003e and \u003ccode\u003ebubbleDecayRate\u003c/code\u003e), none of which exist (the actual \u003ccode\u003eWaveformChartView\u003c/code\u003e is expecting an array of raw samples).\u003c/p\u003e\n\u003cp\u003eAdditionally, the \u003ccode\u003escheduleRaindrops\u003c/code\u003e, \u003ccode\u003erenderAudio\u003c/code\u003e functions were just a placeholder comment (you know, the critical functions of the app) and the \u003ccode\u003ecase .pink\u003c/code\u003e and \u003ccode\u003ecase .brown\u003c/code\u003e branches of the \u003ccode\u003ecreateNoiseBuffer\u003c/code\u003e function were also empty comments. As was the \u003ccode\u003eonAppear\u003c/code\u003e function in the \u003ccode\u003eContentView\u003c/code\u003e which read simply \u0026ldquo;Initialize audio engine and start generating sounds\u0026rdquo;.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m way past my 15 minutes cleanup time just trying to analyze the mess that\u0026rsquo;s been made.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eScore: 2/10\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eThere\u0026rsquo;s some scaffolding there and maybe you could use it as a starting point. Maybe. But it\u0026rsquo;s missing most of the functionality and there could be hours of cleanup, further work or prompting to get it working and honestly, I might prefer to start over. It\u0026rsquo;s possibly worse than last year.\u003c/p\u003e\n\u003ch2 id=\"gpt-52\"\u003eGPT 5.2\u003c/h2\u003e\n\u003cp\u003eGPT generates a quick hierarchical overview which I like.\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eRainApp\n ├─ ContentView (SwiftUI UI + Charts)\n ├─ RainParameters (ObservableObject)\n ├─ RainAudioEngine\n │   ├─ AVAudioEngine\n │   ├─ AVAudioSourceNode (rain drops)\n │   ├─ AVAudioSourceNode (noise)\n │   └─ RaindropScheduler\n └─ DSP\n     ├─ RaindropVoice\n     ├─ NoiseGenerators\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eBut despite this hierarchy, it generated all 295 lines in a single file. Thanks.\u003c/p\u003e\n\u003cp\u003eThe code did not compile as-is.  There\u0026rsquo;s some kind of confusion between \u003ccode\u003eUnsafeMutableAudioBufferListPointer.Element\u003c/code\u003e (which doesn\u0026rsquo;t have a \u003ccode\u003efloatChannelData\u003c/code\u003e property) and \u003ccode\u003eAVAudioPCMBuffer\u003c/code\u003e (which does):\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003efor buffer in abl {\n    let ptr = buffer.floatChannelData![0]\n    ptr[frame] = Float(sample)\n}\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eSo I ended up replacing this with the following code (I\u0026rsquo;m sure there\u0026rsquo;s a smarter way to do this):\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003efor buffer in abl {\n    buffer.mData?.bindMemory(to: Float.self, capacity: Int(frameCount))[frame] = Float(sample)\n}\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eWith that change (and an \u003ccode\u003eimport Combine\u003c/code\u003e) everything else seemed to work.\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/gpt-5-raindrop.png\"\n    alt=\"Raindrop Synthesizer from GPT 5.2\" width=\"500px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eRaindrop Synthesizer from GPT 5.2\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003e\u003cstrong\u003eScore: 7/10\u003c/strong\u003e Contained a nuisance of a compile bug. The UI\u0026rsquo;s missing most labels and it\u0026rsquo;s updating the chart on every frame, making things super sluggish. But if you get past all that, I guess it works.\u003c/p\u003e\n\u003ch2 id=\"gemini-3\"\u003eGemini 3\u003c/h2\u003e\n\u003cp\u003eGemini isn\u0026rsquo;t very chatty. After seeming to complain that \u0026ldquo;this is a sophisticated request\u0026rdquo;, it tersely spat out 227 lines over 3 files (RainEngine, ContentView and RaindropSynthesizerApp).\u003c/p\u003e\n\u003cp\u003eThe code did not compile as-is. Obviously it\u0026rsquo;ll need the standard \u003ccode\u003eimport Combine\u003c/code\u003e line but the following line used for the pink noise generator isn\u0026rsquo;t valid Swift:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003eb0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eb1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eb2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eb3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eb4\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eb5\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eb6\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis corrects with a single multi-selection code edit and the result is:\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/gemini-3-raindrop.png\"\n    alt=\"Raindrop Synthesizer from Gemini 3\" width=\"500px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eRaindrop Synthesizer from Gemini 3\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eThis was by far the shortest solution to the prompt and it\u0026rsquo;s one of the best. Compared to GPT-5.2, its coding bug was more straightforward, it has labelled everything and runs fast. I don\u0026rsquo;t think it needed a min-height of 700 but otherwise, this is pretty good.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eScore: 8/10\u003c/strong\u003e Made one trivial coding error but it\u0026rsquo;s otherwise well laid out and works well.\u003c/p\u003e\n\u003ch2 id=\"claude-sonnet-45\"\u003eClaude Sonnet 4.5\u003c/h2\u003e\n\u003cp\u003eClaude\u0026rsquo;s web UI puts summary information in the left column while it generates the code in the right. That means you can read the summary while the code is generated. A nice affordance – although the summary information is useless.\u003c/p\u003e\n\u003cp\u003eThe generated code is a little more verbose than the other implementations (445 lines in a single file). But it has a SwiftUI Preview and all the UI is commented and neatly divided into sections.\u003c/p\u003e\n\u003cp\u003eThe only compilation issue was the failure to \u003ccode\u003eimport Combine\u003c/code\u003e.\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/claude-4-raindrop.png\"\n    alt=\"Raindrop Synthesizer from Claude Sonnet 4.5\" width=\"300px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eRaindrop Synthesizer from Claude Sonnet 4.5\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eThis is the first implementation to offer a \u0026ldquo;Start/Stop\u0026rdquo; button. Technically, I didn\u0026rsquo;t request a start/stop button but it\u0026rsquo;s a nice affordance (and managing audio cancellation is one of the trickier task).\u003c/p\u003e\n\u003cp\u003eBut the button affects only the drops, not the noise generators, so you still can\u0026rsquo;t turn the app \u0026ldquo;off\u0026rdquo; without quitting. The only other negative comment I have is that the UI is gigantic (it didn\u0026rsquo;t fit on my 4k monitor\u0026rsquo;s default 1920x1080x2 resolution).\u003c/p\u003e\n\u003cp\u003eThe code contained abstracted elements (each of those sections and sliders involved reusable functions). Overall, a fairly tidy implementation, if a little verbose compared to the Gemini result.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eScore: 9/10\u003c/strong\u003e Has its quirks but this is easily the best implementation.\u003c/p\u003e\n\u003ch2 id=\"local-llms\"\u003eLocal LLMs\u003c/h2\u003e\n\u003ch3 id=\"deepseek-r2-lite-devstral-small-2-nemotron-3-nano\"\u003eDeepSeek-R2-Lite, Devstral Small 2, Nemotron 3 Nano\u003c/h3\u003e\n\u003cp\u003eI\u0026rsquo;ve lumped these all into one category. The reality is that most local LLMs tend to be so bad at Swift coding that I wonder if the model is having technical issues. Nemotron 3 would apparently omit tokens (e.g. writing \u003ccode\u003edid\u003c/code\u003e instead of \u003ccode\u003edidSet\u003c/code\u003e and omitting some \u003ccode\u003e:\u003c/code\u003e characters) so it\u0026rsquo;s entirely possible there was something wrong in the execution.\u003c/p\u003e\n\u003cp\u003eRegardless, none of these models produced anything close to a working program. None of them correctly understood what was meant by Swift Charts or correctly used AVAudioSourceNode for live audio rendering (attempting instead to pre-generate buffers). They would call audio rendering functions that don\u0026rsquo;t exist and try to construct audio node types that don\u0026rsquo;t exist. DeepSeek even tried to \u003ccode\u003eusleep\u003c/code\u003e during its audio render function to \u0026ldquo;delay\u0026rdquo; the audio – which indicates that it\u0026rsquo;s really not understanding what\u0026rsquo;s going on.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eScore: 1/10\u003c/strong\u003e Unfortunately, many local LLMs tend to fail more than succeed. Not even usable as a starting point.\u003c/p\u003e\n\u003ch3 id=\"qwen3-coder-30b-4bit\"\u003eQwen3-Coder 30B 4bit\u003c/h3\u003e\n\u003cp\u003eThis generated 535 lines in a single file but there were 50 errors immediately in Xcode.\u003c/p\u003e\n\u003cp\u003eMost of the issues were \u003ccode\u003eFloat\u003c/code\u003e to \u003ccode\u003eDouble\u003c/code\u003e conversion issues but there were also UI issues (too many steps in a slider causes macOS to lock up), audio issues (no mixer resulted in channel mismatch errors thrown when trying to use the audio engine) and ultimately no audio was produced. This implementation also didn\u0026rsquo;t seem to understand what a SwiftUI Chart is and never hooked up the noise generators (even though it implemented them). The code also tried to use iOS AVAudioSession in a macOS app.\u003c/p\u003e\n\u003cp\u003eHowever, after cleaning up the worst of the issues, at least it produced a usable UI.\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/qwen-3-raindrop.png\"\n    alt=\"Raindrop Synthesizer from Qwen 3 Coder\" width=\"500px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eRaindrop Synthesizer from Qwen 3 Coder\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003e\u003cstrong\u003eScore: 4/10\u003c/strong\u003e It didn\u0026rsquo;t leave any blanks like GPT-4o but there\u0026rsquo;s still a lot of cleanup needed before this would work.\u003c/p\u003e\n\u003ch3 id=\"gpt-oss-20b-mxfp4\"\u003eGPT-OSS-20B mxfp4\u003c/h3\u003e\n\u003cp\u003eThis generated 348 lines in a single file. There were lots of \u003ccode\u003eFloat\u003c/code\u003e/\u003ccode\u003eDouble\u003c/code\u003e conversion issues but other than that, this compiled.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThis was the \u003cem\u003eonly\u003c/em\u003e output that included \u003ccode\u003eimport Combine\u003c/code\u003e. Yes, it had plenty of other compile issues but that does make it better than the \u003cem\u003efull\u003c/em\u003e version of GPT on this one point.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eAfter addressing the compiler issues, the code corrupted the stack and crashed on launch. Not great.\u003c/p\u003e\n\u003cp\u003eAfter some puzzled staring at the uncorrupted portions of the stack, I realized that this code:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 2. Add raindrop samples if any are scheduled for this block\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003enow\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eCACurrentMediaTime\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003ewhile\u003c/span\u003e \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003edrop\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003escheduler\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enextDrop\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebefore\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003enow\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"nb\"\u003eDouble\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eframeCount\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"mf\"\u003e44100.0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kc\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eapply\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edrop\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003edrop\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eto\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esamples\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eatSampleIndex\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"n\"\u003edrop\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etime\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mf\"\u003e44100.0\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWas generating negative sample indexes because the start of the \u003ccode\u003enextDrop\u003c/code\u003e is always \u003ccode\u003ebefore\u003c/code\u003e \u003ccode\u003enow\u003c/code\u003e. Rather than fix the drop time logic (or remove the needless use of \u003ccode\u003eCACurrentMediaTime\u003c/code\u003e) I just guarded against negative indices (so the first few samples from a drop will always be lost) and re-ran.\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/gpt-oss-raindrop.png\"\n    alt=\"Raindrop Synthesizer from GPT OSS 20B\" width=\"500px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eRaindrop Synthesizer from GPT OSS 20B\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eThe noise is being applied to the raindrop waveform in the chart and the scale doesn\u0026rsquo;t show the entire waveform. Also, there audio is coming only from the left speaker.\u003c/p\u003e\n\u003cp\u003eBut I\u0026rsquo;m going to give that a passing grade. That\u0026rsquo;s a working solution after fixing a few type conversion issues and adding one guard.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eScore: 6/10\u003c/strong\u003e Not bad for a local LLM.\u003c/p\u003e\n\u003ch2 id=\"discussion\"\u003eDiscussion\u003c/h2\u003e\n\u003cp\u003eI\u0026rsquo;ve been pretty generous here with the LLMs – allowing 15 minutes to clean up the non-working result that each produced. If a human coder created a PR on one of my repos, submitting one of the best solutions from this lot, I\u0026rsquo;d still hit them with a pretty long list of cleanup requests.\u003c/p\u003e\n\u003cp\u003eEven Claude, which I\u0026rsquo;ve named the winner, produced code that used:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003ccode\u003eObservableObject\u003c/code\u003e (would be better replaced by \u003ccode\u003eObservable\u003c/code\u003e)\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eTimer.scheduledTimer\u003c/code\u003e and \u003ccode\u003eDispatchQueue.main.asyncAfter\u003c/code\u003e (would be better replaced by \u003ccode\u003eTask\u003c/code\u003e containing a \u003ccode\u003eTask.sleep\u003c/code\u003e)\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eRainAudioEngine\u003c/code\u003e requires \u003ccode\u003eMainActor\u003c/code\u003e isolation (audio code should be nonisolated)\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eNot to mention the oversized UI and the start/stop button that doesn\u0026rsquo;t stop the noise generators.\u003c/p\u003e\n\u003cp\u003eAlso omitted is that all these LLMs feel completely random. Change a couple words and they\u0026rsquo;ll give completely different results – often going from working to not-working and back again.\u003c/p\u003e\n\u003cp\u003eIf you don\u0026rsquo;t like unpredictability, it\u0026rsquo;s still not a comfortable approach to programming.\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusion\u003c/h2\u003e\n\u003cp\u003eLast year, in response to the question \u0026ldquo;Was Copilot worth it\u0026rdquo;, my answer was: \u0026ldquo;Sometimes, yes. But not always\u0026rdquo;. As I mentioned, I ended up \u003cem\u003enot\u003c/em\u003e using LLMs to write code for the rest of the year, so my final subjective feeling was that they really \u003cem\u003eweren\u0026rsquo;t\u003c/em\u003e worth the effort.\u003c/p\u003e\n\u003cp\u003eThis year the answer is more positive for LLMs – at least for the best frontier models. You can now code a 500 line app with one prompt and as little as 1 or 2 line fix-ups, making things not feel like an unending struggle. So that\u0026rsquo;s progress. They\u0026rsquo;re still making mistakes, emitting outdated code and running into issues caused by changed defaults in Xcode, so it\u0026rsquo;s far from a flawless experience. Obviously, when you\u0026rsquo;re in an agentic harness like Claude Code, Open AI Codex or OpenCode, the agentic can usually perform these touchups, often making LLM assisted programming in Swift effortless.\u003c/p\u003e\n\u003cp\u003eLocal LLMs are much worse. Most are completely useless and even the good ones are barely above the \u0026ldquo;debatably good enough\u0026rdquo; position that GPT 4o was in last year. I only have 48GB RAM on this machine, so maybe there are better performing local LLMs beyond what I can run.\u003c/p\u003e\n\u003cp\u003eDid I have fun vibe coding? Sometimes. Honestly, it\u0026rsquo;s still been a little exhausting trying out options to find things that work.\u003c/p\u003e\n\u003cp\u003eI haven\u0026rsquo;t included the code generated. It\u0026rsquo;d be interesting to see if others can repeat the same prompt with the same models. My own experience is that you can get quite different results – especially if you change a word or two. It\u0026rsquo;s all a little non-deterministic. Across all types of LLM, predictability and reliability is not there. For any prompt, you\u0026rsquo;re rolling dice to see if you get a good job that might help you move forward or a complete mess that would take longer to fix than to scrap and start over.\u003c/p\u003e\n\u003cp\u003eWill I actually end up using LLMs to write anything beyond sample-code this year? Still unclear.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eUpdate:\u003c/strong\u003e Yeah, okay, in retrospect I was far too pessimistic and I\u0026rsquo;m now using these tools most days. Sometimes you don\u0026rsquo;t see things until you dive in.\u003c/p\u003e\n\u003c/blockquote\u003e\n",
            "date_published": "2025-12-29T13:29:41+11:00"
        }, {
            "title": "Using Copilot to write a raindrop audio synthesizer using AVAudioEngine",
            "id": "https://www.cocoawithlove.com/blog/copilot-raindrop-generator.html",
            "url": "https://www.cocoawithlove.com/blog/copilot-raindrop-generator.html",
            "content_html": "\u003cp\u003eI\u0026rsquo;ve largely ignored the use of large language models (LLMs) as programming assistants, despite (or because of) the hype of the last 2 years. I\u0026rsquo;ve had a preconception that an LLM might not work well enough or might not meet my expectations of code quality.\u003c/p\u003e\n\u003cp\u003eSince Microsoft have recently made the lowest tier of \u003ca href=\"https://code.visualstudio.com/blogs/2024/12/18/free-github-copilot\"\u003eGitHub Copilot for VS Code free\u003c/a\u003e, I wanted to test that preconception and see how helpful LLM-assisted programming might be when implementing a small project in Swift. And maybe this will make an interesting point of comparison to \u003ca href=\"https://www.apple.com/newsroom/2024/06/apple-empowers-developers-and-fuels-innovation-with-new-tools-and-resources/\"\u003eApple\u0026rsquo;s Swift Assist\u003c/a\u003e (if and when that\u0026rsquo;s finally available).\u003c/p\u003e\n\u003cp\u003eAs for the project itself: I wanted to explore sound synthesis using \u003ccode\u003eAVAudioEngine\u003c/code\u003e after stumbling across \u003ca href=\"https://github.com/747745124/Raindrop-Generator\"\u003ea GitHub repository that implied a raindrop could be synthesized using a very simple waveform\u003c/a\u003e. It seems unlikely that a simple waveform would produce a convincing raindrop sound but the project should be well within the \u0026ldquo;few thousand line\u0026rdquo; context limit for an LLM so I think it\u0026rsquo;s an achievable goal.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eOn calling everything “AI”\u003c/strong\u003e: LLMs are commonly referred to as “AI” but the description makes me uncomfortable. I wrote my engineering thesis using neural networks more than 20 years ago and we never used the term “AI”. The terms used were always “neural networks” or “machine learning”. “AI” has always implied a more-general intelligence and I associate it with people deceptively implying that tools are smarter than they are or that general intelligence is just around the corner.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c!-- TOC --\u003e\n\u003ch2 id=\"the-setup\"\u003eThe setup\u003c/h2\u003e\n\u003cp\u003eI\u0026rsquo;m running Visual Studio Code 1.96.2 and using its built-in connection to Github Copilot (GPT 4o) that is currently free to use if you authorise via your GitHub account. In VS Code, I\u0026rsquo;ve installed the Swift v1.11.4 extension for Swift language server support and SweetPad 0.1.48 for building via Xcode and ensuring VS Code behaves better on Xcode Projects.\u003c/p\u003e\n\u003cp\u003eIf you haven\u0026rsquo;t used SweetPad before, you need to set it up for each project by typing \u003cem\u003eCommand-Shift-P\u003c/em\u003e in VS Code and choosing \u0026ldquo;SweetPad: Generate Build Server Config\u0026rdquo; from the popup that appears and then selecting the build target so it can create a buildServer.json file. Additionally, the Swift extension might ask to configure itself (global is usually fine).\u003c/p\u003e\n\u003cp\u003eIf you aren\u0026rsquo;t prompted when you launch VS Code, you may need to \u003ca href=\"https://code.visualstudio.com/docs/copilot/setup-simplified\"\u003eenable Copilot Free in VS Code to configure Copilot\u003c/a\u003e. When you want to ask Copilot something, press \u003cem\u003eCommand-Control-I\u003c/em\u003e and that will present a chat window where you can prompt Copilot to write new code or refactor existing code.\u003c/p\u003e\n\u003cp\u003eI haven\u0026rsquo;t really spent enough time configuring VS Code the way I like it so outside of interacting with Copilot, I\u0026rsquo;m likely to do most of my editing, building and running in Xcode itself.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eCheck it out\u003c/strong\u003e: you can \u003ca href=\"https://github.com/mattgallagher/RainGenerator\"\u003edownload the code for this article from the RainGenerator repository\u003c/a\u003e. The commit history includes most of the steps discussed in this article.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"alternative-setup\"\u003eAlternative setup\u003c/h3\u003e\n\u003cp\u003eNot to offer too much of a peek behind the curtain but I ran this same experiment nearly a month ago using \u003ca href=\"https://www.cursor.com\"\u003eCursor\u003c/a\u003e (a fork of Visual Studio Code designed to focus on these LLM-based workflows). Thomas Ricouard has written \u003ca href=\"https://dimillian.medium.com/how-to-use-cursor-for-ios-development-54b912c23941\"\u003ea pretty good article on using Cursor for iOS development\u003c/a\u003e, if you\u0026rsquo;d like to know more. I think the Claude 3.5 LLM that Cursor uses by default did produce slightly better code than Copilot but the difference between VS Code and Cursor or Copilot and Claude wasn\u0026rsquo;t huge (the broad strokes are identical between each). This article will focus on my Copilot experience since I didn\u0026rsquo;t really document my Cursor experience (although the first commit in the repository still has a date of December 1 from the Cursor timeline).\u003c/p\u003e\n\u003ch2 id=\"tone-generator\"\u003eTone Generator\u003c/h2\u003e\n\u003ch3 id=\"first-effort\"\u003eFirst effort\u003c/h3\u003e\n\u003cp\u003eI wanted to start the project simple so to begin, I thought I\u0026rsquo;d ask Copilot to do something very simple \u003ca href=\"/2010/10/ios-tone-generator-introduction-to.html\"\u003ethat I\u0026rsquo;ve written about before: a tone generator\u003c/a\u003e. That code involves a huge amount of boilerplate that shouldn\u0026rsquo;t be rerequired in \u003ccode\u003eAVAudioEngine\u003c/code\u003e (the latest iteration of the same audio processing graph concept).\u003c/p\u003e\n\u003cp\u003eWhile I\u0026rsquo;ve read about \u003ccode\u003eAVAudioEngine\u003c/code\u003e (introduced between macOS 10.10 and 10.15) I haven\u0026rsquo;t really had the chance to use it so maybe Copilot can show me how it\u0026rsquo;s done.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cb\u003ePrompt:\u003c/b\u003e Create a Tone Generator that plays a sine, square, or sawtooth waveform and offers sliders to control volume and frequency.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eThe only context I\u0026rsquo;ve provided to Copilot is a \u003ccode\u003eContentView.swift\u003c/code\u003e file, so I\u0026rsquo;m hoping it will understand that I\u0026rsquo;d like a SwiftUI interface. But I haven\u0026rsquo;t mentioned \u003ccode\u003eAVAudioEngine\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eHow does Copilot fair?\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/copilot-tone-generator-1.png\"\n    alt=\"Copilot\u0026rsquo;s first effort\" width=\"500px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eCopilot\u0026rsquo;s first effort\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eIt has created an \u003ccode\u003eAudioEngine\u003c/code\u003e class that tries to render to a PCM buffer and play that in a loop. But it looks clunky (there\u0026rsquo;s no real need for separate \u0026ldquo;Play\u0026rdquo; and \u0026ldquo;Stop\u0026rdquo; buttons and that \u0026ldquo;Frequency\u0026rdquo; slider is using a step size of 1 with 2000 steps, making it slow and largely unusable) but worst of all, \u003cstrong\u003eit doesn\u0026rsquo;t work\u003c/strong\u003e. Hitting play throws an exception.\u003c/p\u003e\n\u003cp\u003eNot a great start.\u003c/p\u003e\n\u003cp\u003eLooking at the code, it\u0026rsquo;s pretty simple to see that the exception is because the \u003ccode\u003eformat\u003c/code\u003e in the \u003ccode\u003einit\u003c/code\u003e function – as used to connect the player to the \u003ccode\u003eoutputNode\u003c/code\u003e – is not the same as the \u003ccode\u003eformat\u003c/code\u003e used in the \u003ccode\u003estart\u003c/code\u003e function and the mismatch is leading to a runtime exception.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003einit\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eengine\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eattach\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eplayer\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eformat\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eengine\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eoutputNode\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003einputFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eforBus\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eengine\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econnect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eplayer\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eto\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eengine\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eoutputNode\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eformat\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eformat\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e?\u003c/span\u003e \u003cspan class=\"n\"\u003eengine\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003estart\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewaveform\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eWaveform\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003efrequency\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eDouble\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003evolume\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eDouble\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003esampleRate\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e44100\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003elength\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eAVAudioFrameCount\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esampleRate\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eformat\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eAVAudioFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estandardFormatWithSampleRate\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eDouble\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esampleRate\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"n\"\u003echannels\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ebuffer\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eAVAudioPCMBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epcmFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eformat\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eframeCapacity\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ebuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e?.\u003c/span\u003e\u003cspan class=\"n\"\u003eframeLength\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003elength\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eLet\u0026rsquo;s see if Copilot can sort out this problem.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cb\u003ePrompt:\u003c/b\u003e The AudioEngine isn\u0026rsquo;t making a sound. Can you identify what is wrong?\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eCopilot adds a catch around the creation of the \u003ccode\u003eAVAudioEngine\u003c/code\u003e (previously it was using a \u003ccode\u003etry?\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edo\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003etry\u003c/span\u003e \u003cspan class=\"n\"\u003eengine\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003ecatch\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"bp\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Error starting audio engine: \u003c/span\u003e\u003cspan class=\"si\"\u003e\\(\u003c/span\u003e\u003cspan class=\"n\"\u003eerror\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elocalizedDescription\u003c/span\u003e\u003cspan class=\"si\"\u003e)\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYeah, it didn\u0026rsquo;t have a clue.\u003c/p\u003e\n\u003cp\u003eMaybe if I give Copilot the exact exception, it can fix the problem.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003ePrompt:\u003c/strong\u003e Can you fix the following exception that is thrown when scheduleBuffer is called: \u0026ldquo;required condition is false: [AVAudioPlayerNode.mm:740:ScheduleBuffer: (_outputFormat.channelCount == buffer.format.channelCount)]\u0026rdquo;\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eWith this, Copilot does make the audio play – by changing the \u003ccode\u003eformat\u003c/code\u003e line in the \u003ccode\u003estart\u003c/code\u003e function to:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eformat\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eengine\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eoutputNode\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003einputFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eforBus\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHowever, even though the audio plays, it is mono, in the left speaker only. A proper fix would have been to take the \u003ccode\u003eformat\u003c/code\u003e, as defined in the \u003ccode\u003estart\u003c/code\u003e function, and use it when connecting the \u003ccode\u003eplayer\u003c/code\u003e in the \u003ccode\u003einit\u003c/code\u003e function. I can do that manually and if I also remove the \u003ccode\u003estep\u003c/code\u003e from the Frequency slider in the UI (so the app doesn\u0026rsquo;t hang when trying to draw the slider), then the app \u0026ldquo;works\u0026rdquo;.\u003c/p\u003e\n\u003ch3 id=\"second-effort\"\u003eSecond effort\u003c/h3\u003e\n\u003cp\u003eI want to see if Copilot can handle a bit of cleanup if I give it the steps. Instead of pre-rendering a PCM buffer and looping that, I prefer to use a render function that generates the audio-on-the-fly.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003ePrompt:\u003c/strong\u003e Refactor the AudioEngine to generate audio using an AVAudioSourceNode render block that calls a generateSample function. Move the @State variables from the ContentView into the AudioEngine and make the ContentView observe the AudioEngine.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eCopilot follows all my instructions and the code now ends up \u003cem\u003elooking\u003c/em\u003e better but it has gone from \u0026ldquo;working\u0026rdquo; to \u0026ldquo;not working\u0026rdquo;, again. Now the tone plays immediately on application startup and the \u0026ldquo;Play\u0026rdquo; and \u0026ldquo;Stop\u0026rdquo; buttons no longer work.\u003c/p\u003e\n\u003cp\u003eFor some reason, alongside the requested changes, Copilot decided to change the \u003ccode\u003estart\u003c/code\u003e and \u003ccode\u003estop\u003c/code\u003e functions to set the \u003ccode\u003eengine.mainMixerNode.outputVolume\u003c/code\u003e to 1 or 0 (instead of actually pausing the audio generation). This is a bad idea (you should either pause or stop when audio generation is paused by the user). But it doesn\u0026rsquo;t work because in the current state, the \u003ccode\u003emainMixerNode\u003c/code\u003e is not being added to the graph at all, so setting volume on it has no effect.\u003c/p\u003e\n\u003cp\u003eGetting a little exhausted with these issues, I resort to manually editing the code: calling \u003ccode\u003estart()\u003c/code\u003e and \u003ccode\u003estop()\u003c/code\u003e on the engine instead of changing the volume, removing the \u003ccode\u003estep\u003c/code\u003e parameter from the frequency slider that was making it practically unusable and replacing the separate \u0026ldquo;Play\u0026rdquo; and \u0026ldquo;Stop\u0026rdquo; buttons with a single toggle button that changes its label.\u003c/p\u003e\n\u003cp\u003eSo far, I\u0026rsquo;ve spent about 20 minutes of cleanup, plus inspection and LLM prompting, on what\u0026rsquo;s less than 200 lines of code. I could probably have written the code myself in this time so I feel like it\u0026rsquo;s a borderline call about whether the LLM is helping at this point.\u003c/p\u003e\n\u003ch2 id=\"lets-synthesize-a-raindrop-sound\"\u003eLet\u0026rsquo;s synthesize a raindrop sound\u003c/h2\u003e\n\u003cp\u003eI had an ulterior motive with this entire exercise. I didn\u0026rsquo;t really want a tone generator. I really wanted to try a raindrop sound synthesizer. I had randomly stumbled across GitHub repository \u003ca href=\"https://github.com/747745124/Raindrop-Generator\"\u003ehttps://github.com/747745124/Raindrop-Generator\u003c/a\u003e and wanted to try the simple waveform it suggested might sound like a raindrop.\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/raindrop.png\"\n    alt=\"Schematic of the sound produced by a water droplet falling onto a liquid surface. Credit Stanley J. Miklavcic, Andreas Zita and Per Arvidsson\" width=\"500px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eSchematic of the sound produced by a water droplet falling onto a liquid surface. Credit \u003ca href=\"https://www.dafx.de/paper-archive/details/eRUDT2B3rmJZ6w0yVL-fyw\"\u003eStanley J. Miklavcic, Andreas Zita and Per Arvidsson\u003c/a\u003e\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eGrabbing the \u0026ldquo;drop_v2.hpp\u0026rdquo; file from that repository, I asked Copilot to swap the tone generator for a raindrop generator.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003ePrompt:\u003c/strong\u003e The samples generated by generateSample in AudioEngine are a basic tone with either a sine, square or sawtooth waveform. Replace this tone generation with a raindrop sample generation. Use the algorithm in drop_v2.hpp for the raindrop sound.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eThis change broke the compile since it removed a number of parameters that the \u003ccode\u003eContentView\u003c/code\u003e referenced but after cleaning that up, this step in the exercise was by far the most impressive help that Copilot was able to give.\u003c/p\u003e\n\u003cp\u003eOrdinarily, when given a new piece of code, in an unfamiliar language and told to integrate the algorithm into an existing codebase, most programmers would need time to process, time to understand and time to feel out the best way to apply.\u003c/p\u003e\n\u003cp\u003eFor an LLM, translating from one context to another is their biggest strength. It\u0026rsquo;s no problem that one codebase is C++ and the other is Swift. You could argue that all an LLM \u003cem\u003eever\u003c/em\u003e really does is take the pattern and structure of content absorbed in one context and re-emitting that content in a way that follows on from a new context.\u003c/p\u003e\n\u003cp\u003eHowever, it\u0026rsquo;s difficult to tell that this code is working from the audio alone, since it is playing just one 20ms raindrop sound and then stopping.\u003c/p\u003e\n\u003cp\u003eWe\u0026rsquo;re going to need more drops.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003ePrompt:\u003c/strong\u003e The AVAudioSourceNode render function currently plays a single raindrop sound from the rainDrop variable which stops playing once time reaches tInit + deltaT3. This code should be changed so that instead of a single raindrop, there\u0026rsquo;s an array of raindrops. This array should initially contain just one raindrop but more raindrops should be added to the array after random intervals so that a target number of raindrops are added per minute.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eFor reasons that are not clear, Copilot ignored the request for \u0026ldquo;random\u0026rdquo; and instead the drops are being scheduled precisely \u003ccode\u003esampleInterval\u003c/code\u003e apart.\u003c/p\u003e\n\u003cp\u003eAfter plugging in a rough \u0026ldquo;random\u0026rdquo; concept (I don\u0026rsquo;t think I\u0026rsquo;ve done a great job, here) and hooking up some sliders to the new \u003ccode\u003esampleInterval\u003c/code\u003e and \u003ccode\u003erandomness\u003c/code\u003e properties that Copilot has decided to include, the app now looks like this:\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/raingenerator.png\"\n    alt=\"A raindrop synthesizer that actually works\" width=\"500px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eA raindrop synthesizer that actually works\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eI\u0026rsquo;m not sure what that \u0026ldquo;randomness\u0026rdquo; parameter is supposed to be. It\u0026rsquo;s not randomizing the interval between drops and is instead hooked up to a number of internal parameters in a seemingly haphazard way.\u003c/p\u003e\n\u003ch2 id=\"charting-the-waveform\"\u003eCharting the waveform\u003c/h2\u003e\n\u003cp\u003eThe drops sometimes sound like raindrops on a tent, sometimes like small drops in a water glass and often like someone playing a toy xylophone. If nothing else, I\u0026rsquo;d like to confirm that my waveforms match that in the \u0026ldquo;A simple water droplet waveform\u0026rdquo; graph that I showed, above.\u003c/p\u003e\n\u003cp\u003eTo do this, it would be helpful to plot the waveform.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003ePrompt:\u003c/strong\u003e I would like to use Swift Charts in the ContentView to show a sample waveform generated by the AudioEngine that updates as the user adjusts the sliders in the ContentView.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eThis did add a Swift Chart but it showed nothing.\u003c/p\u003e\n\u003cp\u003eA little inspection revealed that it was plotting the first 1000 samples\u0026hellip; of a 44kHz waveform, so just 250 microseconds. Additionally, it was trying to plot all raindrops but I wanted the waveform of a single raindrop.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003ePrompt:\u003c/strong\u003e For the generateWaveform function, just generate the waveform from a single raindrop, created locally from the createNewRaindrop function.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eAgain, some cleanup was needed. I also decided to color code things and hook up sliders to all the key variables (asking Copilot to do this just made a mess).\u003c/p\u003e\n\u003cp\u003eThe end result though is quite nice:\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/chart-waveform.png\"\n    alt=\"The waveform of the synthesized raindrop\" width=\"500px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eThe waveform of the synthesized raindrop\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eThis confirms that the synthesized raindrop is approximately the same as the target waveform.\u003c/p\u003e\n\u003ch2 id=\"adding-some-noise\"\u003eAdding some noise\u003c/h2\u003e\n\u003cp\u003eIn terms of synthesizing the sound of a raindrop – it\u0026rsquo;s not great. I can tweak the parameters but it never really sounds like a raindrop and definitely never sounds like rain.\u003c/p\u003e\n\u003cp\u003eMost people who synthesize rain generally start with a noise generator. There are different types of noise (white, pink, brown). While I know how to write white noise, I\u0026rsquo;m not sure about brown and don\u0026rsquo;t have any knowledge of pink noise.\u003c/p\u003e\n\u003cp\u003eSo I make Copilot do it.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003ePrompt:\u003c/strong\u003e Add pink noise to the audio generated by the AudioEngine.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003egeneratePinkNoise\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ewhite\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erandom\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003ein\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mf\"\u003e1.0\u003c/span\u003e\u003cspan class=\"p\"\u003e...\u003c/span\u003e\u003cspan class=\"mf\"\u003e1.0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.99886\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ewhite\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.0555179\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.99332\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ewhite\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.0750759\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.96900\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ewhite\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.1538520\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.86650\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ewhite\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.3104856\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.55000\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ewhite\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.5329522\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.7616\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"n\"\u003ewhite\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.0168980\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003epink\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e6\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003ewhite\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.5362\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epinkNoiseState\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e6\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ewhite\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.115926\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003epink\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt got this right, first time. I think. Honestly, I don\u0026rsquo;t know what pink noise is supposed to do.\u003c/p\u003e\n\u003cp\u003eLooking around to see how pink noise generation should look, Copilot appears to have chosen an implementation of the \u0026ldquo;Voss-McCartney algorithm\u0026rdquo; for pink noise and \u003ca href=\"https://www.firstpr.com.au/dsp/pink-noise/\"\u003eit has a fairly long history\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eFurther adding brown noise and white noise and hooking up some sliders gives the final form of the app:\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/raingenerator-plusnoise.png\"\n    alt=\"Raindrop and noise generator\" width=\"500px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eRaindrop and noise generator\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003ch2 id=\"cleaning-up-thread-safety\"\u003eCleaning up thread safety\u003c/h2\u003e\n\u003cp\u003eIt seems that the \u003ccode\u003eAVAudioSourceNode\u003c/code\u003e closure is missing an \u003ccode\u003e@Sendable\u003c/code\u003e annotation so even though Swift 6 doesn\u0026rsquo;t give any warnings, the code at this point is thread unsafe. In fact, if you annotate \u003ccode\u003eAudioEngine\u003c/code\u003e with \u003ccode\u003e@MainActor\u003c/code\u003e, you\u0026rsquo;ll get a fatal error at runtime when the render closure is invoked, indicating that Swift 6 is under the (incorrect) assumption that the closure will run in the same context as the surrounding type.\u003c/p\u003e\n\u003cp\u003eI don\u0026rsquo;t think there\u0026rsquo;s anything that would crash the app if the thread safety is left unaddressed – just some parameters that may be invalid or fail to update correctly – but it\u0026rsquo;s not a good idea to write to the parameters on the main thread and read them from the audio render thread without some form of synchronization.\u003c/p\u003e\n\u003cp\u003eI asked Copilot to fix the problem:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003ePrompt:\u003c/strong\u003e To ensure thread safety in the AudioEngine class, apply these steps:\u003cbr\u003e\n\u003cbr\u003e\nAdd the @MainActor annotation to the AudioEngine class.\u003cbr\u003e\nAdd the @Sendable annotation to the AVAudioSourceNode closure.\u003cbr\u003e\nAdd the nonisolated keyword to the generateAudio function.\u003cbr\u003e\nUse \u003ccode\u003eAtomic\u003c/code\u003e values to copy parameters from the main actor to the generateAudio function.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eCopilot ignored the request for an \u003ccode\u003e@Sendable\u003c/code\u003e annotation and used an \u003ccode\u003eNSLock\u003c/code\u003e instead of \u003ccode\u003eAtomic\u003c/code\u003e so it wasn\u0026rsquo;t exactly what I wanted. I can\u0026rsquo;t \u003cem\u003ereally\u003c/em\u003e fault Copilot since I\u0026rsquo;m also not sure how to correctly use \u003ccode\u003eAtomic\u003c/code\u003e to solve this problem. In the end, the final commit in this repository (applied manual during a final cleanup phase) adds thread safety via Swift\u0026rsquo;s new \u003ccode\u003eMutex\u003c/code\u003e type. I\u0026rsquo;m not completely happy with that (you don\u0026rsquo;t generally want to deal with locks on the audio processing thread) but I\u0026rsquo;m not sure what the best pattern for exchanging data via \u003ccode\u003eAtomic\u003c/code\u003e would be.\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusion\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eCheck it out\u003c/strong\u003e: the final product plus most of the intermediate steps discussed in this article are available from the \u003ca href=\"https://github.com/mattgallagher/RainGenerator\"\u003eRainGenerator repository\u003c/a\u003e.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"is-it-a-good-rain-synthesizer\"\u003eIs it a good rain synthesizer?\u003c/h3\u003e\n\u003cp\u003eNot really but it was fun to play with, especially once I could see the waveform via the chart.\u003c/p\u003e\n\u003cp\u003eIt turns out that the rough waveform \u003ca href=\"https://www.dafx.de/paper-archive/details/eRUDT2B3rmJZ6w0yVL-fyw\"\u003ecame from a paper by Stanley J. Miklavcic; Andreas Zita; Per Arvidsson\u003c/a\u003e which goes more into depth about necessary randomization, distribution and spatial effects in order make the raindrops sound more real and even then concludes that other water, flowing and storm sounds are required to fill out the complete soundscape of rain.\u003c/p\u003e\n\u003ch3 id=\"what-was-copilot-good-at\"\u003eWhat was Copilot good at?\u003c/h3\u003e\n\u003cp\u003eI asked Copilot to perform a number of refactoring passes and it was generally good at following instructions and cleaning up the integration points. This project is less than 500 lines though, so I don\u0026rsquo;t know how well it might scale to a more functional sized app.\u003c/p\u003e\n\u003cp\u003eFor SDKs where I wasn\u0026rsquo;t familiar, it was quicker to give Copilot a vague description of intent than to look up the documentation, read the documentation and integrate into the surrounding context. This is a big win and might help to eliminate the trepidation involved in working with new SDKs.\u003c/p\u003e\n\u003cp\u003eCopilot was \u003cem\u003ereally\u003c/em\u003e good at integrating existing code from another location, even when that code was in a different programming language.\u003c/p\u003e\n\u003cp\u003eIt was also \u003cem\u003ereally\u003c/em\u003e good at looking up how to implement a relatively obscure algorithm like pink noise, even when it\u0026rsquo;s not an algorithm where the first result in a web search will give a good response.\u003c/p\u003e\n\u003ch3 id=\"ways-in-which-copilot-was-better-than-expected-but-not-great\"\u003eWays in which Copilot was better than expected but not great\u003c/h3\u003e\n\u003cp\u003eI never had a syntax error.\u003c/p\u003e\n\u003cp\u003eI expected more hallucinations or references to non-existent symbols. I really only experienced one variable that didn\u0026rsquo;t exist (from the Claude chatbot) and one function that didn\u0026rsquo;t exist (from Copilot). The non-existant function was \u003ccode\u003eFloat.clamped(to: ClosedRange\u0026lt;Float\u0026gt;)\u003c/code\u003e – which I simply implemented as it\u0026rsquo;s likely just an extension that Copilot has trained on without realizing it\u0026rsquo;s not a Swift extension.\u003c/p\u003e\n\u003cp\u003eOther compilation errors I experienced were generally minor (e.g. use of iOS only APIs on the Mac and a use of a variable before the init function had finished initializing) or an update to one file that breaks another file not included in the chat context.\u003c/p\u003e\n\u003ch3 id=\"what-was-copilot-bad-at\"\u003eWhat was Copilot bad at?\u003c/h3\u003e\n\u003cp\u003eI immediately wanted to make minor changes to almost everything Copilot wrote.\u003c/p\u003e\n\u003cp\u003eThe most common problems were violations of \u0026ldquo;Don\u0026rsquo;t repeat yourself\u0026rdquo;, \u0026ldquo;Keep it simple, stupid\u0026rdquo;, missing abstractions or unneeded indirection. Just about every common coding problem was there.\u003c/p\u003e\n\u003cp\u003eCopilot would repeat code a lot in SwiftUI with the same set of modifiers applied without any desire to avoid repetition. Copilot would also do goofy things like adding \u003ccode\u003edidSet\u003c/code\u003e or \u003ccode\u003eonChange\u003c/code\u003e handlers everywhere instead of simply changing the underlying data without a layer of indirection.\u003c/p\u003e\n\u003cp\u003eI ended up manually adding \u003ccode\u003eAudioEngine\u003c/code\u003e subtypes – \u003ccode\u003eRaindrop\u003c/code\u003e, \u003ccode\u003eParameters\u003c/code\u003e and \u003ccode\u003eGeneratorState\u003c/code\u003e – to encapsulate parameters and state and simplify observing and updates because Copilot doesn\u0026rsquo;t seem to do any such tidy ups without explicit instruction.\u003c/p\u003e\n\u003cp\u003eAs discussed, Copilot left the audio graph incompletely connected, failed to get audio formats aligned between functions. Unless two functions are directly interfacing, it doesn\u0026rsquo;t seem to check if they\u0026rsquo;re doing anything related that should be made common.\u003c/p\u003e\n\u003cp\u003eIn some requests, it would outright ignore part of the instructions like \u0026ldquo;random intervals\u0026rdquo; or \u0026ldquo;add @Sendable\u0026rdquo; to this closure. Other requests like \u0026ldquo;tidy up the user interface so things are more compact and aligned\u0026rdquo; don\u0026rsquo;t seem to have any practical effect (though Copilot might respond by adding a bunch of repetitive modifiers like \u003ccode\u003eonChange\u003c/code\u003e to each slider for no reason).\u003c/p\u003e\n\u003cp\u003eCopilot never seemed to work out that I was writing a Mac app and kept trying to stick a green background with corner radius inside the Mac \u0026ldquo;bordered\u0026rdquo; buttons (really unsightly) and it tried color everything \u003ccode\u003esystemGray6\u003c/code\u003e (I\u0026rsquo;m not sure why a color should be iOS-only but it is). Perhaps this is my fault for failing to inform the bot about the compile target but it\u0026rsquo;s just another way you need to work to keep an LLM assistant behaving correctly.\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/ugly-green-button.png\"\n    alt=\"This Mac button already has a border, and even if this was iOS, that\u0026rsquo;s probably too much green\" width=\"500px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eThis Mac button already has a border, and even if this was iOS, that\u0026rsquo;s probably too much green\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eLanguages and SDKs evolve pretty quickly and Copilot still seems to prefer \u003ccode\u003e@ObservableObject\u003c/code\u003e over \u003ccode\u003e@Observable\u003c/code\u003e and seems largely unaware of Swift 6, \u003ccode\u003e@Sendable\u003c/code\u003e, \u003ccode\u003eAtomic\u003c/code\u003e and other changes in the last 6 months. You\u0026rsquo;re limited to its training set and that\u0026rsquo;s never going to include as much \u0026ldquo;upcoming\u0026rdquo; code as \u0026ldquo;legacy\u0026rdquo; code.\u003c/p\u003e\n\u003ch3 id=\"problems-with-vs-code--copilot-for-swift\"\u003eProblems with VS Code + Copilot for Swift\u003c/h3\u003e\n\u003cp\u003eRounding out the negatives are a number of ergonomic problems related to VS Code and its current integration of Copilot.\u003c/p\u003e\n\u003cp\u003eAny time I manually made changes to the code there\u0026rsquo;s the problem that this can make the code out-of-sync with the chat window so if you do ask Copilot for more changes, it may behave like your manual changes don\u0026rsquo;t exist and revert them in its next refactor creating a situation where bugs and entire previous states of the app may reappear.\u003c/p\u003e\n\u003cp\u003eWith code naturally spanning multiple files, it\u0026rsquo;s easy to forget to include all files in the chat context and have refactoring changes break the connections between two files. What I\u0026rsquo;d really like is an IDE that automatically detected which files and functions are actually relevant to the context and automatically include them with the request.\u003c/p\u003e\n\u003cp\u003eAnd finally, VS Code isn\u0026rsquo;t Xcode, so it\u0026rsquo;s not an environment focussed on Swift development. Another developer might be familiar with VS Code but I\u0026rsquo;m not and I found myself bouncing between IDEs a lot.\u003c/p\u003e\n\u003ch3 id=\"was-copilot-worth-it\"\u003eWas Copilot worth it?\u003c/h3\u003e\n\u003cp\u003eSometimes, yes. But not always and therein lies an estimation risk. Will Copilot write the code I need and save me time? Or will it waste my time as I clean up the mess it makes?\u003c/p\u003e\n\u003cp\u003eIt can be hard to guess whether it would be faster to ask Copilot to improve its own code or to skip Copilot and apply the fix manually. Just typing out a detailed request for changes can take longer than making the changes myself – largely because I\u0026rsquo;m really accustomed to editing code but I find that being accurate in prose is a slow process. Trying to clean up code details by talking in chat with Copilot can start to feel like a sunk cost problem. Combined with the above-mentioned complication that blending Copilot and manual changes can cause chat to lose sync with the code, it becomes exhausting.\u003c/p\u003e\n\u003cp\u003eWorking with an LLM assistant really does feel like training a very keen but very messy new developer – totally happy to write 1000 lines of code to solve a problem that should be solved in 100 lines and when the PR comes in, you need to slowly walk them through all the things they need to change before you can feel like the code is maintainable.\u003c/p\u003e\n\u003cp\u003eThere is a big difference though: with the new developer, the time taken to help them fix their code isn\u0026rsquo;t time wasted. They\u0026rsquo;ll improve, get accustomed to the patterns and expectations of the codebase and in a few months their PRs will be easier to review and they\u0026rsquo;ll start fixing your dumb mistakes.\u003c/p\u003e\n\u003cp\u003eBy contrast: Copilot is not learning from my suggestions. There\u0026rsquo;s nothing to be won by making it clean up its own mess if a manual change would have been faster. I\u0026rsquo;d certainly \u003cem\u003elike\u003c/em\u003e Copilot to be better at eliminating redundancy and improving coordination between disparate parts of an app but I can\u0026rsquo;t make it happen in the short term.\u003c/p\u003e\n",
            "date_published": "2024-12-25T21:35:10+11:00"
        }, {
            "title": "App architecture basics in SwiftUI Part 4: Services",
            "id": "https://www.cocoawithlove.com/blog/separated-services-layer.html",
            "url": "https://www.cocoawithlove.com/blog/separated-services-layer.html",
            "content_html": "\u003cp\u003eThis article is about adding a separated Services-layer to an app. A Services-layer is, in my opinion, the single best app architectural addition you can make, after the basic Model-View separation already implicit in SwiftUI. Model-View separation decouples the Model from the front-end but a Services-layer completes Model separation by separating the Model from the back-end and is one of the few app architectural changes you can point at and say: this change unlocks these capabilities.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ll be changing just 9 lines from the \u003ca href=\"/blog/app-submodules.html\"\u003e127 line CwlFeedReader app from the previous article\u003c/a\u003e (plus a few lines of boilerplate in new files). It\u0026rsquo;s a relatively small change but it\u0026rsquo;s a topic with plenty of different opinions about the best approach so there\u0026rsquo;s a lot of discuss.\u003c/p\u003e\n\u003c!-- TOC --\u003e\n\u003ch2 id=\"lets-try-to-add-tests-to-cwlfeedreader\"\u003eLet\u0026rsquo;s try to add tests to CwlFeedReader\u003c/h2\u003e\n\u003cp\u003eBefore we look in detail at what I mean by a \u0026ldquo;separated Services-layer\u0026rdquo;, I\u0026rsquo;d like to start by looking at the biggest capability this change will unlock: testing.\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s assume we want to do the bare minimum in testing. This generally means interface tests on the \u003ccode\u003eModel\u003c/code\u003e. By \u0026ldquo;interface\u0026rdquo;, I\u0026rsquo;m referring to the public functions of the Model (the \u003cem\u003emodule\u0026rsquo;s\u003c/em\u003e interface, not the user-interface). This type of test can detect major regressions (functionality that is accidentally broken when you add new features) and is a valuable addition to any app project due to its low time cost.\u003c/p\u003e\n\u003cp\u003eThe interface to the \u003ccode\u003eModel\u003c/code\u003e looks like this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eModel\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eObservableObject\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003ePublished\u003c/span\u003e \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kr\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003efeed\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eFeed\u003c/span\u003e\u003cspan class=\"p\"\u003e?\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003ePublished\u003c/span\u003e \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003eerror\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eIdentifiableError\u003c/span\u003e\u003cspan class=\"p\"\u003e?\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003ePublished\u003c/span\u003e \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kr\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003eisReadStatuses\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eBool\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003einit\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003esetIsRead\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003e_\u003c/span\u003e \u003cspan class=\"n\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eBool\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003ereload\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eTo test this interface, we need tests that call each of:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003ccode\u003einit\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003esetIsRead\u003c/code\u003e and\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ereload\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003ethat validate the behavior of these functions by reading the changes to \u003ccode\u003efeed\u003c/code\u003e, \u003ccode\u003eerror\u003c/code\u003e and \u003ccode\u003eisReadStatuses\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ve written tests for all 3 functions in \u003ca href=\"https://github.com/mattgallagher/CwlFeedReader/tree/part-four-broken-tests\"\u003ethe CwlFeedReader repository\u003c/a\u003e but I want to focus on the test for the most important function in the program, \u003ccode\u003ereload\u003c/code\u003e. There\u0026rsquo;s a complication that \u003ccode\u003ereload\u003c/code\u003e is called from inside \u003ccode\u003einit\u003c/code\u003e so I\u0026rsquo;m going to simply construct \u003ccode\u003eModel()\u003c/code\u003e and – relying on the fact that \u003ccode\u003ereload\u003c/code\u003e delivers its results \u003cem\u003easychronously\u003c/em\u003e to the main thread – I will then examine the \u003cem\u003esecond\u003c/em\u003e value received via \u003ccode\u003e$feed\u003c/code\u003e and treat this as equivalent to the result of calling \u003ccode\u003ereload\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003efeedFirstURL\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;https://www.cocoawithlove.com/blog/swiftui-natural-pattern.html\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003etestReload\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"c1\"\u003e// Given a newly inited model and an expectation that stops on the second feed value\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003emodel\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eModel\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003esecondValue\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eexpectation\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edescription\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;feed should emit 2 values.\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ecancellable\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"err\"\u003e$\u003c/span\u003e\u003cspan class=\"n\"\u003efeed\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"bp\"\u003edropFirst\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esink\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"kc\"\u003e_\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003esecondValue\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efulfill\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"c1\"\u003e// When the automatically invoked `reload()` completes\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003ewait\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003esecondValue\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003etimeout\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mf\"\u003e30.0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003ecancellable\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecancel\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"c1\"\u003e// Then the first feed URL should be the expected swiftui-natural-pattern.html\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003eXCTAssertEqual\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efeed\u003c/span\u003e\u003cspan class=\"p\"\u003e?.\u003c/span\u003e\u003cspan class=\"n\"\u003eitems\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"bp\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"bp\"\u003efirst\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003efeedFirstURL\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eEverything looks good, right? I mean: there\u0026rsquo;s no bug in the code. Not exactly.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThese tests are available from the \u003ca href=\"https://github.com/mattgallagher/CwlFeedReader/tree/part-four-broken-tests\"\u003epart-four-broken-tests branch of the CwlFeedReader repository\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eThe test fails.\u003c/p\u003e\n\u003cp\u003eWhy? Because that \u003ccode\u003efeedFirstURL\u003c/code\u003e value was correct in late January when I wrote the test but it has already broken because the Cocoa with Love JSON feed has updated since then. We wanted this test to detect regression in the \u003ccode\u003ereload\u003c/code\u003e function but instead, we\u0026rsquo;ve merely detected that live-data has changed. This test is a waste of our time.\u003c/p\u003e\n\u003cp\u003eBut the test isn\u0026rsquo;t the source of the problem, it is merely a symptom.\u003c/p\u003e\n\u003cp\u003eThe true source of the problem is that it is not possible to isolate the code we\u0026rsquo;ve written from dependencies we use (the state of the network and data received from remote servers). If the upstream data changes, our tests fail. If the network is down, our tests fail. There are potentially worse problems that could occur if our tests overwhelm production servers or if our tests mistakenly change live data.\u003c/p\u003e\n\u003cp\u003eAnd even if live data isn\u0026rsquo;t a problem, our tests are \u003cem\u003emuch slower\u003c/em\u003e than they should be. The test relies on asynchronous I/O, causing the test to take between a few hundred milliseconds and a few seconds, instead of a couple microseconds. While this may seem like the least significant problem here, this adds up. Even small apps can have thousands of tests. Hundreds of milliseconds per test can become \u003cem\u003ehours\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eThe conclusion here is straightforward: if you can\u0026rsquo;t isolate your application from the outside world, tests are practically useless. Even if you can endure unreliability, the best you can achieve are some basic app-server integration smoke tests. Any other kind of testing will be impossible.\u003c/p\u003e\n\u003ch2 id=\"the-problem-is-broader-than-testing\"\u003eThe problem is broader than testing\u003c/h2\u003e\n\u003cp\u003eIt\u0026rsquo;s not just tests that are facing problems. We can\u0026rsquo;t do any useful work on the app if dependencies are unavailable. And dependencies are often, by their nature, beyond our control. If the server goes down, we\u0026rsquo;re blocked. If the server APIs are not finished, we\u0026rsquo;re blocked. Want to run a demo in a location without network access? You can\u0026rsquo;t.\u003c/p\u003e\n\u003cp\u003eEven the SwiftUI previews in CwlFeedReader construct the \u003ccode\u003eModel()\u003c/code\u003e and access the network for their content. Our SwiftUI previews can fail or misbehave when the network is unavailable. If we can\u0026rsquo;t usefully edit our \u003cem\u003eviews\u003c/em\u003e without working dependencies, then we\u0026rsquo;re in trouble.\u003c/p\u003e\n\u003cp\u003eAvailability isn\u0026rsquo;t the only problem. Configuration of data is also a limitation. Want to debug a situation that requires specific data or user states? You\u0026rsquo;ll have to manually create those users and states each time.\u003c/p\u003e\n\u003cp\u003eThese are the symptoms of an app that we can\u0026rsquo;t isolate from its dependencies but there\u0026rsquo;s another, more conceptual problem: we don\u0026rsquo;t even have a \u003cem\u003edescription\u003c/em\u003e of our app without its dependencies.\u003c/p\u003e\n\u003cp\u003eYes, the \u003cem\u003euser\u003c/em\u003e sees the Cocoa with Love JSON feed presented as a \u003ccode\u003eSwiftUI\u003c/code\u003e \u003ccode\u003eList\u003c/code\u003e and \u003ccode\u003eWKWebView\u003c/code\u003e but that\u0026rsquo;s not what\u0026rsquo;s in our Model-layer. What the user \u003cem\u003esees\u003c/em\u003e is a product of the data from connected \u003cem\u003edependencies\u003c/em\u003e flowing through the app.\u003c/p\u003e\n\u003cp\u003eIf we remove all dependencies from consideration (including the View-layer), we\u0026rsquo;ve written a program that:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003efetches a JSON list containing identifiers, including error handling on fetch\u003c/li\u003e\n\u003cli\u003ecan re-fetch the JSON list when requested\u003c/li\u003e\n\u003cli\u003efetches a dictionary of booleans, keyed by the same identifiers in the JSON list\u003c/li\u003e\n\u003cli\u003ecan update and save the booleans for each identifier when requested\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eThis could be the description of a to-do list app, an email client or a control app for a network-connected set of light-switches. The only difference is the dependencies we reference.\u003c/p\u003e\n\u003cp\u003eIf we control how we reference our dependencies, we can develop, test and use our program with precision, be flexible between problem domains, and we can work without getting blocked.\u003c/p\u003e\n\u003ch2 id=\"putting-it-in-a-diagram\"\u003ePutting it in a diagram\u003c/h2\u003e\n\u003cp\u003eHere\u0026rsquo;s a diagram of the CwlFeedReader app, showing the commonly identified Model-View-Controller components. Solid lines show references (use of concrete types or functions), dotted lines show data flow without reference. No solid arrow should ever point away from the Model – that would indicate a violation of Model isolation.\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/mvc_minus_services.svg\"\n    alt=\"MVC representation of CwlFeedReader (solid lines are explicit references, dotted lines are decoupled interactions)\" width=\"500px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eMVC representation of CwlFeedReader (solid lines are explicit references, dotted lines are decoupled interactions)\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eThis diagram isn\u0026rsquo;t \u003cem\u003ewrong\u003c/em\u003e but it shows only the front-end of the Model-layer. Here\u0026rsquo;s a more complete architectural diagram of the CwlFeedReader app including the back-end:\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/mvc_services.svg\"\n    alt=\"MVC\u0026#43;Services representation of CwlFeedReader (solid lines are explicit references, dotted lines are decoupled interactions)\" width=\"700px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eMVC+Services representation of CwlFeedReader (solid lines are explicit references, dotted lines are decoupled interactions)\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eImmediately, you should be able to see the problem: there\u0026rsquo;s a solid arrow pointing away from the Model. Our Model is not isolated because it directly references \u003ccode\u003eURLSession\u003c/code\u003e and \u003ccode\u003eUserDefaults\u003c/code\u003e – sources of side-effects that affect the app.\u003c/p\u003e\n\u003cp\u003eIn the same way that we need to maintain separation between the Model and the Controller+View components at the front-end, we need to maintain separation between the Model and these side-effects at the back end or we compromise our ability to test, isolate and reason about our own application.\u003c/p\u003e\n\u003ch2 id=\"some-clarification-around-the-word-services\"\u003eSome clarification around the word \u0026ldquo;Services\u0026rdquo;\u003c/h2\u003e\n\u003cp\u003eThere\u0026rsquo;s a few definitions of \u0026ldquo;services\u0026rdquo; around. The definition I\u0026rsquo;m using is:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eService\u003c/strong\u003e: any type or function that provides \u003cem\u003eside effects\u003c/em\u003e that can affect the testable behavior of the Model-layer\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cdiv class=\"aside\"\u003eA \u003ca href=\"https://en.wikipedia.org/wiki/Side_effect_(computer_science)\"\u003eside effect\u003c/a\u003e is a function (including methods and computed values) that might change its behavior despite being called with the same parameters. Typical examples are anything read from the network, disk, clock or operating system. In some cases, simply asynchronously returning results is side-effect (since it may change the ordering of results).\u003c/div\u003e\n\u003cp\u003eThe difficulty with services is that they\u0026rsquo;re dependent on the state of the things (network, disk, other processes running on the system). For testing and development purposes, we want to keep all of this state constant so we can focus on whether our program has changed behavior.\u003c/p\u003e\n\u003cp\u003eIdentification of services is only the first step. The second step is to move them into a Services-layer:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eServices-layer\u003c/strong\u003e: a collection of the app\u0026rsquo;s services and third-party dependencies, isolated so they are never directly referenced by the Model-layer, and replaceable at test or debug time with side-effect-free versions that offer robustly repeatable results.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cdiv class=\"aside\"\u003eThe term \u0026ldquo;dependency injection\u0026rdquo; is commonly used to describe separation from dependencies. The term can accurately be applied to my approach but I tend to downplay this description due to its association with \u0026ldquo;dependency injection frameworks\u0026rdquo; (an approach that I avoid, as I\u0026rsquo;ll discuss later).\u003c/div\u003e\n\u003cp\u003eNotice that I\u0026rsquo;ve added \u0026ldquo;third-party dependencies\u0026rdquo;, along with \u0026ldquo;app\u0026rsquo;s services\u0026rdquo; in the Services-layer. The purpose of a Services-layer is that it lets the app swap components out at launch – good for removing services for testing, also good for removing third-party dependencies for updates, refactoring or replacements.\u003c/p\u003e\n\u003ch2 id=\"how-can-we-decouple-the-model-from-services\"\u003eHow can we decouple the Model from Services?\u003c/h2\u003e\n\u003cp\u003eI want to separate the Model interface from the Services interface. Separating two interfaces from each other is a process usually called \u0026ldquo;decoupling\u0026rdquo;. Where do we draw a line for this separation?\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s consider the network-data request pipeline that involves \u003ccode\u003eURLSession\u003c/code\u003e in the CwlFeedReader app. The major features of the pipeline are:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003ethe Cocoa with Love domain name\u003c/li\u003e\n\u003cli\u003euse of the \u003ccode\u003eURLSession\u003c/code\u003e type\u003c/li\u003e\n\u003cli\u003euse of the relative path for the feed.json file, parameters and JSON encoding/decoding to marshal parameters to \u003ccode\u003eURLSession\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eIt\u0026rsquo;s possible to separate an app along any of these 3 lines. I consider only one of these choices \u0026ldquo;correct\u0026rdquo; but let\u0026rsquo;s look at the arguments used in favor of each.\u003c/p\u003e\n\u003ch3 id=\"1-decouple-from-production-server-using-different-environments\"\u003e1. Decouple from production server using different environments\u003c/h3\u003e\n\u003cp\u003eIt\u0026rsquo;s common to find apps that don\u0026rsquo;t change their \u003cem\u003ecode\u003c/em\u003e but change their \u003cem\u003edata\u003c/em\u003e and re-point their app at a stub-server when testing or debugging.\u003c/p\u003e\n\u003cp\u003eFor the CwlFeedReader app, you could imagine the \u003ccode\u003eModel\u003c/code\u003e taking an API base string in its \u003ccode\u003einit\u003c/code\u003e function (e.g. \u003ccode\u003ehttps://www.cocoawithlove.com\u003c/code\u003e) and at test time, passing a value like \u003ccode\u003ehttps://localhost:8080\u003c/code\u003e as an alternate argument for this parameter. Assuming you\u0026rsquo;re running a webserver on port 8080 on the same machine, you could place a fixed version of the \u0026ldquo;feed.json\u0026rdquo; file at the same relative path as on the production server.\u003c/p\u003e\n\u003ch4 id=\"pros\"\u003ePros\u003c/h4\u003e\n\u003cp\u003eThis approach lets you test every single line of code and, if retro-fitted to an existing codebase, requires the least effort.\u003c/p\u003e\n\u003ch4 id=\"cons\"\u003eCons\u003c/h4\u003e\n\u003cp\u003eThis approach has many limitations:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eWorks for network dependencies but is harder to apply to other dependencies (like \u003ccode\u003eUserDefaults\u003c/code\u003e) so it isn\u0026rsquo;t really usable for general service isolation.\u003c/li\u003e\n\u003cli\u003eEven for network dependencies, it decouples network data but not network APIs. Our app can\u0026rsquo;t swap to a different network library or data source using this approach.\u003c/li\u003e\n\u003cli\u003eYou need to start the stub server before testing, meaning that your tests don\u0026rsquo;t \u0026ldquo;just work\u0026rdquo; without extra setup.\u003c/li\u003e\n\u003cli\u003eSwitching configurations on the server for testing requires side-channel configuration of the server.\u003c/li\u003e\n\u003cli\u003eStub servers can be complex entities, often in another language, so they add additional learning overheads for Swift developers\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"2-put-a-protocol-around-the-smallest-possible-subset-of-urlsession\"\u003e2. Put a protocol around the smallest possible subset of URLSession\u003c/h3\u003e\n\u003cp\u003eAnother approach is to decouple the dependency in code but to keep this effort as simple as possible. In Swift, this means: create a protocol that describes our direct usage of each dependency.\u003c/p\u003e\n\u003cp\u003eWe could define the following protocol:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003eprotocol\u003c/span\u003e \u003cspan class=\"nc\"\u003eNetworkService\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003efetchData\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewith\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eURLRequest\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ehandler\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003eescaping\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eData\u003c/span\u003e\u003cspan class=\"p\"\u003e?,\u003c/span\u003e \u003cspan class=\"n\"\u003eURLResponse\u003c/span\u003e\u003cspan class=\"p\"\u003e?,\u003c/span\u003e \u003cspan class=\"n\"\u003eError\u003c/span\u003e\u003cspan class=\"p\"\u003e?)\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"nb\"\u003eVoid\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eAnyCancellable\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eand provide the following implementation to wrap our use of \u003ccode\u003eURLSession\u003c/code\u003e completely in a protocol.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eextension\u003c/span\u003e \u003cspan class=\"nc\"\u003eURLSessionDataTask\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eCancellable\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eextension\u003c/span\u003e \u003cspan class=\"nc\"\u003eURLSession\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eNetworkService\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003efetchData\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewith\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eURLRequest\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ehandler\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003eescaping\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eData\u003c/span\u003e\u003cspan class=\"p\"\u003e?,\u003c/span\u003e \u003cspan class=\"n\"\u003eURLResponse\u003c/span\u003e\u003cspan class=\"p\"\u003e?,\u003c/span\u003e \u003cspan class=\"n\"\u003eError\u003c/span\u003e\u003cspan class=\"p\"\u003e?)\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"nb\"\u003eVoid\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eAnyCancellable\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003etask\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edataTask\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewith\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecompletionHandler\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ehandler\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003etask\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eresume\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eAnyCancellable\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etask\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIf we only use \u003ccode\u003eURLSession\u003c/code\u003e through this protocol, then we become decoupled from \u003ccode\u003eURLSession\u003c/code\u003e as a dependency and source of side-effects.\u003c/p\u003e\n\u003ch4 id=\"pros-1\"\u003ePros\u003c/h4\u003e\n\u003cp\u003eWrapping dependencies in protocols is relatively simple and the resulting code maintains the same basic structure as if it were using \u003ccode\u003eURLSession\u003c/code\u003e.\u003c/p\u003e\n\u003ch4 id=\"cons-1\"\u003eCons\u003c/h4\u003e\n\u003cp\u003eDoesn\u0026rsquo;t wrap \u003ccode\u003eURLRequest\u003c/code\u003e or handle other details. We still need to configure details like path and headers and handle work like JSON decoding so it\u0026rsquo;s more like we need to say \u0026ldquo;fetch a JSON list whose location is defined by this URLRequest\u0026rdquo; instead of the simple  \u0026ldquo;fetch the feed\u0026rdquo;.\u003c/p\u003e\n\u003ch3 id=\"3-expand-the-protocol-boundary-to-include-related-busy-work\"\u003e3. Expand the protocol boundary to include related busy-work\u003c/h3\u003e\n\u003cp\u003eMarshalling parameters for network requests, filling in headers, selecting endpoints, decoding responses; this is all tedious work.\u003c/p\u003e\n\u003cp\u003eWe could define the \u003ccode\u003eNetworkService\u003c/code\u003e to have an interface that matches what our Model needs.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003eprotocol\u003c/span\u003e \u003cspan class=\"nc\"\u003eNetworkService\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003efetchFeed\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ehandler\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003eescaping\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eResult\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eFeed\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eError\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;)\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"nb\"\u003eVoid\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eAnyCancellable\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis creates a much simpler interface – no \u003ccode\u003eURLRequest\u003c/code\u003e, no JSON decoding, just the simple transaction that the Model desires.\u003c/p\u003e\n\u003ch4 id=\"pros-2\"\u003ePros\u003c/h4\u003e\n\u003cp\u003eMatches our Model\u0026rsquo;s \u0026ldquo;fetch feed\u0026rdquo; intent and makes the service feel like a clean interface.\u003c/p\u003e\n\u003ch4 id=\"cons-2\"\u003eCons\u003c/h4\u003e\n\u003cp\u003eAdding different interfaces for each kind of request increases the service size – and remember, most services are implemented \u003cem\u003etwice\u003c/em\u003e (once for the production services, once for the testing services) so this can be a significant additional burden.\u003c/p\u003e\n\u003cp\u003eServices themselves aren\u0026rsquo;t testable so putting more code into the service reduces potential test coverage. We\u0026rsquo;re putting Model-logic (correct headers for different endpoints, HTTP message encoding) in the untestable services.\u003c/p\u003e\n\u003cp\u003eNot reusable between different applications because it includes Model-specific logic.\u003c/p\u003e\n\u003ch3 id=\"personal-opinion\"\u003ePersonal opinion\u003c/h3\u003e\n\u003cp\u003eI think (2) is the best option: wrap dependencies with side-effects in a protocol that \u003cem\u003eminimally\u003c/em\u003e wraps the features of the dependency that your app uses. You can change the interface slightly (as I changed \u003ccode\u003edataTask\u003c/code\u003e to remove \u003ccode\u003eURLSesionDataTask\u003c/code\u003e from the interface) but such changes should be focussed on simplification without inclusion of \u003cem\u003eany\u003c/em\u003e Model data or logic.\u003c/p\u003e\n\u003cp\u003eUsing a stub server, as in option (1), just doesn\u0026rsquo;t solve enough problems and the problems it does solve aren\u0026rsquo;t solved cleanly. It can be good as a stop-gap if you have no other option and can serve other purposes (like cross-platform validation) but keeping it running on test machines is annoying and it will never deliver a clean solution across all dependencies.\u003c/p\u003e\n\u003cp\u003eAt the other end, putting app-specific logic into a service (3) is a mistake. Model-logic should be tested but services aren\u0026rsquo;t testable (they\u0026rsquo;re supposed to a dependency, not \u003cem\u003eyour\u003c/em\u003e code). And the goal of (3) – to improve the abstraction of server communication – can be done by wrapping your services \u003cem\u003einside\u003c/em\u003e your Model-layer, translating from simplified model concepts (like \u0026ldquo;feed\u0026rdquo;) into expanded model details (like the \u003ccode\u003eURLRequest\u003c/code\u003e and the decoding of its result).\u003c/p\u003e\n\u003ch2 id=\"using-the-services-layer-from-the-model\"\u003eUsing the Services-layer from the Model\u003c/h2\u003e\n\u003cp\u003eTaking option (2) and wrapping our services as minimally as possible makes including it in existing code relatively painless. We can bundle all of the apps services together in a single struct:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"nc\"\u003eServices\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003enetworkService\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eNetworkService\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ekeyValueService\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eKeyValueService\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWith an instance of this struct available in the \u003ccode\u003eModel\u003c/code\u003e, we can replace the following use of \u003ccode\u003eURLSession\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003etask\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eURLSession\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eshared\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edataTask\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewith\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eerror\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"cm\"\u003e/* ... */\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003etask\u003c/span\u003e\u003cspan class=\"p\"\u003e?.\u003c/span\u003e\u003cspan class=\"n\"\u003eresume\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003ewith\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003etask\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eservices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enetworkService\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efetchData\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewith\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eerror\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"cm\"\u003e/* ... */\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eReplacing \u003ccode\u003eUserDefaults\u003c/code\u003e is similar:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eUserDefaults\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estandard\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"kr\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e?\u003c/span\u003e \u003cspan class=\"n\"\u003eJSONEncoder\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"n\"\u003eencode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eisReadStatuses\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"n\"\u003eforKey\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;isReadStatuses\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003ebecomes:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eservices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ekeyValueService\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;isReadStatuses\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eBool\u003c/span\u003e\u003cspan class=\"p\"\u003e].\u003c/span\u003e\u003cspan class=\"kc\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eisReadStatuses\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt\u0026rsquo;s a drop-in replacement.\u003c/p\u003e\n\u003ch2 id=\"constructing-services\"\u003eConstructing services\u003c/h2\u003e\n\u003cp\u003eWhat I\u0026rsquo;ve ignored is exactly how and where the \u003ccode\u003eServices\u003c/code\u003e instance is constructed. The reason is that different developers choose different approaches.\u003c/p\u003e\n\u003cp\u003eHistorically, dependency injection frameworks were used to satisfy this problem. Services in a class would be specially named, typed or attributed and at runtime, the dependency injection framework would intercept the construction of services and set them to appropriate values (fully constructed production services or specialized testing versions).\u003c/p\u003e\n\u003cp\u003eI don\u0026rsquo;t recommend dependency injection frameworks. The purpose of service isolation should be to prevent side-effects but using a runtime framework to manipulate the contents of your classes \u003cem\u003eis\u003c/em\u003e a side-effect. It\u0026rsquo;s a conceptual conflict of interest and so completely unnecessary since manually managing services is so simple.\u003c/p\u003e\n\u003cp\u003eAll we need to do configure the Model-layer with appropriate services is to pass them as a parameter at construction.\u003c/p\u003e\n\u003cp\u003eIt\u0026rsquo;s weird when something so straightforward needs to be explained but I\u0026rsquo;ve seen developers go to extraordinary lengths to avoid passing a single parameter around. Here\u0026rsquo;s one approach commonly attempted to avoid passing services into the model – default constructed services:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003einit\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003eservices\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eServices\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eServices\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003enetworkService\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eURLSession\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eshared\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003ekeyValueService\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eUserDefaults\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estandard\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAt first, it seems highly practical because you don\u0026rsquo;t need to change how the \u003ccode\u003eModel\u003c/code\u003e class is constructed. The same \u003ccode\u003eModel()\u003c/code\u003e invocation will now construct the class with the production services and at test time, we can specify a different \u003ccode\u003eservices\u003c/code\u003e argument for specialized testing services.\u003c/p\u003e\n\u003cp\u003eThis approach works when \u003ccode\u003eModel\u003c/code\u003e is the only class in existence but as soon as you need other classes, it falls apart. It is not composable. If \u003ccode\u003eModel\u003c/code\u003e relies on class \u003ccode\u003eSubmodel\u003c/code\u003e, then \u003ccode\u003eModel\u003c/code\u003e must construct \u003ccode\u003eSubmodel\u003c/code\u003e with the same \u003ccode\u003eServices\u003c/code\u003e from \u003ccode\u003eModel\u003c/code\u003e, otherwise the two won\u0026rsquo;t agree. Letting \u003ccode\u003eSubmodel\u003c/code\u003e default-construct its own \u003ccode\u003eServices\u003c/code\u003e is a potential source of bugs and should never occur. The only robust solution requires \u003ccode\u003eServices\u003c/code\u003e are \u003cem\u003ealways\u003c/em\u003e passed into the type from outside.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s the construction of \u003ccode\u003eModel\u003c/code\u003e in the \u003ccode\u003eCwlFeedReaderApp\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003eStateObject\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003emodel\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eModel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eservices\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eServices\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI would be possible to construct all the services inline but the list of services in an app tends to grow, and precisely configuring reusable services can get complex, so it\u0026rsquo;s a good idea to move this work into a dedicated \u003ccode\u003einit\u003c/code\u003e function for the construction of production services:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eextension\u003c/span\u003e \u003cspan class=\"nc\"\u003eServices\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003einit\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kc\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"kd\"\u003einit\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enetworkService\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eURLSession\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eshared\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ekeyValueService\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eUserDefaults\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estandard\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"organize-into-modules-to-preserve-separation\"\u003eOrganize into modules to preserve separation\u003c/h2\u003e\n\u003cp\u003eNow that we\u0026rsquo;ve added protocols, some implementations and some construction code, there\u0026rsquo;s a little organizing to consider to maintain the clean isolation we wanted.\u003c/p\u003e\n\u003cp\u003eService \u003cem\u003eprotocols\u003c/em\u003e (\u003ccode\u003eNetworkService\u003c/code\u003e and \u003ccode\u003eKeyValueService\u003c/code\u003e) and the \u003ccode\u003eServices\u003c/code\u003e struct which holds them should go into the \u0026ldquo;Model\u0026rdquo; module (they are the Model-layer defining its own interface).\u003c/p\u003e\n\u003cp\u003eService \u003cem\u003eimplementations\u003c/em\u003e (\u003ccode\u003eextension URLSession: NetworkService\u003c/code\u003e and \u003ccode\u003eextension UserDefaults: KeyValueService\u003c/code\u003e) must remain separated from the Model-layer. They can go into a new module named \u0026ldquo;ServiceImplementations\u0026rdquo;. This new module should be imported into the \u003ccode\u003eCwlFeedReaderApp\u003c/code\u003e (since it constructs the \u003ccode\u003eServices\u003c/code\u003e for the \u003ccode\u003eModel\u003c/code\u003e at startup) but shouldn\u0026rsquo;t be imported anywhere else.\u003c/p\u003e\n\u003ch2 id=\"a-final-diagram\"\u003eA final diagram\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe complete MVC+Services version of the app is available from the \u003ca href=\"https://github.com/mattgallagher/CwlFeedReader/tree/part-four\"\u003epart-four branch of the CwlFeedReader repository\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eThe result is that Model-layer only references service protocols, never service implementations. This means the Model-layer no longer has any solid references to any other layer:\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/mvc_separate_services.svg\"\n    alt=\"MVC with separated Services-layer\" width=\"700px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eMVC with separated Services-layer\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eThe Controller\u0026rsquo;s role in pulling everything together is apparent – it has a solid line to every other layer. Fortunately, despite the many arrows, the Controller\u0026rsquo;s workload is minimal – the connection to the Services-layer is just a single function.\u003c/p\u003e\n\u003cp\u003eThe line from Services to the Model has gone from dotted to solid, indicating that Services now concretely references the Model-layer (to access the service protocol definitions). Eventually, moving these service protocols into a Definitions module (to avoid Services becoming coupled with Model details) might be helpful but we\u0026rsquo;ve added enough modules for the moment.\u003c/p\u003e\n\u003cp\u003eThe effect on code was minor, particularly if you consider only the impact on existing code: we\u0026rsquo;ve changed just 9 lines of the 127 line program.\u003c/p\u003e\n\u003cp\u003eThere are 46 new lines in new files (two new protocols and their implementations, the \u003ccode\u003eServices\u003c/code\u003e struct and its constructor) but exactly how to count these is debatable. The protocols and their implementations are reusable between apps. Like the \u003ccode\u003eWebView\u003c/code\u003e and \u003ccode\u003eIdentifiableError\u003c/code\u003e – that I ignore when counting lines of code – this new code could be considered external to the app. Additionally, service implementations grow only occasionally; we could increase the remaining program by an order of magnitude without needing additional service implementation code.\u003c/p\u003e\n\u003ch2 id=\"replacing-the-services-layer-at-testing-time\"\u003eReplacing the Services-layer at testing time\u003c/h2\u003e\n\u003cp\u003eNow the Model-layer is cleanly separated from the Services-layer but we haven\u0026rsquo;t used it for any change in behavior. For this, we need test-friendly implementations of each service and an easy way to construct these test services, instead of the production services.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s an implementation of \u003ccode\u003eNetworkService\u003c/code\u003e that pulls its data from a file resource instead of the network:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eMockNetworkService\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eNetworkService\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003efetchData\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewith\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eURLRequest\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ehandler\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003eescaping\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eData\u003c/span\u003e\u003cspan class=\"p\"\u003e?,\u003c/span\u003e \u003cspan class=\"n\"\u003eURLResponse\u003c/span\u003e\u003cspan class=\"p\"\u003e?,\u003c/span\u003e \u003cspan class=\"n\"\u003eError\u003c/span\u003e\u003cspan class=\"p\"\u003e?)\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"nb\"\u003eVoid\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eAnyCancellable\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003eguard\u003c/span\u003e \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003emethod\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ehttpMethod\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eurl\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003ehandler\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eURLError\u003c/span\u003e\u003cspan class=\"p\"\u003e(.\u003c/span\u003e\u003cspan class=\"n\"\u003ebadURL\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eAnyCancellable\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003eswitch\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e\\(\u003c/span\u003e\u003cspan class=\"n\"\u003emethod\u003c/span\u003e\u003cspan class=\"si\"\u003e)\u003c/span\u003e\u003cspan class=\"s\"\u003e \u003c/span\u003e\u003cspan class=\"si\"\u003e\\(\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eabsoluteString\u003c/span\u003e\u003cspan class=\"si\"\u003e)\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;GET https://www.cocoawithlove.com/feed.json\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ehandler\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emockFixture\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;feed.json\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esuccessResponse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003edefault\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ehandler\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enotFoundResponse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"n\"\u003eURLError\u003c/span\u003e\u003cspan class=\"p\"\u003e(.\u003c/span\u003e\u003cspan class=\"n\"\u003efileDoesNotExist\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eAnyCancellable\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eStubs and mocks\u003c/strong\u003e: I\u0026rsquo;ve referred to \u0026ldquo;stub servers\u0026rdquo; and now \u0026ldquo;mock services\u0026rdquo;. These names are conventions from projects that I\u0026rsquo;ve worked on. The terms \u0026ldquo;mocks\u0026rdquo; and \u0026ldquo;stubs\u0026rdquo; have specific definitions in testing (\u0026ldquo;stubs\u0026rdquo; record data and provide output data, whereas \u0026ldquo;mocks\u0026rdquo; record the order in which functions are invoked). Just to be confusing, both \u0026ldquo;stub servers\u0026rdquo; and \u0026ldquo;mock services\u0026rdquo; generally break this terminology because they don\u0026rsquo;t record and would more correctly be termed \u0026ldquo;fakes\u0026rdquo; (partially working, isolated implementations).\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eAs with running the app against the production services, we can construct \u003ccode\u003eServices\u003c/code\u003e for testing in a simplified manner:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003eextension\u003c/span\u003e \u003cspan class=\"nc\"\u003eServices\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003emock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eServices\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eServices\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enetworkService\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMockNetworkService\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"n\"\u003ekeyValueService\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMockKeyValueService\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNow that we have this \u003ccode\u003eServices.mock\u003c/code\u003e implementation, all we need to fix the failing \u003ccode\u003etestReload()\u003c/code\u003e from the start of the article is to replace the \u003ccode\u003elet model = Model()\u003c/code\u003e with \u003ccode\u003elet model = Model(services: Services.mock)\u003c/code\u003e and ensure we\u0026rsquo;re testing the result expected from our \u0026ldquo;feed.json\u0026rdquo; file.\u003c/p\u003e\n\u003cp\u003eFinally, we have reliable, fast tests that won\u0026rsquo;t break over time (unless we break our code – which is the breakage we \u003cem\u003ewant\u003c/em\u003e to detect).\u003c/p\u003e\n\u003cp\u003eWe can also run our SwiftUI previews without relying on the network by replacing the old preview code:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#if\u003c/span\u003e \u003cspan class=\"cp\"\u003eDEBUG\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"nc\"\u003eListView_Previews\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ePreviewProvider\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003epreviews\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esome\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003emodel\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eModel\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003eListView\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#endif\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003ewith the new \u003ccode\u003eServices.mock\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#if\u003c/span\u003e \u003cspan class=\"cp\"\u003eDEBUG\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eimport\u003c/span\u003e \u003cspan class=\"nc\"\u003eMockServiceImplementations\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"nc\"\u003eListView_Previews\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ePreviewProvider\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003epreviews\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esome\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003emodel\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eModel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eservices\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eServices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emock\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003eListView\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#endif\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis will let us pre-populate our SwiftUI previews however we desire, without needing the network to be available.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThese tests and previews are available from the \u003ca href=\"https://github.com/mattgallagher/CwlFeedReader/tree/part-four\"\u003epart-four branch of the CwlFeedReader repository\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"some-ugliness-due-to-missing-se-0273-functionality\"\u003eSome ugliness due to missing SE-0273 functionality\u003c/h2\u003e\n\u003cp\u003eAs with \u0026ldquo;ServiceImplementations\u0026rdquo;, the \u0026ldquo;MockServiceImplementations\u0026rdquo; should also be its own module (by now, you should be able to see why moving to a module-based build approach was so necessary in the previous article).\u003c/p\u003e\n\u003cp\u003eAt a minimum, the tests will need to import \u0026ldquo;MockServiceImplementations\u0026rdquo; but ideally, the app should be able to import it too, for use in SwiftUI previews. We don\u0026rsquo;t really want to include the mock services module in release builds (since mock fixtures take up a lot of space and we\u0026rsquo;d rather not include our internal testing details in the released product) so we need to pass \u003ccode\u003econdition: .when(configuration: .debug)\u003c/code\u003e to the Swift Package Manger for the \u0026ldquo;MockServiceImplementations\u0026rdquo; library.\u003c/p\u003e\n\u003cp\u003eUnfortunately, the \u003ccode\u003e.when(configuration:)\u003c/code\u003e condition I just mentioned is still waiting for the \u003ca href=\"https://github.com/apple/swift-evolution/blob/master/proposals/0273-swiftpm-conditional-target-dependencies.md\"\u003eSwift Package Manager developers to finish SE-0273\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eAhem\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eUntil then, we need some ugly workarounds to make \u0026ldquo;MockServiceImplementations\u0026rdquo; a debug-only dependency.\u003c/p\u003e\n\u003cp\u003eFirst, the \u0026ldquo;MockServiceImplementations\u0026rdquo; need to be built into a separate library. The Swift \u003ccode\u003ePackageDescription\u003c/code\u003e begins:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003epackage\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ePackage\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;CwlFeedReaderLib\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003eplatforms\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[.\u003c/span\u003e\u003cspan class=\"n\"\u003eiOS\u003c/span\u003e\u003cspan class=\"p\"\u003e(.\u003c/span\u003e\u003cspan class=\"n\"\u003ev14\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emacOS\u003c/span\u003e\u003cspan class=\"p\"\u003e(.\u003c/span\u003e\u003cspan class=\"n\"\u003ev11\u003c/span\u003e\u003cspan class=\"p\"\u003e)],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003eproducts\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elibrary\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;CwlFeedReaderLib\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003etargets\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Model\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;ServiceImplementations\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Toolbox\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;ViewToolbox\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elibrary\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;MockServiceImplementations\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003etargets\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;MockServiceImplementations\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThen in the \u0026ldquo;Build Phases\u0026rdquo; for the app target, we need to add \u0026ldquo;MockServiceImplementations\u0026rdquo; to the \u0026ldquo;Dependencies\u0026rdquo; but \u003cem\u003eDO NOT\u003c/em\u003e add it to the list of libraries or linked files. Instead, find the \u0026ldquo;Other Linker Flags\u0026rdquo; setting in the \u0026ldquo;Build Settings\u0026rdquo; and expand it to reveal \u0026ldquo;Debug\u0026rdquo;/\u0026ldquo;Release\u0026rdquo; and under \u0026ldquo;Debug\u0026rdquo; add \u003ccode\u003e${BUILT_PRODUCTS_DIR}/MockServiceImplementations.o\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eThat\u0026rsquo;s not \u003cem\u003etoo\u003c/em\u003e bad but our MockServiceImplementations module contains \u0026ldquo;fixtures\u0026rdquo; (fixed API responses in the form of JSON files) and we need to copy these into the app bundle for debug builds so we need a \u0026ldquo;Run script\u0026rdquo; build phase:\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eif [ \u0026#34;${CONFIGURATION}\u0026#34; = \u0026#34;Debug\u0026#34; ]; then\n  echo \u0026#34;Copying ${SCRIPT_INPUT_FILE_0}\u0026#34;\n  cp -Rf \u0026#34;${SCRIPT_INPUT_FILE_0}\u0026#34; \u0026#34;${SCRIPT_OUTPUT_FILE_0}\u0026#34;\nfi\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003ewith input file:\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e$(BUILT_PRODUCTS_DIR)/CwlFeedReaderLib_MockServiceImplementations.bundle\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eand output file\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/CwlFeedReaderLib_MockServiceImplementations.bundle\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eYuck.\u003c/p\u003e\n\u003cp\u003eOh and it\u0026rsquo;s not really possible to establish a dependency on the tree of contents inside a folder so if you change the contents of a fixture without changing something that alters the date on the folder itself, you may need to uncheck the \u0026ldquo;Based on dependency analysis\u0026rdquo; checkbox on the build script, briefly, and build again to pick up the changes.\u003c/p\u003e\n\u003cp\u003eDouble yuck.\u003c/p\u003e\n\u003cp\u003eHopefully, a complete SE-0273 implementation is on its way and all of this agony goes away. Until then, if anyone knows a better dependency-observing way to copy Debug-only resources please let me know. Dependency analysis omissions don\u0026rsquo;t cause problems very often but when they do, it\u0026rsquo;s when you\u0026rsquo;re debugging a test failure and you swear you\u0026rsquo;ve fixed the problem but the test is still failing and\u0026hellip; \u003cem\u003edammit\u003c/em\u003e I fixed the problem 20 minutes ago but it just wasn\u0026rsquo;t getting picked up by the build system.\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusion\u003c/h2\u003e\n\u003cp\u003eApp architecture is primarily about establishing roles throughout an app. By structuring our code according to roles, we aim to make code easier to write, easier to read and easier to manage. But these roles pertain only to the expression of logic and do not usually affect the output of that logic – so app architecture rarely affects the ultimate behavior of an app.\u003c/p\u003e\n\u003cp\u003eSwitching to a separated Services-layer is unusual as an architectural change because, while the app\u0026rsquo;s behavior is unchanged in production, the app can also run in a second mode where the behavior is quite different – controlled and testing-friendly. A Services-layer unlocks real changes, even if these changes are limited to debugging, testing and development on non-production builds of an app.\u003c/p\u003e\n\u003cp\u003eDespite the benefits, service separation is far from widespread in the iOS app developer community. I\u0026rsquo;ve joined multiple projects where a couple tests exist in code repositories but due to lack of service separation the tests are necessarily limited and largely useless for detecting regressions. I\u0026rsquo;ve also worked on codebases where network services were separated but tests still broke each month as they relied on the system clock to select \u0026ldquo;this month\u0026rsquo;s data\u0026rdquo;. Separating the Model from services – including more services than just the network service – is a skill I hope more developers learn.\u003c/p\u003e\n\u003cp\u003eEven though I\u0026rsquo;m strongly advocating for a separate Services-layer, total isolation of all side effects is not necessarily a goal. I identified \u003ccode\u003eDispatchQueue.main.async\u003c/code\u003e as a \u0026ldquo;service\u0026rdquo; in CwlFeedReader but I chose \u003cem\u003enot\u003c/em\u003e to separate it since it wouldn\u0026rsquo;t affect any of the tests I had written (my tests were already waiting for asychronous expectations). Service abstractions can be simple but they\u0026rsquo;re never free. I\u0026rsquo;d like to think that if a test broke due to asychronous behavior, I would take the effort to create a separate \u003ccode\u003eSchedulingService\u003c/code\u003e (see my previous \u003ca href=\"/blog/testing-actions-over-time.html\"\u003eTesting actions over time\u003c/a\u003e for possible approaches). Or would I try to avoid the problem another way? There isn\u0026rsquo;t a wrong answer, only a judgement call about what is right for your project.\u003c/p\u003e\n",
            "date_published": "2021-03-23T08:37:06+10:00"
        }, {
            "title": "App architecture basics in SwiftUI Part 3: Module-separated layers",
            "id": "https://www.cocoawithlove.com/blog/app-submodules.html",
            "url": "https://www.cocoawithlove.com/blog/app-submodules.html",
            "content_html": "\u003cp\u003e\u003ca href=\"/blog/swiftui-natural-pattern.html\"\u003eIn the previous article\u003c/a\u003e, I looked at how SwiftUI\u0026rsquo;s data-driven changes force a basic separation between Model and View. The separation is limited in scope, requiring only that there exist a state value or observable object that drives view updates.\u003c/p\u003e\n\u003cp\u003eIf a cleaner separation between Model and View is desired, then slicing an app into modules (Swift\u0026rsquo;s name for discrete libraries) is the best technical step you can make. Separating architectural layers (e.g. Model, View, and optionally others) into their own modules lets you establish rules about how to connect your layers, which types and properties should be accessible and keeps the overhead low on adding new layers.\u003c/p\u003e\n\u003cp\u003eIf you\u0026rsquo;ve ever wanted to improve your app\u0026rsquo;s architecture, simply moving your Model into a separate module is a step you should take before choosing to make any other architectural change. It can reveal problems you never realized you had and prevent future problems.\u003c/p\u003e\n\u003c!-- TOC --\u003e\n\u003ch2 id=\"the-problem\"\u003eThe problem\u003c/h2\u003e\n\u003cp\u003eWhen I wrote the \u003ca href=\"/blog/coding-through-iteration-and-integration.html\"\u003eCwlFeedReader app, in the first article in this series\u003c/a\u003e, the Xcode project navigator looked like this:\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/cwlfeedreader-project-before-separation.png\"\n    alt=\"Starting state of the CwlFeedReader app\" width=\"400px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eStarting state of the CwlFeedReader app\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eThe app is really just 5 files in a single folder. I\u0026rsquo;ve placed three \u0026ldquo;Utilities\u0026rdquo; into a separate folder but the primary reason for this is that these are components I didn\u0026rsquo;t really discuss during the article - they are dependencies and I don\u0026rsquo;t consider them part of the app.\u003c/p\u003e\n\u003cp\u003eIs there really any problem that we need to solve, here? The app is very small and simple; we don\u0026rsquo;t \u003cem\u003eneed\u003c/em\u003e to break it up.\u003c/p\u003e\n\u003cp\u003eThe reasons I want to break up this project fall into the following three categories.\u003c/p\u003e\n\u003ch3 id=\"1-interface-design\"\u003e1. Interface design\u003c/h3\u003e\n\u003cp\u003eEverything \u003ccode\u003einternal\u003c/code\u003e on \u003ccode\u003eModel\u003c/code\u003e is accessible by the View-layer types. The View could do ill-advised things like start its own downloads and store them directly on the \u003ccode\u003eURLSessionDataTask\u003c/code\u003e or update the \u003ccode\u003efeed\u003c/code\u003e following user changes and store it back in the same location – potentially conflicting with Model-driven updates.\u003c/p\u003e\n\u003cp\u003eThese are the sorts of obviously-bad short-cuts which get written when developers are rushed or aren\u0026rsquo;t thinking clearly about encapsulation. If problems like this aren\u0026rsquo;t cleaned up before committing code, then suddenly the app is full of surprising bugs. Yes, we can prevent this in a simple way by marking these properties as \u003ccode\u003eprivate\u003c/code\u003e but then we remove the ability to break the \u003ccode\u003eModel\u003c/code\u003e class across multiple files (because everything that needs to access \u003ccode\u003eprivate\u003c/code\u003e must stay in the same file).\u003c/p\u003e\n\u003cp\u003eWe either have a gigantic Model file with everything marked \u003ccode\u003eprivate\u003c/code\u003e or a leaky interface requiring constant vigilence.\u003c/p\u003e\n\u003cp\u003eBetter to have \u003ccode\u003eModel\u003c/code\u003e live in a separate module. If something is View-safe, we mark it \u003ccode\u003epublic\u003c/code\u003e. If something requires strong control, we can use \u003ccode\u003eprivate\u003c/code\u003e but \u003ccode\u003einternal\u003c/code\u003e access becomes truly useful – especially since \u003ccode\u003einternal\u003c/code\u003e is the default.\u003c/p\u003e\n\u003ch3 id=\"2-connection-rules\"\u003e2. Connection rules\u003c/h3\u003e\n\u003cp\u003eThe Model-layer should never reference any types from the View-layer; the connection arrow must point from View to Model. In a single module, we can\u0026rsquo;t prevent the Model from accessing View types. Once we move the Model to a separate module, we can let the build system enforce that for us.\u003c/p\u003e\n\u003ch3 id=\"3-unit-testing\"\u003e3. Unit testing\u003c/h3\u003e\n\u003cp\u003eThis is a technical consideration due to how Xcode projects work but everything about adding testing bundles to Xcode projects is higher friction than adding a testing target via the Swift Package Manager.\u003c/p\u003e\n\u003cp\u003eFor example, if I were to add a unit testing bundle to this project it would add 130 lines to the pbxproj file and Info.plist. That addition is nearly as big as the Swift code-size of the app. Even though most of those lines are autogenerated, Xcode expects us to maintain all of these settings. The appearance alone of that much boilerplate in a git merge request is mind numbing.\u003c/p\u003e\n\u003cp\u003eFurther, if we wanted unit tests for both macOS and iOS (remember, this is a multiplatform project) we would need to add two unit testing bundles. That\u0026rsquo;s 260 lines of Xcode project configuration.\u003c/p\u003e\n\u003cp\u003eThe final insult is that trying run unit tests attached directly to the app is practically useless because the entire app will run whenever the testing bundle is loaded. Having an entire app running in the background is likely to interfere with tests and even if it doesn\u0026rsquo;t it will certainly slow down the tests.\u003c/p\u003e\n\u003ch2 id=\"creating-an-inline-swift-package\"\u003eCreating an inline Swift Package\u003c/h2\u003e\n\u003cp\u003eHistorically, I would solve some of the above-mentioned problems by creating child frameworks. You would go to the \u0026ldquo;File → New → Target\u0026rdquo; menu and create a new iOS Framework. If, for some reason, your project \u003cem\u003ecan\u0026rsquo;t\u003c/em\u003e use the Swift Package Manager then this remains the best approach.\u003c/p\u003e\n\u003cp\u003eHowever, separate frameworks have many of the problems listed under \u0026ldquo;Unit Testing\u0026rdquo;, above: there\u0026rsquo;s xcodeproj bloat and you need a separate framework per platform.\u003c/p\u003e\n\u003cp\u003eFortunately, we have the Swift Package Manager and it offers a much better experience.\u003c/p\u003e\n\u003cp\u003eDocumentation on the Swift Package Manager in Xcode is almost entirely focussed on adding dependencies from outside your project or creating libraries to be shared outside your project. Neither of these are features that interest us, here.\u003c/p\u003e\n\u003cp\u003eYou don\u0026rsquo;t need to use the dependency management features of Swift Package Manager for it to be useful.\u003c/p\u003e\n\u003cp\u003eFor the CwlFeedReader app, I selected \u0026ldquo;File → New → Swift Package\u0026hellip;\u0026rdquo; from the menubar. When the \u0026ldquo;Save As:\u0026rdquo; dialog appears, I went to the same folder that contains the CwlFeedReader.xcodeproj file for the project, typed \u0026ldquo;CwlFeedReaderLib\u0026rdquo; in the \u0026ldquo;Save As:\u0026rdquo; name field and selected the \u0026ldquo;CwlFeedReader\u0026rdquo; project in the \u0026ldquo;Add to:\u0026rdquo; and \u0026ldquo;Group:\u0026rdquo; popup menus.\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/cwlfeedreader-create-package.png\"\n    alt=\"Creating an inline Swift Package\" width=\"750px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eCreating an inline Swift Package\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eAfter the package was created, I went to the CwlFeedReader app\u0026rsquo;s Target and added CwlFeedReaderLib under the list of Frameworks, Libraries and Embedded Content.\u003c/p\u003e\n\u003ch2 id=\"establishing-the-desired-structure\"\u003eEstablishing the desired structure\u003c/h2\u003e\n\u003cp\u003eNow, I can reorganize files in the project browser to place \u0026ldquo;Model.swift\u0026rdquo; under \u0026ldquo;CwlFeedReaderLib/Sources/Model\u0026rdquo;, the \u0026ldquo;IdentifiableError.swift\u0026rdquo; under \u0026ldquo;CwlFeedReaderLib/Sources/Toolbox\u0026rdquo; and \u0026ldquo;View+PlatformCompatibility.swift\u0026rdquo; and \u0026ldquo;WebView.swift\u0026rdquo; under \u0026ldquo;CwlFeedReaderLib/Sources/ViewToolbox\u0026rdquo;.\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/cwlfeedreader-project-after-separation.png\"\n    alt=\"Desired state of the CwlFeedReader app\" width=\"400px\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eDesired state of the CwlFeedReader app\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eUpdating the Package.swift file to include these locations gives the following:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// swift-tools-version:5.3\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eimport\u003c/span\u003e \u003cspan class=\"nc\"\u003ePackageDescription\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003epackage\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ePackage\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;CwlFeedReaderLib\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003eplatforms\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[.\u003c/span\u003e\u003cspan class=\"n\"\u003eiOS\u003c/span\u003e\u003cspan class=\"p\"\u003e(.\u003c/span\u003e\u003cspan class=\"n\"\u003ev14\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emacOS\u003c/span\u003e\u003cspan class=\"p\"\u003e(.\u003c/span\u003e\u003cspan class=\"n\"\u003ev11\u003c/span\u003e\u003cspan class=\"p\"\u003e)],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003eproducts\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elibrary\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;CwlFeedReaderLib\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003etargets\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Model\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Toolbox\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;ViewToolbox\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003etargets\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etarget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Model\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003edependencies\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Toolbox\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etarget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Toolbox\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003edependencies\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etarget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;ViewToolbox\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003edependencies\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Toolbox\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNotice that I\u0026rsquo;m already using the Swift Package Manager to create a few addditional modules with \u0026ldquo;Toolbox\u0026rdquo; and \u0026ldquo;ViewToolbox\u0026rdquo; keeping reusable dependencies outside the \u0026ldquo;Model\u0026rdquo; and \u0026ldquo;View\u0026rdquo; components.\u003c/p\u003e\n\u003cp\u003eThe reason for this type of separation is to identify files that should contain no app-specific logic – it\u0026rsquo;s telling any reader of the code what to expect and promoting code re-use. Neither of these points is essential but since the effort is low, they are both good to promote.\u003c/p\u003e\n\u003ch2 id=\"imports-and-access-modifiers\"\u003eImports and access modifiers\u003c/h2\u003e\n\u003cp\u003eOnce we\u0026rsquo;ve actually separated our Model and Toolbox dependencies, everything will break.\u003c/p\u003e\n\u003cp\u003eThe \u0026ldquo;Model.swift\u0026rdquo; file will need to \u003ccode\u003eimport Toolbox\u003c/code\u003e. All four files remaining in the App folder will need to \u003ccode\u003eimport Model\u003c/code\u003e and \u0026ldquo;DetailView.swift\u0026rdquo; will need to \u003ccode\u003eimport ViewToolbox\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eOnce that\u0026rsquo;s done, everything the View needs to access in the Model module will need to be marked \u003ccode\u003epublic\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eI won\u0026rsquo;t list everything here but you can examine the diffs in the final commit on the \u003ca href=\"https://github.com/mattgallagher/CwlFeedReader/commits/part-three\"\u003e\u0026ldquo;Part 3\u0026rdquo; branch of the CwlFeedReader code repository\u003c/a\u003e.\u003c/p\u003e\n\u003ch2 id=\"submodules\"\u003eSubmodules?\u003c/h2\u003e\n\u003cp\u003eI\u0026rsquo;ve taken an Xcode project in a single repository and broken it into multiple internal modules.\u003c/p\u003e\n\u003cp\u003eSubmodules is a proposed language feature that would be able to achieve a similar effect without the Swift Package Manager. All the code would reside in a single Swift module but you would be able to \u0026ldquo;namespace\u0026rdquo; some sections as residing in a separate submodule. \u003ca href=\"https://forums.swift.org/t/proposal-discussion-modular-swift/5271\"\u003eDraft proposals exist to add them to the language\u003c/a\u003e. However, submodules has never moved past the draft proposal stage and it\u0026rsquo;s not clearly going anywhere.\u003c/p\u003e\n\u003cp\u003eIt\u0026rsquo;s possible submodules will never be implemented but that doesn\u0026rsquo;t worry me. I usually dislike touching build-systems but I think this is one problem that might be more cleanly solved at the build-system level than the language level. Build-system enforced isolation makes accidental violations of encapsulation harder and therefore separation is cleaner.\u003c/p\u003e\n\u003cp\u003eHowever, potential changes to either Swift or Xcode do make me wonder what the lifetime of this article will be. The advice to \u0026ldquo;isolate your layers\u0026rdquo; will never be wrong but the implementation could be superceded.\u003c/p\u003e\n\u003cp\u003eSubmodules could be implemented in Swift and introduce confusion about whether they\u0026rsquo;re better or worse for the job.\u003c/p\u003e\n\u003cp\u003eThe xcodeproj format in Xcode could be replaced by Swift Packages with apps created as a single Swift Package with the basic \u0026ldquo;Model\u0026rdquo; and \u0026ldquo;View\u0026rdquo; modules immediately created by the \u0026ldquo;New Project\u0026rdquo; app template. Of course, I\u0026rsquo;ve been hoping for xcodeproj deprecation since 2015; I have no reason to believe it will change in the next couple years.\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusion\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe full code for this article is available from the \u003ca href=\"https://github.com/mattgallagher/CwlFeedReader/tree/part-three\"\u003epart-three branch of the CwlFeedReader repository.\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eAs with most of this \u0026ldquo;App architecture basics in SwiftUI\u0026rdquo; series, I\u0026rsquo;m not doing anything revolutionary, here. I\u0026rsquo;ve spent about half this article giving a tutorial on how to add a Swift Package to an Xcode project.\u003c/p\u003e\n\u003cp\u003eThe bigger message that I want to convey is that layer-isolation requires a tiny amount of up-front work but it yields clear benefits. When applying this type of separation to established projects, it\u0026rsquo;s common to discover large numbers of bugs and undesired behavior due to Model and View accessing each others \u003ccode\u003einternal\u003c/code\u003e details. It\u0026rsquo;s a change that informs us about our own laziness.\u003c/p\u003e\n\u003cp\u003eIt\u0026rsquo;s also a change that has productivity benefits moving forward. New modules are now trivial to add. Remember the 130 lines times 2 platforms increase when adding a testing target? Now it\u0026rsquo;s 4 lines, total, without the possibility of the app running and interfering in the background.\u003c/p\u003e\n\u003ch3 id=\"looking-forward\"\u003eLooking forward\u0026hellip;\u003c/h3\u003e\n\u003cp\u003eWhile I mentioned \u0026ldquo;tests\u0026rdquo; in this article, I never wrote any. That will change in the next article as I highlight the most important architectural inclusion that this app is still missing.\u003c/p\u003e\n",
            "date_published": "2021-02-12T08:37:06+10:00"
        }, {
            "title": "App architecture basics in SwiftUI, Part 2: SwiftUI's natural pattern",
            "id": "https://www.cocoawithlove.com/blog/swiftui-natural-pattern.html",
            "url": "https://www.cocoawithlove.com/blog/swiftui-natural-pattern.html",
            "content_html": "\u003cp\u003eIn the \u003ca href=\"/blog/coding-through-iteration-and-integration.html\"\u003eprevious article\u003c/a\u003e, I wrote a simple SwiftUI app. During the writing, I deliberately kept the code simple – writing code only when needed to satisfy user-facing goals. I want to take a closer look at the application architecture that naturally emerges in SwiftUI when following this kind of functionally minimalist approach.\u003c/p\u003e\n\u003cp\u003ePerform a web search for \u0026ldquo;SwiftUI pattern\u0026rdquo; and you\u0026rsquo;ll find numerous discussions of SwiftUI that wonder if its use of model-bindings make it a form of Model-View-ViewModel (MVVM) or if its use of immutable views and view-state make it redux or Elm-like. While SwiftUI does include these components, the reality is much simpler: SwiftUI\u0026rsquo;s natural pattern is a form of Model-View-Controller (MVC), although very different from UIKit MVC.\u003c/p\u003e\n\u003cp\u003eIn any case, the precise naming is less important than the components that work together to form the architecture. In this article, I\u0026rsquo;ll identify the different application architectural roles fulfilled by components of SwiftUI and talk about how they work together to form the overall pattern.\u003c/p\u003e\n\u003c!-- TOC --\u003e\n\u003ch2 id=\"changes-triggered-by-model-data\"\u003eChanges triggered by model data\u003c/h2\u003e\n\u003cp\u003eSwiftUI introduces many differences compared to macOS AppKit or iOS UIKit but a single difference has the biggest effect on application architecture: how changes are triggered.\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s start by looking at how UIKit triggers changes. In UIKit, you construct a tree of view-objects. These objects are reference types and you can hold onto those references to trigger a change.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003ewindow\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eUIWindow\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ewindow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emakeKeyAndVisible\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eDispatchQueue\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003easyncAfter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edeadline\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eseconds\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003elabel\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eUILabel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eframe\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ewindow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eframe\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003elabel\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etext\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Boo!\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003ewindow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eaddSubview\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elabel\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHere, I\u0026rsquo;ve created a view (\u003ccode\u003ewindow\u003c/code\u003e) and I\u0026rsquo;ve changed its content after 1 second by holding onto the \u003ccode\u003ewindow\u003c/code\u003e reference and adding a new subview which triggers the update.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s how it might look in SwiftUI:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"nc\"\u003eContentView\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003eState\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003eshowSubview\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eBool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esome\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003eZStack\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eColor\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ewhite\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eonAppear\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003eDispatchQueue\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003easyncAfter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edeadline\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eseconds\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                  \u003cspan class=\"kc\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eshowSubview\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eshowSubview\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Boo!\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOh look, I\u0026rsquo;ve managed to find one of the few scenarios where UIKit is more syntactically efficient than SwiftUI.\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s ignore that because there\u0026rsquo;s a more important difference.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eIn UIKit, I changed the \u003ccode\u003esubviews\u003c/code\u003e of the window – a \u003cstrong\u003eUIKit\u003c/strong\u003e property – and that triggered the view update.\u003c/li\u003e\n\u003cli\u003eIn SwiftUI, I changed \u003ccode\u003eshowSubview\u003c/code\u003e – my \u003cstrong\u003eown\u003c/strong\u003e property –  and that triggered the view update.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eWhat difference does that make?\u003c/p\u003e\n\u003cp\u003eIt means my own values are the source of truth for the View, not the state of a view-tree. I must create data which represents the current view-state. This is a Model (or a Model-interface*) and it is mandatory in SwiftUI whereas in UIKit is was \u003ca href=\"/blog/worst-possible-application.html\"\u003epossible to create an app without one\u003c/a\u003e.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e* A Model is a repository of domain \u003cem\u003elogic\u003c/em\u003e, not \u003cem\u003edata\u003c/em\u003e. Data and definitions exist in the Model only to communicate (interface) with other layers. Please don\u0026rsquo;t look at this \u003ccode\u003e@State var showSubview: Bool\u003c/code\u003e and think that\u0026rsquo;s all a Model should do. Ideally, the timing details and the assignment would be expressed inside the Model but I\u0026rsquo;m trying to keep things simple.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"construction-role\"\u003eConstruction role\u003c/h2\u003e\n\u003cp\u003eIn UIKit, lots of components can, and do, fulfill parts of the view-construction role. Some of them are scattered across storyboards, \u003ccode\u003eUIViewController\u003c/code\u003e lifecycle methods, methods in \u003ccode\u003eUITableViewDelegate\u003c/code\u003e (among others) and \u003ccode\u003eUIView\u003c/code\u003es themselves. Nominally, construction is a \u003ccode\u003eUIViewController\u003c/code\u003e responsibility but implementation-wise, it\u0026rsquo;s a bit of a mess.\u003c/p\u003e\n\u003cp\u003eIn SwiftUI, it should be obvious that \u003ccode\u003eView\u003c/code\u003es construct themselves however, it\u0026rsquo;s not as simple as that. While SwiftUI doesn\u0026rsquo;t have a direct counterpart to \u003ccode\u003eUIViewController\u003c/code\u003e, there are some \u003ccode\u003eView\u003c/code\u003es that focus on construction and some that focus on layout and drawing.\u003c/p\u003e\n\u003cp\u003eTo explain how this works, I want to look a little closer at the two different kinds of \u003ccode\u003eView\u003c/code\u003e:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003ebuilt-in views\u003c/strong\u003e return \u003ccode\u003eNever\u003c/code\u003e from their \u003ccode\u003ebody\u003c/code\u003e function\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ecompositional views\u003c/strong\u003e return another \u003ccode\u003eView\u003c/code\u003e from their \u003ccode\u003ebody\u003c/code\u003e function because they really just serve to configure and aggregate underlying views and may hold observable state\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eYou could break each of these into sub categories but this is enough for the purposes of this article.\u003c/p\u003e\n\u003cp\u003eWe can\u0026rsquo;t write the first kind of \u003ccode\u003eView\u003c/code\u003e ourselves because they are effectively the rendering primitives in SwiftUI (like CoreGraphics functions in UIKit). They draw the text, fill and stroke the beziers and position elements.\u003c/p\u003e\n\u003cp\u003eBy contrast, all of the views we actually write in SwiftUI primarily serve to group, layout, bind to data and construct these built-in views.\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s look at the default Xcode template for the \u003ccode\u003eContentView\u003c/code\u003e, used as the placeholder View in the code for the previous article:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"nc\"\u003eContentView\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esome\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Hello, world!\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epadding\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn this example, \u003ccode\u003eContentView\u003c/code\u003e is a compositional view but \u003ccode\u003eText\u003c/code\u003e and the \u003ccode\u003eModifiedLayout\u003c/code\u003e produced by the \u003ccode\u003e.padding()\u003c/code\u003e call are built-in views and are specially handled by the SwiftUI system.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003eavailable\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eiOS\u003c/span\u003e \u003cspan class=\"mf\"\u003e13.0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003emacOS\u003c/span\u003e \u003cspan class=\"mf\"\u003e10.15\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etvOS\u003c/span\u003e \u003cspan class=\"mf\"\u003e13.0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ewatchOS\u003c/span\u003e \u003cspan class=\"mf\"\u003e6.0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eextension\u003c/span\u003e \u003cspan class=\"nc\"\u003eText\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003etypealias\u003c/span\u003e \u003cspan class=\"n\"\u003eBody\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eNever\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"centeraside\"\u003eIt\u0026rsquo;s not possible to have a \u003ccode\u003eBody = Never\u003c/code\u003e for our own views. It is feature of built-in views, only.\u003c/div\u003e\n\u003cp\u003eIt should be clear that compositional views (particularly the higher level ones) are the scene constructors in SwiftUI and the built-in views fulfilling the rendering role.\u003c/p\u003e\n\u003ch2 id=\"event-handling-role\"\u003eEvent handling role\u003c/h2\u003e\n\u003cp\u003eFor view interaction to work, there must be code that receives \u0026ldquo;events\u0026rdquo; and can trigger code in other parts of the system.\u003c/p\u003e\n\u003cp\u003eIn SwiftUI this is handled by \u003ccode\u003eaction\u003c/code\u003e closures, sometimes as part of a visible \u003ccode\u003eView\u003c/code\u003e (like a \u003ccode\u003eButton\u003c/code\u003e) and sometimes as a standalone \u003ccode\u003eon\u003c/code\u003e event-handler.\u003c/p\u003e\n\u003cp\u003eThe \u003ccode\u003e.onAppear\u003c/code\u003e transformation appends an event-handling view – another SwiftUI built-in view – which calls a closure that captures our view and can access its \u003ccode\u003e@State\u003c/code\u003e variable.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"nc\"\u003eContentView\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003eState\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003eshowSubview\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eBool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esome\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003eZStack\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eColor\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ewhite\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eonAppear\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003eDispatchQueue\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003easyncAfter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edeadline\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eseconds\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                  \u003cspan class=\"kc\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eshowSubview\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"cm\"\u003e/* ... other code omitted ... */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn some respects, this is similar to the Target-Action pattern in UIKit. However, the immutable nature of SwiftUI views encourages the notion that destination of an event should be one of the mutable SwiftUI model properties, like the \u003ccode\u003e@State\u003c/code\u003e in this example.\u003c/p\u003e\n\u003cp\u003eThis enforces the notion that events come from view-hierarchy elements and the interaction is sent directly to the Model – an improvement compared to Target-Action in UIKit which typically sent interactions via the Controller-layer.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eI\u0026rsquo;m making the claim that all \u003ccode\u003e@State\u003c/code\u003e properties in SwiftUI (like the \u003ccode\u003eshowSubview\u003c/code\u003e property) are Model-layer entities since they are decoupled, observable and directly drive View-updates. SwiftUI\u0026rsquo;s View-state is similar to the Elm architecture where View-state is just a kind of Model-state, compared to UIKit where View-state was an unobserved side-effect.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"data-binding-role\"\u003eData binding role\u003c/h2\u003e\n\u003cp\u003eWhere the event-handling role is concerned with incoming changes triggered by the View-layer, data-binding is concerned with outgoing changes from the Model-layer.\u003c/p\u003e\n\u003cp\u003eUIKit traditionally (prior to Combine) had no data-binding beyond key-value observing and \u003ccode\u003eNotificationCenter\u003c/code\u003e. Cocoa on macOS had Cocoa Bindings, which could be powerful but were difficult to extend, opaque to the reader and have been effectively deprecated for a decade.\u003c/p\u003e\n\u003cp\u003eSwiftUI, on the other hand, is built around data-bindings – tracking which \u003ccode\u003eView\u003c/code\u003es are dependent on which \u003ccode\u003eDynamicProperty\u003c/code\u003e (\u003ccode\u003e@State\u003c/code\u003e, \u003ccode\u003e@ObservedObject\u003c/code\u003e, et al) state properties and making sure views are re-generated when the state changes. While the exact machinery inside these property wrapper attributes is opaque to us, we don\u0026rsquo;t typically need to worry – just use the state and the binding is established automatically.\u003c/p\u003e\n\u003cp\u003eOnce again, this is a role assigned to our compositional views, since they are the ones which host Model properties like \u003ccode\u003e@ObservedObject\u003c/code\u003e and \u003ccode\u003e@State\u003c/code\u003e properties.\u003c/p\u003e\n\u003cp\u003eIn the \u003ccode\u003eshowSubview\u003c/code\u003e example, above, the \u003ccode\u003eContentView\u003c/code\u003e is responsible for establishing a connection between the \u003ccode\u003eshowSubview\u003c/code\u003e View-state and the appearance of the \u003ccode\u003eText\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"nc\"\u003eContentView\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003eState\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003eshowSubview\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eBool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esome\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003eZStack\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"cm\"\u003e/* ... other code omitted ... */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eshowSubview\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Boo!\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"taken-together-what-does-all-this-mean\"\u003eTaken together, what does all this mean?\u003c/h2\u003e\n\u003cp\u003eWe have:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eA Model, enforced by SwiftUI\u0026rsquo;s approach to change management\u003c/li\u003e\n\u003cli\u003eBuilt-in views handle all the drawing\u003c/li\u003e\n\u003cli\u003eComposite views handle construction\u003c/li\u003e\n\u003cli\u003eComposite views establish Model-to-View bindings\u003c/li\u003e\n\u003cli\u003eEvent views route events using Model references provided by composite views\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eWith separate Model and View roles, plus an amalgam of Construction, Event and Binding roles around our composite views, SwiftUI is really just Model-View-Controller, with the composite views acting as the Controller.\u003c/p\u003e\n\u003cp\u003eIf you pay attention only to the names \u003ccode\u003eState\u003c/code\u003e and \u003ccode\u003eView\u003c/code\u003e, you could be forgiven for thinking that SwiftUI\u0026rsquo;s natural pattern is Model-View but the roles performed by composite views clearly cover the responsibilities of the Controller in MVC, even if SwiftUI makes the syntactic overhead so low that you could overlook it.\u003c/p\u003e\n\u003cp\u003eThe boundary that I\u0026rsquo;ve drawn between a composite and built-in view is blurred (many built-in views may be internally composite and many of our own views might be static and behavior-less). However, this blurry boundary is not a new complication in an MVC pattern. The UIKit \u003ccode\u003eUIViewController\u003c/code\u003e could perform view styling and didn\u0026rsquo;t need to have data or behaviors; and container \u003ccode\u003eUIView\u003c/code\u003es would often function as controllers, applying data connections and establishing links between events and the model.\u003c/p\u003e\n\u003ch2 id=\"why-is-the-pattern-not-mvvm\"\u003eWhy is the pattern not MVVM?\u003c/h2\u003e\n\u003cp\u003eI want to address MVVM for two reasons:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eI\u0026rsquo;ve seen commenters make the mistake of claiming that SwiftUI is, or uses, MVVM.\u003c/li\u003e\n\u003cli\u003eI will talk about ViewModels in a later article of this series and I want the distinction to be clear.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eIn general MVVM programs should aim to follow these two principles:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eevery data-driven property in the View should be driven by a unique Model property;\u003c/li\u003e\n\u003cli\u003epresentation-logic (like sorting or string formatting) should be applied in the Model before the property is exposed to the View.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eLets focus on two lines from \u003ccode\u003edetailView\u003c/code\u003e in the code for the \u003ca href=\"/blog/coding-through-iteration-and-integration.html\"\u003eCwlFeedReader app in the previous article\u003c/a\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eisRead\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eisReadStatuses\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003earticle\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e??\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eand\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eisRead\u003c/span\u003e \u003cspan class=\"p\"\u003e?\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Mark as unread\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Mark as read\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThese lines clearly show:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003ethe \u003ccode\u003edetailView\u003c/code\u003e reading \u003ccode\u003emodel.isReadStatuses\u003c/code\u003e (a property used in multiple parts of the program and definitely not unique to this button label in the \u003ccode\u003edetailView\u003c/code\u003e)\u003c/li\u003e\n\u003cli\u003ethe code performs two transformations on the value: \u003ccode\u003ex ?? false\u003c/code\u003e to give a default value when \u003ccode\u003enil\u003c/code\u003e and then \u003ccode\u003eisRead ? a : b\u003c/code\u003e to transform the \u003ccode\u003eBool\u003c/code\u003e into a display \u003ccode\u003eString\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eEach of these are violations of the principles that an MVVM program should aim to follow.\u003c/p\u003e\n\u003cp\u003eYou might point out – particularly with the second point – that this is some very minor presentation-logic. And its true that most implementations of MVVM permit \u003cem\u003esome\u003c/em\u003e transformations in the View – often including this type of boolean logic. But I\u0026rsquo;m using this example to highlight in a simple way that SwiftUI allows for an \u003cem\u003eunbounded\u003c/em\u003e amount of logic in the View and ultimately this is contrary to MVVM which tries to put all presentation logic in the ViewModel.\u003c/p\u003e\n\u003cp\u003eIf SwiftUI wanted to force an MVVM pattern, it could have made \u003ccode\u003e@Published\u003c/code\u003e properties a single-use, one-to-one binding, to address the first requirement. Upcoming Swift \u0026ldquo;Ownership\u0026rdquo; features like consumable and move-only types would make this possible. They were not used.\u003c/p\u003e\n\u003cp\u003eSimilarly, SwiftUI could have taken steps to prevent or limit arbitrary logic in the \u003ccode\u003ebody\u003c/code\u003e of \u003ccode\u003eView\u003c/code\u003e. In .NET, the domain-specific language XAML is used to achieve this goal. SwiftUI could have introduced a subset of Swift that permitted only combining operators. SwiftUI did introduce a domain-specific language with \u003ccode\u003e@ViewBuilder\u003c/code\u003e but it is focussed on simplifying composition, not limiting logic.\u003c/p\u003e\n\u003cp\u003eSwiftUI didn\u0026rsquo;t take any of these steps so it\u0026rsquo;s clear that SwiftUI is not MVVM.\u003c/p\u003e\n\u003ch2 id=\"why-do-people-claim-swiftui-is-mvvm\"\u003eWhy do people claim SwiftUI is MVVM?\u003c/h2\u003e\n\u003cp\u003eIn researching this article, I found multiple articles and forum responses claiming that SwiftUI is or uses MVVM. Why would people jump to this conclusion?\u003c/p\u003e\n\u003ch3 id=\"is-it-the-model-data-bindings\"\u003eIs it the Model-data bindings?\u003c/h3\u003e\n\u003cp\u003eThe \u003ccode\u003e@Published\u003c/code\u003e and \u003ccode\u003e@ObservedObject\u003c/code\u003e attributes used in SwiftUI do make observing data changes much easier – especially since some form of data observing is \u003cem\u003erequired\u003c/em\u003e to perform view updates. Good data observing can significantly improve MVVM where you might have dozens of bindings between each view-model and view.\u003c/p\u003e\n\u003cp\u003eBut the reality is that good MVC apps also use data observing – it is not unique to MVVM. UIKit offered some out-of-the-box tools and most developers supplemented this with their own additions.\u003c/p\u003e\n\u003cp\u003eThe \u003ccode\u003e@Published\u003c/code\u003e attributes make no effort to limit observing to one-to-one scenarios so really, this isn\u0026rsquo;t a boost to MVVM as much as it is a rejection of programming without data observing.\u003c/p\u003e\n\u003ch3 id=\"is-it-combine\"\u003eIs it Combine?\u003c/h3\u003e\n\u003cp\u003eApple introduced the Combine framework alongside SwiftUI. Combine is a reactive programming framework and reactive programming frameworks have been commonly used on iOS/macOS to implement bindings as part of an MVVM pattern.\u003c/p\u003e\n\u003cp\u003eThe incorrect implication here, is that SwiftUI implies Combine implies MVVM.\u003c/p\u003e\n\u003cp\u003eI stated above that Model-data bindings (even reactive programming bindings) don\u0026rsquo;t imply MVVM but the other implication is also incorrect: just because you\u0026rsquo;re using SwiftUI doesn\u0026rsquo;t mean you have to use Combine. SwiftUI offers good \u003cem\u003einteroperability\u003c/em\u003e with Combine but any reactive programming when using SwiftUI is a totally optional addition.\u003c/p\u003e\n\u003cp\u003eYes, there are some parts of the SwiftUI data flow that \u003cem\u003einternally\u003c/em\u003e use Combine (most notably \u003ccode\u003e@Published\u003c/code\u003e and \u003ccode\u003eObservableObject\u003c/code\u003e) but these are internal implementation details. My \u003ca href=\"/blog/coding-through-iteration-and-integration.html\"\u003eCwlFeedReader in the previous article\u003c/a\u003e did not import Combine or use reactive programming.\u003c/p\u003e\n\u003ch3 id=\"is-it-just-because-uiviewcontroller-is-gone\"\u003eIs it just because UIViewController is gone?\u003c/h3\u003e\n\u003cdiv class=\"aside\"\u003eThe original Smalltalk definition of MVC had the Controller filling a role closer to \u0026ldquo;event handling role\u0026rdquo; than anything else in this article. As I discussed in \u003ca href=\"/blog/mvc-and-cocoa.html\"\u003eLooking at Model-View-Controller in Cocoa\u003c/a\u003e the precise definition of MVC is platform specific, making a precise declaration of whether or not something is MVC a little difficult.\u003c/div\u003e\n\u003cp\u003eTo be honest, I think this might be close to reality.\u003c/p\u003e\n\u003cp\u003eWhen MVC in UIKit was discussed, people were not discussing MVC in general. MVC on UIKit meant \u003ccode\u003eUIViewController\u003c/code\u003e and the way it dominated iOS development. \u003ccode\u003eUIViewController\u003c/code\u003e was the screens, the storyboards, the delegate and 80% of the rest.\u003c/p\u003e\n\u003cp\u003eTo many UIKit developers, \u003ccode\u003eUIViewController\u003c/code\u003e defined the Controller in Model-View-Controller. Without it, what do we have?\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusion\u003c/h2\u003e\n\u003cp\u003eI think SwiftUI\u0026rsquo;s pattern is best described as Model-View-Controller. The Controller role is downplayed and mixed with the View, but that\u0026rsquo;s common in MVC+Bindings approaches like SwiftUI. However, top-level \u0026ldquo;composite\u0026rdquo; views retain the contruction, binding and event routing roles that the Controller in UIKit fulfilled.\u003c/p\u003e\n\u003cp\u003eEven if we call SwiftUI\u0026rsquo;s pattern \u0026ldquo;The SwiftUI pattern\u0026rdquo; to distinguish from UIKit\u0026rsquo;s MVC, I think the more important point is to understand that SwiftUI includes:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eModels and view-state\u003c/li\u003e\n\u003cli\u003eRender-primitive-like built-in views\u003c/li\u003e\n\u003cli\u003eConstruction\u003c/li\u003e\n\u003cli\u003eEvent handling and model-interactions\u003c/li\u003e\n\u003cli\u003eData observing and binding\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eand even though all of these can appear in a 10 line SwiftUI \u003ccode\u003eView\u003c/code\u003e, they are distinct roles and you should be able to identify them separately.\u003c/p\u003e\n\u003cp\u003eAs a developer who has focussed heavily on application architecture, I\u0026rsquo;m struck by how straightforward the architecture around the View is in SwiftUI. In AppKit/UIKit, there was a continuous question around how to set up observing, where to perform View construction and configuration and whether to bother with the overhead of bindings and event handling. SwiftUI doesn\u0026rsquo;t eliminate bad architecture but in my experience so far, a good architecture feels much more natural than it ever did in UIKit.\u003c/p\u003e\n\u003ch3 id=\"looking-forward\"\u003eLooking forward\u0026hellip;\u003c/h3\u003e\n\u003cp\u003eIn SwiftUI there are fewer application architecture problems to solve around the View-layer and View-bindings but there are still plenty of places for improvement. In the next couple articles, I\u0026rsquo;m going to look at some architectural patterns to improve the Model-layer.\u003c/p\u003e\n",
            "date_published": "2021-01-19T10:32:11+11:00"
        }, {
            "title": "App architecture basics in SwiftUI, Part 1: Coding through iteration and integration",
            "id": "https://www.cocoawithlove.com/blog/coding-through-iteration-and-integration.html",
            "url": "https://www.cocoawithlove.com/blog/coding-through-iteration-and-integration.html",
            "content_html": "\u003cp\u003eIn this series of articles, I\u0026rsquo;ll look at fundamental app architectural concepts and how they apply to an app written in SwiftUI.\u003c/p\u003e\n\u003cp\u003eTo begin the series, I want to start with something small: a JSON feed reader app in SwiftUI. I want to focus on the order of the steps, not the code itself and answer a common question: which should you write first, View-code or Model-code?\u003c/p\u003e\n\u003cp\u003eThe short answer is \u0026ldquo;both\u0026rdquo; (or \u0026ldquo;neither\u0026rdquo;) but properly answering the question offers a segue into a more important question: how can I make reliable progress as a programmer when I\u0026rsquo;m uncertain about what I\u0026rsquo;m doing or the blocks of work seem too big to manage?\u003c/p\u003e\n\u003c!-- TOC --\u003e\n\u003ch2 id=\"where-to-start\"\u003eWhere to start?\u003c/h2\u003e\n\u003cp\u003eThe app I want to write is a JSON feed reader for the Cocoa with Love JSON feed. The app will be a macOS/iOS SwiftUI app that looks like this:\u003c/p\u003e\n\u003cfigure\u003e\u003cimg src=\"/assets/blog/json_feed_reader.png\"\n    alt=\"End goal for the CwlFeedReader app\"\u003e\u003cfigcaption\u003e\n      \u003cp\u003eEnd goal for the CwlFeedReader app\u003c/p\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003eYou have to start somewhere. Where should you begin? Model? View?\u003c/p\u003e\n\u003cp\u003eApps are made of multiple components (data, models, views) and these components all depend on each other, leading to a chicken-and-egg problem of which component should be written first.\u003c/p\u003e\n\u003cp\u003eWhere you start doesn\u0026rsquo;t matter. What matters is that you work in small steps (\u003cstrong\u003eiterate\u003c/strong\u003e) and ensure that changes are immediately extended out into every major component in the app (\u003cstrong\u003eintegrate\u003c/strong\u003e). Between iteration and integration, the most important is integration – that\u0026rsquo;s how you find problems and ensure you\u0026rsquo;re progressing in the right direction. Iteration is simply writing small blocks of code so you can get back to integration, again.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m going to follow this approach with some fairly concrete steps:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eplaceholders\u003c/li\u003e\n\u003cli\u003estubs\u003c/li\u003e\n\u003cli\u003eimplementation\u003c/li\u003e\n\u003cli\u003eiteration\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"placeholders\"\u003ePlaceholders\u003c/h2\u003e\n\u003cp\u003eIf we want early integration, then we need something to act as both Model and the View as early as possible. It shouldn\u0026rsquo;t be the real Model or the real View or have any substantial property of the real layers. It will merely be a name.\u003c/p\u003e\n\u003cp\u003eThis is the concept of a placeholder.\u003c/p\u003e\n\u003ch3 id=\"view-placeholder\"\u003eView placeholder\u003c/h3\u003e\n\u003cp\u003eCreate an application from Xcode\u0026rsquo;s SwiftUI project templates and you\u0026rsquo;ll get a \u003ccode\u003eContentView\u003c/code\u003e that looks like this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"nc\"\u003eContentView\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esome\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Hello, world!\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epadding\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThat\u0026rsquo;s our placeholder View. It has no behavior and it has the wrong structure but it has a name and that\u0026rsquo;s all we need.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eAnswering the question \u0026ldquo;which should you write first, View-code or Model-code\u0026rdquo;\u003c/strong\u003e: the View \u003cem\u003eappeared\u003c/em\u003e first but I didn\u0026rsquo;t write it. Not the most helpful answer.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"model-placeholder\"\u003eModel placeholder\u003c/h3\u003e\n\u003cp\u003eLet\u0026rsquo;s create a placeholder for the Model. The Model for a JSON feed app might initially be just the JSON feed itself.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003earticles\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis alone would work as a Model placeholder – it has a name.\u003c/p\u003e\n\u003cp\u003eI think I can take an extra half-step to give it array arrity here because the important part about JSON feed is that it contains an array of articles.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003earticles\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;one\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;two\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;three\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis array of \u003ccode\u003earticles\u003c/code\u003e is not a representation of a JSON feed but it will work as a placeholder. I have ensured that it has equivalent \u0026ldquo;cardinality\u0026rdquo; (it\u0026rsquo;s an array, not a single object) which will help me segue into the next iteration.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eAnswering the question \u0026ldquo;which should you write first, View-code or Model-code\u0026rdquo;\u003c/strong\u003e: the first line of code that I wrote was the Model placeholder but it\u0026rsquo;s not going to survive through the next iteration. Maybe it\u0026rsquo;s not the most useful question.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"placeholder-integration\"\u003ePlaceholder integration\u003c/h3\u003e\n\u003cp\u003eBefore we iterate further, we need to integrate – which is nothing more than putting the model inside the view.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"nc\"\u003eContentView\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003earticles\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;one\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;two\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;three\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esome\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Hello, world!\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"n\"\u003epadding\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis integration was straightforward. We\u0026rsquo;ll see a more substantial integration in the next section before choosing to change the order and make Model changes before View changes.\u003c/p\u003e\n\u003ch2 id=\"stubs\"\u003eStubs\u003c/h2\u003e\n\u003ch3 id=\"stub-views\"\u003eStub views\u003c/h3\u003e\n\u003cp\u003eThis is the start of the second iteration and that means throwing away code from an old iteration. In this case, we throw away the entire contents of the \u003ccode\u003eContentView.body\u003c/code\u003e property.\u003c/p\u003e\n\u003cp\u003eIn its places, we can stub out a \u003ccode\u003eList\u003c/code\u003e using the array of \u003ccode\u003earticles\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"nc\"\u003eContentView\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003earticles\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;one\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;two\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;three\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esome\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003eNavigationView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003earticles\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"kc\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003earticle\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eNavigationLink\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edestination\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003earticle\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003earticle\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eColor\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eclear\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enavigationViewStyle\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eDoubleColumnNavigationViewStyle\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOur stub view has the basic components: a \u003ccode\u003eDoubleColumnNavigationViewStyle\u003c/code\u003e with \u003ccode\u003eList\u003c/code\u003e and a \u003ccode\u003eNavigationLink\u003c/code\u003e but the views have no real content and do nothing.\u003c/p\u003e\n\u003cp\u003eStubbing out views is probably the best aspect of SwiftUI. In 14 lines of code (5 of which are largely empty) you can see the entire program.\u003c/p\u003e\n\u003cp\u003eThis code runs and give the following app:\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"/assets/blog/json_feed_reader_stubs.png\" alt=\"\"\u003e\u003c/p\u003e\n\u003cp\u003eAs we add refinements and features in SwiftUI, the details can dwarf the basic structure. Hiding details, without hiding structure, should be the goal of any good SwiftUI developer. It\u0026rsquo;s important to remember what your view looks like at the stub stage and continually refactor to resurface this underlying structure.\u003c/p\u003e\n\u003ch3 id=\"stub-model\"\u003eStub model\u003c/h3\u003e\n\u003cp\u003eAs we did when we started the stub view, we\u0026rsquo;re throwing away the \u003cem\u003eentire\u003c/em\u003e stub model. It was just a name and there\u0026rsquo;s nothing to carry forward (we\u0026rsquo;re not even going to keep the name).\u003c/p\u003e\n\u003cp\u003eWhat do we want instead? The primary requirement of a stub Model it that it needs to adopt the expected structure of the data, so we\u0026rsquo;re going to add a much larger block of code here.\u003c/p\u003e\n\u003cp\u003eThe \u003ccode\u003eFeed\u003c/code\u003e and \u003ccode\u003eArticle\u003c/code\u003e types are dictated by the format of the \u003ca href=\"https://jsonfeed.org\"\u003eJSONFeed standard\u003c/a\u003e, so it\u0026rsquo;s not difficult to write.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"nc\"\u003eFeed\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eCodable\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eitems\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eArticle\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"nc\"\u003eArticle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eCodable\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eURL\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eString\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003econtent\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eString\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003eenum\u003c/span\u003e \u003cspan class=\"nc\"\u003eCodingKeys\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eString\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eCodingKey\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003econtent\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;content_html\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eModel\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eObservableObject\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003ePublished\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003efeed\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eFeed\u003c/span\u003e\u003cspan class=\"p\"\u003e?\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eFeed\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003eitems\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eArticle\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;mock://x/a1.html\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Article1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003econtent\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;One\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eArticle\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;mock://x/a2.html\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Article2\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003econtent\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Two\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eArticle\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;mock://x/a3.html\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Article3\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003econtent\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Three\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"stub-integration\"\u003eStub integration\u003c/h3\u003e\n\u003cp\u003eIn the above code, I made \u003ccode\u003eModel\u003c/code\u003e conform to \u003ccode\u003eObservableObject\u003c/code\u003e and offered \u003ccode\u003efeed\u003c/code\u003e as a \u003ccode\u003e@Published\u003c/code\u003e property. This is the technical requirement to connect this version of the Model with the View.\u003c/p\u003e\n\u003cp\u003eOn the other end of the connection, we must make the \u003ccode\u003eContentView\u003c/code\u003e reference this new \u003ccode\u003eModel\u003c/code\u003e. We remove the \u003ccode\u003earticles\u003c/code\u003e stub and instead include our new \u003ccode\u003emodel\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"nc\"\u003eContentView\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003eObservedObject\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eModel\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"c1\"\u003e// ...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAll \u003ccode\u003eContentView\u003c/code\u003e construction will now need to pass a \u003ccode\u003eModel\u003c/code\u003e and the \u003ccode\u003eList\u003c/code\u003e needs to be updated to follow the new structure of the data.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efeed\u003c/span\u003e\u003cspan class=\"p\"\u003e?.\u003c/span\u003e\u003cspan class=\"n\"\u003eitems\u003c/span\u003e \u003cspan class=\"p\"\u003e??\u003c/span\u003e \u003cspan class=\"p\"\u003e[],\u003c/span\u003e \u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003erow\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003eNavigationLink\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edestination\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econtent\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eRunning the app now show that our stub model is visible in the view:\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"/assets/blog/json_feed_reader_stub_integration.png\" alt=\"\"\u003e\u003c/p\u003e\n\u003ch2 id=\"functional-implementations\"\u003eFunctional implementations\u003c/h2\u003e\n\u003ch3 id=\"model-functional-implementation\"\u003eModel functional implementation\u003c/h3\u003e\n\u003cp\u003eWe\u0026rsquo;re now at the point where it\u0026rsquo;s time for the \u003ccode\u003eModel\u003c/code\u003e to load the actual feed. Since this implementation has no effect on the \u003ccode\u003eModel\u003c/code\u003e\u0026rsquo;s interface, it doesn\u0026rsquo;t matter whether this step occurs before or after similar changes in the View.\u003c/p\u003e\n\u003cp\u003eThe placeholder construction of the \u003ccode\u003efeed\u003c/code\u003e is thrown away. Instead, we\u0026rsquo;re going to get real data using \u003ccode\u003eURLRequest\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003etask\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eURLSessionDataTask\u003c/span\u003e\u003cspan class=\"p\"\u003e?\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003einit\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003erequest\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eURLRequest\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;https://www.cocoawithlove.com/feed.json\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003etask\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eURLSession\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eshared\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edataTask\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewith\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eerror\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003edo\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eerror\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eerror\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ethrow\u003c/span\u003e \u003cspan class=\"n\"\u003eerror\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003efeed\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003etry\u003c/span\u003e \u003cspan class=\"n\"\u003eJSONDecoder\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"n\"\u003edecode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eFeed\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"kc\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003efrom\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003edata\u003c/span\u003e \u003cspan class=\"p\"\u003e??\u003c/span\u003e \u003cspan class=\"n\"\u003eData\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eDispatchQueue\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003easync\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"kc\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efeed\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003efeed\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003ecatch\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003etask\u003c/span\u003e\u003cspan class=\"p\"\u003e?.\u003c/span\u003e\u003cspan class=\"n\"\u003eresume\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"view-functional-implementation\"\u003eView functional implementation\u003c/h3\u003e\n\u003cp\u003eThe View-equivalent of functional implementation in the Model are display changes unrelated to model data. Again, as with the Model functional implementation, this could happen before or after the Model changes.\u003c/p\u003e\n\u003cp\u003eThere\u0026rsquo;s less throwing away code in this implementation, it\u0026rsquo;s more a progressive refactoring pass. I\u0026rsquo;ve added nav bar titles, nav bar styling, and a \u003ccode\u003eUIViewRepresentable\u003c/code\u003e-wrapped \u003ccode\u003eWKWebView\u003c/code\u003e for feed content instead of a simple \u003ccode\u003eText\u003c/code\u003e view). The \u003ccode\u003eContentView\u003c/code\u003e \u003ccode\u003ebody\u003c/code\u003e function is updated as follows:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003edetailView\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003e_\u003c/span\u003e \u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eArticle\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003esome\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003eSharedWebView\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003econtent\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econtent\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enavigationTitle\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enavigationBarTitleDisplayModeIfAvailable\u003c/span\u003e\u003cspan class=\"p\"\u003e(.\u003c/span\u003e\u003cspan class=\"n\"\u003einline\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esome\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003eNavigationView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efeed\u003c/span\u003e\u003cspan class=\"p\"\u003e?.\u003c/span\u003e\u003cspan class=\"n\"\u003eitems\u003c/span\u003e \u003cspan class=\"p\"\u003e??\u003c/span\u003e \u003cspan class=\"p\"\u003e[],\u003c/span\u003e \u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003erow\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eNavigationLink\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edestination\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003edetailView\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enavigationTitle\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Articles\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enavigationBarTitleDisplayModeIfAvailable\u003c/span\u003e\u003cspan class=\"p\"\u003e(.\u003c/span\u003e\u003cspan class=\"n\"\u003einline\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003eColor\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eclear\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enavigationViewStyle\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eDoubleColumnNavigationViewStyle\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHere\u0026rsquo;s how it looks:\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"/assets/blog/json_feed_reader_functional.png\" alt=\"\"\u003e\u003c/p\u003e\n\u003cp\u003eAt this point, we now have an end-to-end functioning version of the app. By focussing on a single data pipeline (the feed) we\u0026rsquo;ve avoided complications and can now build on the core by adding the additional buttons and visual changes from our goal design.\u003c/p\u003e\n\u003ch2 id=\"iterate-on-the-implementation\"\u003eIterate on the implementation\u003c/h2\u003e\n\u003cp\u003eI want the \u0026ldquo;is-read\u0026rdquo; statuses for each article and I want them to be persistent. Let\u0026rsquo;s add those now.\u003c/p\u003e\n\u003cp\u003eIn the Placeholder and Stub sections, I wrote the View before the Model but at each stage, the integration required after the Model changes grew. Now that we have a non-empty View, it becomes easier to start changes at the Model so we can combine updates to the View with any required integration steps.\u003c/p\u003e\n\u003cp\u003eAs you fill out a structure and pattern for your program, it becomes easier to handle more complex changes in each step. The key is to observe whether you\u0026rsquo;re able to write your code continuously – without loss of focus, mistakes or backtracking. If you find these problems occurring, it\u0026rsquo;s a sign that you should attempt iterations in smaller steps.\u003c/p\u003e\n\u003ch3 id=\"model-iteration\"\u003eModel iteration\u003c/h3\u003e\n\u003cp\u003eIn this case, I\u0026rsquo;m going to add the \u0026ldquo;is-read\u0026rdquo; status in one step. The \u0026ldquo;is-read\u0026rdquo; implementation in \u003ccode\u003eModel\u003c/code\u003e looks like this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003ePublished\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003eisReadStatuses\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eBool\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003einit\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kc\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eisReadStatuses\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eUserDefaults\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estandard\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eforKey\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;isReadStatuses\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eflatMap\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e?\u003c/span\u003e \u003cspan class=\"n\"\u003eJSONDecoder\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"n\"\u003edecode\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"n\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eBool\u003c/span\u003e\u003cspan class=\"p\"\u003e].\u003c/span\u003e\u003cspan class=\"kc\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003efrom\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nv\"\u003e$0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e??\u003c/span\u003e \u003cspan class=\"p\"\u003e[:]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"c1\"\u003e// ... feed loading omitted\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003esetIsRead\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003e_\u003c/span\u003e \u003cspan class=\"n\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eBool\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003eisReadStatuses\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003evalue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003eUserDefaults\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estandard\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"kr\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e?\u003c/span\u003e \u003cspan class=\"n\"\u003eJSONEncoder\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"n\"\u003eencode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eisReadStatuses\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"n\"\u003eforKey\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;isReadStatuses\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"view-iteration\"\u003eView iteration\u003c/h3\u003e\n\u003cp\u003eThis latest Model change can alter the state of the \u003ccode\u003edetailView\u003c/code\u003e after it is presented (by changing the \u0026ldquo;is-read\u0026rdquo; status). For a \u003ccode\u003eNavigationView\u003c/code\u003e to change, it must observe the Model for itself (it cannot rely on the parent\u0026rsquo;s observation or else the old state will remain presented when the state changes) so we need to move the \u003ccode\u003edetailView\u003c/code\u003e into its own \u003ccode\u003eView\u003c/code\u003e implementation:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"nc\"\u003eDetailView\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"n\"\u003eObservedObject\u003c/span\u003e \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eModel\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003earticle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eArticle\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nv\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esome\u003c/span\u003e \u003cspan class=\"n\"\u003eView\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eisRead\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eisReadStatuses\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003earticle\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e??\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eSharedWebView\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003econtent\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003earticle\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econtent\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enavigationTitle\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003earticle\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enavigationBarItems\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003etrailing\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003eButton\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                  \u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esetIsRead\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"n\"\u003eisRead\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003earticle\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"n\"\u003elabel\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                  \u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eisRead\u003c/span\u003e \u003cspan class=\"p\"\u003e?\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Mark as unread\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Mark as read\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enavigationBarTitleDisplayMode\u003c/span\u003e\u003cspan class=\"p\"\u003e(.\u003c/span\u003e\u003cspan class=\"n\"\u003einline\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eonAppear\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esetIsRead\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003earticle\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eand use this new implementation from the primary column:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erows\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003erow\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003eNavigationLink\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edestination\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eDetailView\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003earticle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003eHStack\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nv\"\u003eisRead\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eisReadStatuses\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e??\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eImage\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esystemName\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eisRead\u003c/span\u003e \u003cspan class=\"p\"\u003e?\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;checkmark.circle\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;circle\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"further-iterations\"\u003eFurther iterations\u003c/h3\u003e\n\u003cp\u003eAs you can see from the this iteration, we\u0026rsquo;ve transitioned from iterating where code is thrown away to simpler feature development where new code is added but little is removed. That\u0026rsquo;s okay; that\u0026rsquo;s a sign that the core is complete.\u003c/p\u003e\n\u003cp\u003eThe following features were added in this way to the final code:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eError handling\u003c/li\u003e\n\u003cli\u003eFeed reloading\u003c/li\u003e\n\u003cli\u003eManually toggling \u0026ldquo;is-read\u0026rdquo; status on each article\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eand we\u0026rsquo;ve reached the goal state for the app:\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"/assets/blog/json_feed_reader.png\" alt=\"\"\u003e\u003c/p\u003e\n\u003ch2 id=\"isnt-this-all-obvious\"\u003eIsn\u0026rsquo;t this all obvious?\u003c/h2\u003e\n\u003cp\u003eReading over what I\u0026rsquo;ve said, I don\u0026rsquo;t feel like I\u0026rsquo;ve said anything that isn\u0026rsquo;t completely obvious. Write a couple lines of code, hit build-and-run, confirm that it runs, then add more. That\u0026rsquo;s what everyone does whenever they program, isn\u0026rsquo;t it?\u003c/p\u003e\n\u003cp\u003eDespite the apparent obviousness, working in tight iterations is one of the best rules that I, too often, fail to follow.\u003c/p\u003e\n\u003cp\u003eIf I were perfectly disciplined, I would merge code on my projects roughly twice per day, with each merge being between 100 and 400 lines. When this frequency is maintained, pull requests are manageable and readable, other team members can comment on direction before too much code is written and stories move right at a predictable cadence.\u003c/p\u003e\n\u003cp\u003eUnfortunately, it\u0026rsquo;s very easy to think about the bigger picture and accidentally start work on everything at once. Larger units of code are much slower to work on. It\u0026rsquo;s not an exponential increase but it\u0026rsquo;s worse than linear – integrating a 2000 line change will usually take more than 10 times longer than integrating a 200 line change. Larger code changes are also harder to reason about – team members reviewing your change are more likely to mentally disengage than give useful feedback. And mistakes tend to be bigger, harder to address and, with the time-investment, sunk-cost fallacies are more common.\u003c/p\u003e\n\u003cp\u003eIf small iterations and integrations are \u0026ldquo;obvious\u0026rdquo;, why aren\u0026rsquo;t they more natural on real-world projects?\u003c/p\u003e\n\u003cp\u003eThe difficulty occurs when you\u0026rsquo;re not applying a clear \u0026ldquo;iteration\u0026rdquo; and \u0026ldquo;integration\u0026rdquo; as much as you\u0026rsquo;re refactoring one shape of code into another. Finding small clear steps becomes a challenge. The stages \u0026ldquo;placeholder\u0026rdquo;, \u0026ldquo;stubs\u0026rdquo;, \u0026ldquo;functional implementation\u0026rdquo; and \u0026ldquo;further iterations\u0026rdquo; become much messier.\u003c/p\u003e\n\u003cp\u003eA few options can help\u0026hellip;\u003c/p\u003e\n\u003ch3 id=\"parallel-implementations\"\u003eParallel implementations\u003c/h3\u003e\n\u003cp\u003eInstead of tearing down 5000 lines of code in one block, start a new, independent implementation alongside the old one and move components from the old implementation to the new implementation, one-at-a-time. In this way, you can apply a large refactor like you might create clean new code. Code and use the new implementation for those components moved across but the old implementation for those components yet to be migrated.\u003c/p\u003e\n\u003ch3 id=\"feature-toggles\"\u003eFeature toggles\u003c/h3\u003e\n\u003cp\u003eIf the reason you can\u0026rsquo;t commit a new block of code is because it doesn\u0026rsquo;t work, then you can always turn it off with a feature toggle (a buildtime or runtime condition in the code that skips over the new code). This allows you to commit incomplete or non-functional code to the repository, get feedback and track progress. It\u0026rsquo;s not as good an option as a parallel implementation (which can be partially used from the outset) but it can still allow you to maintain momentum.\u003c/p\u003e\n\u003ch3 id=\"architectural-isolation\"\u003eArchitectural isolation\u003c/h3\u003e\n\u003cp\u003eThe best option to keep code changes small is to have a codebase where all definitions and functionality are isolated and no app-specific definition extends across more than a few thousand lines of code.\u003c/p\u003e\n\u003cp\u003eThis approach gets difficult when your app is tens or even hundreds of thousands of lines of code but I\u0026rsquo;ll be looking at this topic further in later articles in this series.\u003c/p\u003e\n\u003ch2 id=\"whats-next\"\u003eWhat\u0026rsquo;s next?\u003c/h2\u003e\n\u003cp\u003eIn this article, I answered the question \u0026ldquo;which should you write first, View-code or Model-code\u0026rdquo; with a resounding, \u0026ldquo;maybe it\u0026rsquo;s not the most useful question\u0026rdquo;.\u003c/p\u003e\n\u003cp\u003eIn the next article, I want to answer the following question: what app design pattern have I used here? I promise the answer will be more straightforward.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe code for this article \u003ca href=\"https://github.com/mattgallagher/CwlFeedReader/tree/part-one\"\u003eis available on github\u003c/a\u003e and each of the iteration phases is available as a separate commit in the history.\u003c/p\u003e\n\u003c/blockquote\u003e\n",
            "date_published": "2020-12-31T22:29:53+11:00"
        }
    ]
}
