<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alessandro Marrella</title>
    <description>The latest articles on DEV Community by Alessandro Marrella (@amarrella).</description>
    <link>https://dev.to/amarrella</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F250540%2F2ce29663-cc00-460e-b1ae-b82171e88d17.jpeg</url>
      <title>DEV Community: Alessandro Marrella</title>
      <link>https://dev.to/amarrella</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amarrella"/>
    <language>en</language>
    <item>
      <title>Generate a podcast about anything you want</title>
      <dc:creator>Alessandro Marrella</dc:creator>
      <pubDate>Tue, 01 Oct 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/amarrella/generate-a-podcast-about-anything-you-want-48el</link>
      <guid>https://dev.to/amarrella/generate-a-podcast-about-anything-you-want-48el</guid>
      <description>&lt;p&gt;Google's &lt;a href="https://notebooklm.google" rel="noopener noreferrer"&gt;NotebookLM&lt;/a&gt; is making the rounds on the internet (at least in my bubble). It's a new AI tool that Google pitches as a "personalized research assistant" but has evolved to be much more than that, including... a podcast generator.&lt;/p&gt;

&lt;p&gt;Before diving deeper into what NotebookLM does, have a listen to a few of the podcasts I generated and judge for yourself how good (or bad) they are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Podcast about my blog&lt;/strong&gt; &lt;br&gt;
&lt;a href="https://alessandromarrella.com/audio/alessandromarrella-com-podcast.mp3" rel="noopener noreferrer"&gt;https://alessandromarrella.com/audio/alessandromarrella-com-podcast.mp3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Podcast about LLAMA3.1's paper&lt;/strong&gt; &lt;br&gt;
&lt;a href="https://alessandromarrella.com/audio/llama3-paper.mp3" rel="noopener noreferrer"&gt;https://alessandromarrella.com/audio/llama3-paper.mp3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Podcast about chess.com's privacy policy&lt;/strong&gt; &lt;br&gt;
&lt;a href="https://alessandromarrella.com/audio/chess-privacy.mp3" rel="noopener noreferrer"&gt;https://alessandromarrella.com/audio/chess-privacy.mp3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my opinion these podcasts could fool anyone to be "real". Even if the structure is similar, and the hosts are always excited no matter the topic, this is not very far from what happens in the average podcast (especially American ones, sorry :) ).&lt;/p&gt;

&lt;p&gt;Generating a podcast using NotebookLM is very simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign in to &lt;a href="https://notebooklm.google.com/?_gl=1*1itfiso*_ga*NzYyNzQwMzI0LjE3Mjc3NzQ2NjU.*_ga_W0LDH41ZCB*MTcyNzc3NDY2NC4xLjEuMTcyNzc3NDc3Ni4wLjAuMA..&amp;amp;original_referer=https:%2F%2Fnotebooklm.google%23" rel="noopener noreferrer"&gt;NotebookLM&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Accept the terms etc&lt;/li&gt;
&lt;li&gt;Click "Upload Source", there you can upload a file, or paste a link directly, or read from your google drive. You can upload multiple docs as well.&lt;/li&gt;
&lt;li&gt;In the "Audio Overview" section, click "Generate" and wait a few minutes...&lt;/li&gt;
&lt;li&gt;Done! You should be able to play or download the audio directly in the UI.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As mentioned previously, NotebookLM is a research assistant, so you can also use it to ask questions about your documents, generate FAQs and study guides, and more.&lt;/p&gt;

&lt;p&gt;It's a pretty cool tool that Google created, it's (for now) free, and I'm really happy we are moving beyond the "chatbot-style" interface with AI and exploring something new. Turns out LLMs are not only good at generating content, but also questions about the content you provide.&lt;/p&gt;

&lt;p&gt;To learn more I recommend the always amazing &lt;a href="https://simonwillison.net/2024/Sep/29/notebooklm-audio-overview/" rel="noopener noreferrer"&gt;Simon Willison's blog&lt;/a&gt;, and to try it yourself. It's really impressive and could be a really useful tool for learning new things.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Deepfaking myself was scarily easy</title>
      <dc:creator>Alessandro Marrella</dc:creator>
      <pubDate>Tue, 03 Sep 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/amarrella/deepfaking-myself-was-scarily-easy-2l7p</link>
      <guid>https://dev.to/amarrella/deepfaking-myself-was-scarily-easy-2l7p</guid>
      <description>&lt;p&gt;Moved by my curiosity on everything AI related, I decided to give it a try by creating a LoRa adapter on the FLUX.1 model by Black Forest Labs to generate pictures of myself that never happened.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22s83mi52edq8rjzofvq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22s83mi52edq8rjzofvq.png" alt="Deepfake of myself eating pineapple pizza" width="800" height="419"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
FLUX.1 is a model by &lt;a href="https://blackforestlabs.ai/" rel="noopener noreferrer"&gt;Black Forest Labs&lt;/a&gt; and as of today represents the state of the art on image generation. It currently comes into 3 versions: Schnell (open weights, Apache 2.0 licensed), Dev (open weights, Non Commercial) and Pro (Commercial, closed source).&lt;/p&gt;

&lt;p&gt;In this experiment, I decided to try training FLUX.1 Dev, using the very convenient &lt;code&gt;ostris/flux-dev-lora-trainer&lt;/code&gt; hosted on &lt;a href="https://replicate.com/ostris/flux-dev-lora-trainer/train" rel="noopener noreferrer"&gt;Replicate&lt;/a&gt;. The code for the trainer can be found on &lt;a href="https://github.com/ostris/ai-toolkit" rel="noopener noreferrer"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I didn't play around with the parameters much (training costed me around $2.44), the only things I did were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Uploading a Zip file with 12 photos of myself (without any label, I relied on the auto-labeling that the trainer provides that leverages &lt;a href="https://llava-vl.github.io/" rel="noopener noreferrer"&gt;LLava-1.5&lt;/a&gt;, a image captioning model)&lt;/li&gt;
&lt;li&gt;Setting the &lt;code&gt;autocaption_prefix&lt;/code&gt; to "A photo of TOK, ". TOK being a trigger word that should help the model to identify myself. I'm not sure if this helped or not (I decide to limit my budget to do a simple POC), but I did it anyway 🤷‍♂️.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I then kicked off the job, which took about 34 minutes to complete. Once the model was trained, by clicking "Run trained model" in the UI, I was brought to a form where I could configure a prompt like "A photo of TOK, eating pineapple pizza" and generate an image that hopefully would resemble me. You can also provide and image and a "mask" to guide the model on what to generate (see "Lord Commander of the Night's Watch example below).&lt;/p&gt;

&lt;p&gt;And that's it really, generating the images took about a minute, some of them missed the target, but the adapter learned my face reasonably well with no tweaking, and produced absurd results like the ones in the &lt;a href="https://photos.app.goo.gl/Rbn3sa41tzt67D9u6" rel="noopener noreferrer"&gt;examples gallery&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The next step I'm going to try is videos, though I am really creeped out already by the photos one.&lt;/p&gt;

&lt;p&gt;In a world where everything can be realistically faked (again, my example took no effort, I'm sure that can be tweaked be even more realistic) how will we be able to distinguish fiction from reality?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note: if you ever see a picture of me eating pineapple pizza, that's a fake for sure.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>generativeai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>BigQuery Editions vs On Demand</title>
      <dc:creator>Alessandro Marrella</dc:creator>
      <pubDate>Sat, 31 Aug 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/amarrella/bigquery-editions-vs-on-demand-1c1</link>
      <guid>https://dev.to/amarrella/bigquery-editions-vs-on-demand-1c1</guid>
      <description>&lt;p&gt;These are some notes on the tradeoffs and best practices between On Demand pricing vs Editions pricing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing models
&lt;/h2&gt;

&lt;p&gt;BigQuery currently offers two very different pricing models&lt;/p&gt;

&lt;h3&gt;
  
  
  Editions
&lt;/h3&gt;

&lt;p&gt;With &lt;a href="https://cloud.google.com/bigquery/docs/editions-intro" rel="noopener noreferrer"&gt;Editions&lt;/a&gt; you are charged for "compute time" by slot-hour. A slot is a virtual CPU that BigQuery uses to execute queries.&lt;/p&gt;

&lt;p&gt;Within Editions, you can purchase a "committment" for a lower price if you always have stuff running, with the caveat that you are charged the entire time (it doesn't scale up or down at will, and so for a yearly committment for 100 slots you end up paying for 100 * 365 * 24 slot hours, even if you don't use them).&lt;/p&gt;

&lt;p&gt;Outside the committed capacity, you can use an autoscaling reservation, which sets up the minimum (could be 0) and the maximum slots that BigQuery can use. Slots are scaled up and down based on compute requirements for the queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  On Demand
&lt;/h3&gt;

&lt;p&gt;With &lt;a href="https://cloud.google.com/bigquery/pricing#on_demand_pricing" rel="noopener noreferrer"&gt;On Demand&lt;/a&gt; you are charged for "bytes processed". The compute capacity that GCP gives you is about 2000 slots, but you are not being charged for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best practices based on the pricing models
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Minimize bytes scanned (especially on on demand)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Do not select *, but only select the columns you need.&lt;/li&gt;
&lt;li&gt;Partition/Cluster the tables so that you only scan the minimum amount of rows needed (though this increases compute on write, so it's not always helpful with editions, see note on compute used).&lt;/li&gt;
&lt;li&gt;Follow the best practices in the &lt;a href="https://cloud.google.com/bigquery/docs/best-practices-performance-compute#reduce-data-processed" rel="noopener noreferrer"&gt;bigquery docs (reduce data processed)&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Minimize compute used (especially for editions)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Optimize your joins, see &lt;a href="https://alessandromarrella.com/posts/semi-hash-join/" rel="noopener noreferrer"&gt;my note about semi hash joins&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Pay attention to when clustering and partitioning are more harmful than helpful. Take a look at the execution plan of the jobs that produce/update tables with partitioning and clustering. If the table is large, a lot of slot time is going to be spent on sorting the data to match the clustering.&lt;/li&gt;
&lt;li&gt;Follow the best practices in the &lt;a href="https://cloud.google.com/bigquery/docs/best-practices-performance-compute#optimize-query-operations" rel="noopener noreferrer"&gt;bigquery docs (optimize query ops)&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Right-size your slots autoscaling (editions only)
&lt;/h3&gt;

&lt;p&gt;Pay particular attention to the jobs where you have a "contention" warning, and dig through the steps in the execution plan.&lt;/p&gt;

&lt;p&gt;Check that the "Wait ms" stat and compare it with "Read", "Write" and "Compute". "Wait" is time that BigQuery spends waiting for slots to be available.&lt;/p&gt;

&lt;p&gt;Given that with autoscaling you are charged by the time allocated, and slots are allocated even when they are not used, wait time still counts towards the cost.&lt;/p&gt;

&lt;p&gt;If wait time is too high, this might mean that you need to either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;serialize the queries so that they are no longer in contention for slots&lt;/li&gt;
&lt;li&gt;increase the max in the autoscaling reservation to reduce contention&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't increase the autoscaling reservation maximum too much, as from experience the bigquery autoscaler is very eager to use as many slots as it can to run the query as quickly as possible, but scaling down takes time (that you are billed for), and the minimum interval of 1 min adds up quickly if you are using many thousands of slots.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run queries with the model that's cheapest (if possible)
&lt;/h3&gt;

&lt;p&gt;This sounds obvious, but Google doesn't make it exactly easy. You'll need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;estimate the price of a query in both modes.&lt;/li&gt;
&lt;li&gt;run the query in a dedicated project based on which model you choose (you cannot mix modes within a GCP project)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For 1 (estimate the price of a query in both modes), I like to use variations of the following query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
job_id, -- the bigquery job id that identifies a run
query, -- the bigquery query text
destination_table.table_id, -- the name of the table (if the query writes to a table)
start_time,
SUM(total_slot_ms) / (1000 * 60 * 60) * 0.06 as editions_cost -- unit cost (here $0.06/slot_hour) could be different based on the edition chosen and gcp discounts
SUM(total_bytes_billed) / POW(1024, 4) * 6.25 as on_demand_cost -- unit cost (here $6.25/tb) could be different based on the edition chosen and gcp discounts
FROM `{your_project}`.`{your_region}`.INFORMATION_SCHEMA.JOBS_BY_PROJECT -- or JOBS_BY_ORGANIZATION to see the whole company, but then you'll need to remove the query field
GROUP BY ALL

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

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note that the prices here are estimated, because google bills by slots "assigned" not slots used so sometimes slot price is always slightly higher than the estimate (it takes time to scale up and down, and google bills a minimum of 1 minute even if a query runs for 3 seconds).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For 2 (run the query in a dedicated project), you'll need to set up two separate GCP projects, one with the reservation (or the reservation assigned from another project) and the other without.&lt;/p&gt;

</description>
      <category>gcp</category>
      <category>bigquery</category>
      <category>sql</category>
    </item>
    <item>
      <title>How to quickly profile python imports and runtime</title>
      <dc:creator>Alessandro Marrella</dc:creator>
      <pubDate>Sat, 20 Jul 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/amarrella/how-to-quickly-profile-python-imports-and-runtime-28le</link>
      <guid>https://dev.to/amarrella/how-to-quickly-profile-python-imports-and-runtime-28le</guid>
      <description>&lt;p&gt;A small &lt;a href="https://alessandromarrella.com/tags/til/" rel="noopener noreferrer"&gt;TIL&lt;/a&gt; about Python profiling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nschloe/tuna" rel="noopener noreferrer"&gt;tuna&lt;/a&gt; is a really handy tool that renders the output of &lt;a href="https://docs.python.org/3/library/profile.html" rel="noopener noreferrer"&gt;cProfile&lt;/a&gt; and &lt;code&gt;python -X importtime&lt;/code&gt; logs into an easy to navigate tree.&lt;/p&gt;

</description>
      <category>python</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>The AI/ML concepts behind Apple Intelligence</title>
      <dc:creator>Alessandro Marrella</dc:creator>
      <pubDate>Wed, 19 Jun 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/amarrella/the-aiml-concepts-behind-apple-intelligence-adm</link>
      <guid>https://dev.to/amarrella/the-aiml-concepts-behind-apple-intelligence-adm</guid>
      <description>&lt;p&gt;On Monday, June 10, 2024 at their annual World Wide Developer Conference (&lt;a href="https://developer.apple.com/wwdc24/" rel="noopener noreferrer"&gt;WWDC&lt;/a&gt;) Apple announced "&lt;a href="https://www.apple.com/apple-intelligence/" rel="noopener noreferrer"&gt;Apple Intelligence&lt;/a&gt;", their own flavour of AI integration into their operating systems.&lt;/p&gt;

&lt;p&gt;Emphasis should probably go into &lt;em&gt;integration&lt;/em&gt;, as the experience that was demoed at WWDC felt like every feature we are used to on our devices was augmented in a way or another by AI. AI is also able to orchestrate those features and use multiple apps and functionalities to achieve what the user wanted.&lt;/p&gt;

&lt;p&gt;As someone who has a love/hate relationship with Siri, this is only good news, Siri will become way more powerful and able to understand what you want to do.&lt;/p&gt;

&lt;p&gt;This post is not going to be an overview of Apple Intelligence from the users perspective though (for which I suggest the excellent blog post from Simon Willison &lt;a href="https://simonwillison.net/2024/Jun/10/apple-intelligence/" rel="noopener noreferrer"&gt;Thoughts on the WWDC 2024 keynote on Apple Intelligence&lt;/a&gt;) but will explore key concepts such as the Semantic Index, App Intents Toolbox, and foundational model adaptations utilized by Apple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture#
&lt;/h2&gt;

&lt;p&gt;With what was presented in the &lt;a href="https://developer.apple.com/videos/play/wwdc2024/101/" rel="noopener noreferrer"&gt;keynote&lt;/a&gt;, the&lt;a href="https://developer.apple.com/videos/play/wwdc2024/102/?time=95" rel="noopener noreferrer"&gt;platform state of the union&lt;/a&gt; the content in &lt;a href="https://machinelearning.apple.com/research/introducing-apple-foundation-models" rel="noopener noreferrer"&gt;introducing apple foundation models&lt;/a&gt; we can derive a bit of the architecture that powers Apple Intelligence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltmkqpyp4q1kbqjmqqpd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltmkqpyp4q1kbqjmqqpd.png" alt="Architecture diagram for Apple Intelligence, from the platform state of the union." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the diagram above, we can see that the architecture has 3 layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Apps and Experiences&lt;/strong&gt; : features that the users sees and interacts with&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personal Intelligence System&lt;/strong&gt; : what runs on device and on Apple's servers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apple Silicon&lt;/strong&gt; : the specialised Apple hardware that powers the intelligence layer and the security between on-device and cloud communication.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Apps and systemwide experiences are for example writing tools (summarization, rewriting, etc), image playground (to generate images and "genmojis") and Siri.&lt;/p&gt;

&lt;h2&gt;
  
  
  Personal Intelligence System#
&lt;/h2&gt;

&lt;p&gt;Apps and systemwide experiences interact with the Personal Intelligence System by reading from and writing to the "Semantic Index" and the "App Intents Toolbox". Then there is a orchestration layer on device that decides whether to use on-device or server models.&lt;/p&gt;

&lt;p&gt;Let's break down these components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Semantic Index#
&lt;/h3&gt;

&lt;p&gt;What Apple calls a Semantic Index is probably a &lt;em&gt;vector database&lt;/em&gt; storing &lt;em&gt;embeddings&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;An "embedding" is a vector of numbers representing objects like text, images, audio and video into a multidimensional space. By computing the distance between two embeddings, you can see if two concepts are related. You can read more on embeddings in this &lt;a href="https://stackoverflow.blog/2023/11/09/an-intuitive-introduction-to-text-embeddings/" rel="noopener noreferrer"&gt;Stackoverflow Blog Post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Computing the distance between embeddings is usually compute intensive, especially if you are trying to surface "the closest embedding" in a collection of many of those.&lt;/p&gt;

&lt;p&gt;To alleviate the problem, there we tend to look for the &lt;a href="https://en.wikipedia.org/wiki/(1%2B%CE%B5)-approximate_nearest_neighbor_search" rel="noopener noreferrer"&gt;Approximate Nearest Neighbour (ANN)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Vector databases are databases specialized into storing vectors in a way that makes this search as efficient as possible.&lt;/p&gt;

&lt;p&gt;Examples in the opensource world of these databases are &lt;a href="https://www.trychroma.com/" rel="noopener noreferrer"&gt;Chroma&lt;/a&gt; and &lt;a href="https://qdrant.tech/" rel="noopener noreferrer"&gt;Qdrant&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  App Intents Toolbox#
&lt;/h3&gt;

&lt;p&gt;The "App Intents Toolbox" is a way for apps to declare their capabilities so that they can be leveraged by the model. This is leveraging a technique called "tool use", which comes in two variants: single step and multi step. For single step tool use (think "write an email") it's usually referred to as "function calling", while for multi step it's commonly referred to as "agents".&lt;/p&gt;

&lt;p&gt;With &lt;em&gt;function calling&lt;/em&gt;, you make available to the model a list of tools that the model can "call". Based on the prompt, the model chooses whether to use a tool or not, and if it chooses to do so, it returns what tool to use and with what parameters. Based on that, the system will call the tool, get the result and return that to the user. In Apple's terms, what happens is that the user interacts with the "Personal Intelligence System" via Siri or an App, the model that apple uses within the system will check the "App Intent Toolbox" to see if there is any app or system feature that can satisfy the request, call it with the parameters exposed in the App Intents API, and surface the result to the user. For example, a query like "show me the pictures of my dog in Dublin" will interact with the Images app via the intent toolbox, filter the pictures by selecting only those tagged (by another model at a separate time) as my dog, with the geo location set to Dublin, and return the app view with those pictures displayed.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Agents&lt;/em&gt; capture more nuanced use cases, by taking multiple steps of "function calling" on behalf of the user, so queries like "send the pictures of my dog in Dublin to my mom via email" will first call the images app, and then the email app, and the user is presented with the email pre-populated with the pictures.&lt;/p&gt;

&lt;p&gt;Both function calling and agents are available in the API of the most popular models (e.g. OpenAI or Google Gemini) and in the opensource world as well, with models such as Cohere's &lt;a href="https://docs.cohere.com/docs/tools" rel="noopener noreferrer"&gt;Command-R&lt;/a&gt; specialized in tool use, and frameworks such as &lt;a href="https://www.langchain.com/langchain" rel="noopener noreferrer"&gt;Langchain&lt;/a&gt; that help building complex agent applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orchestration#
&lt;/h3&gt;

&lt;p&gt;The orchestration layer is an on-device model whose only task is to decide whether to use one of the many on device or server models based on what the user or the system is trying to do. This is similar to the agents described above, but in this case the "tools" to use are other models!&lt;/p&gt;

&lt;h3&gt;
  
  
  Models#
&lt;/h3&gt;

&lt;p&gt;Individually, the on-device and server models are all multimodal models, with the key difference being the size of the model and the compute power required to run them.&lt;/p&gt;

&lt;p&gt;Apple &lt;a href="https://machinelearning.apple.com/research/introducing-apple-foundation-models" rel="noopener noreferrer"&gt;describes&lt;/a&gt; their model architecture as having foundation models + adapters.&lt;/p&gt;

&lt;p&gt;The modeling happens in different phases: &lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1t0qzo3tcylavc02r32o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1t0qzo3tcylavc02r32o.png" alt="Modeling phases: data collection, preprocessing, pre-training, post-training, optimization. This creates apple models which are specialized with adapters." width="800" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Data collection and pre-processing#
&lt;/h4&gt;

&lt;p&gt;Apple collects licensed data and scraped data by their own web crawler, for which they provide an opt out by configuring the robots.txt file on the website. They do some pre-processing and feed it to the pre-training.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pre-training#
&lt;/h4&gt;

&lt;p&gt;Pre-training happens using &lt;a href="https://github.com/apple/axlearn" rel="noopener noreferrer"&gt;AXLearn&lt;/a&gt;, a framework built on top of Google's JAX and Tensorflow's XLA. This allows them to run the training on GPUs and TPUs. Apple lists a bunch of techniques that they use to achieve efficient pre-training, by splitting the work across multiple machines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data Parallelism: Splitting data across multiple machines to train faster.&lt;/li&gt;
&lt;li&gt;Tensor Parallelism: Breaking down the model itself across multiple machines.&lt;/li&gt;
&lt;li&gt;Sequence Parallelism: Dividing long sequences of data to process them in parts.&lt;/li&gt;
&lt;li&gt;Fully Sharded Data Parallel (FSDP): Distributing both data and model pieces in a way that uses resources most efficiently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These techniques are well explained in the HuggingFace docs, which cover &lt;a href="https://huggingface.co/docs/transformers/main/en/perf_train_gpu_many" rel="noopener noreferrer"&gt;Multiple GPUs and Parallelism&lt;/a&gt; and the &lt;a href="https://huggingface.co/docs/transformers/main/en/fsdp" rel="noopener noreferrer"&gt;Fully Sharded Data Parallel (FSDP)&lt;/a&gt; method.&lt;/p&gt;

&lt;h4&gt;
  
  
  Post-training#
&lt;/h4&gt;

&lt;p&gt;After pre-training, Apple refines the model with "post-training", using a mix of reinforcement learning with human feedback (RLHF) and training on synthetic data.&lt;/p&gt;

&lt;p&gt;RLHF is a technique that became really popular with the release of ChatGPT, and consists in incorporating human feedback to align the model to human preferences. Once the model is pre-trained, humans interact with it and provide feedback on its performance, usually in terms of ranking different responses to the same prompt, or assigning a score. Based on the feedback the model receives a reward or a penalty, and these rewards or penalties are used to update the model's behaviour (this is typical of reinforcement learning, the model will try to optimize to receive as many rewards as possible over time). There are different ways to build this, there is a good overview on &lt;a href="https://huggingface.co/blog/rlhf" rel="noopener noreferrer"&gt;HuggingFace's blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Training on synthetic data is also a way to improve the model output, by feeding the model with data that is generated by a machine. The advantage of this is that the generated data is deterministic, and can be produced in high quantities. I suggest reading this paper from &lt;a href="https://arxiv.org/html/2404.07503v1" rel="noopener noreferrer"&gt;Google Deepmind&lt;/a&gt; to learn more about the current state of the art.&lt;/p&gt;

&lt;p&gt;There is also the possibility of mixing the two, by using Reinforcement Learning with AI Feedback, where instead of humans we can use other AIs, specialized in ranking and scoring.&lt;/p&gt;

&lt;h4&gt;
  
  
  Optimization#
&lt;/h4&gt;

&lt;p&gt;This stage is mainly focused on speed and efficiency of the models. Especially for those running on device, we want them to be able to work very fast and not draw a lot of power to not excessively consume battery. On their &lt;a href="https://machinelearning.apple.com/research/introducing-apple-foundation-models" rel="noopener noreferrer"&gt;models page&lt;/a&gt; Apple lists a few techniques with which they perform these optimizations.&lt;/p&gt;

&lt;h5&gt;
  
  
  Grouped Query Attention#
&lt;/h5&gt;

&lt;p&gt;First, they mention Grouped Query Attention (GQA). There are a few concepts to unpack to understand this technique. "Attention" is a mechanism that weighs the importance of different tokens (words, images, etc) in a sentence to predict the next token to generate, and involves three key components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Queries: the current token or sentence that the model is currently focusing on&lt;/li&gt;
&lt;li&gt;Keys: the tokens that the model can potentially focus on&lt;/li&gt;
&lt;li&gt;Values: the actual values (could be the same as the keys) that can be used in the output&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The simplest form of attention has a single "head" (a head in this context is an independent attention mechanism), and so lets the model only focus on one set of queries, keys and values. So the output can (for example) be computed by doing the dot product queries and keys to get the attention scores.&lt;/p&gt;

&lt;p&gt;Many large language models are based on the Transformer architecture, which commonly uses "multi-head" attention (&lt;a href="https://arxiv.org/abs/1706.03762" rel="noopener noreferrer"&gt;Attention is all you need&lt;/a&gt;). In this case there are multiple attention "heads", each focusing and prioritizing different tokens or sequences, and contributing to the final prediction. Multi-head attention is a very powerful technique because the model can focus on multiple parts of data at the same time, leading to more nuanced outputs. The issue with "multi-head" is that it's much more expensive to compute, slowing down a lot the inference times.&lt;/p&gt;

&lt;p&gt;To speed up the process, Shazeer from Google in 2019 published &lt;a href="https://arxiv.org/pdf/1911.02150" rel="noopener noreferrer"&gt;Fast Transformer Decoding: One Write-Head is All You Need&lt;/a&gt;, which introduces "multi-query attention", which shares keys and values across all the attention heads, only changing the "query". This leads to a much more memory efficient structure, making the inference much faster.&lt;/p&gt;

&lt;p&gt;The issue with the "multi-query attention" technique is a degradation in quality, due to the fact that the selected keys and values are the same for all heads. Grouped Query Attention is a compromise between multi-head attention (where we have distinct keys/values for each head) and multi query attention (where we share them for all heads). The compromise is reached by creating "groups" that share the same key value pairs.&lt;/p&gt;

&lt;h5&gt;
  
  
  Shared embedding tables#
&lt;/h5&gt;

&lt;p&gt;The models leverage tables that map tokens ("vocabulary") to vectors and vice-versa. By sharing them across models this allows for lower memory usage and greater inference speeds.&lt;/p&gt;

&lt;p&gt;Apple mentions that they use a 49K vocab for on device models, and 100K for server models (which include additional language and technical tokens). For comparison, &lt;a href="https://alessandromarrella.com/posts/apple-intelligence/(https://ai.meta.com/blog/meta-llama-3/)" rel="noopener noreferrer"&gt;Meta's LLama 3&lt;/a&gt; model uses a vocabulary of 128K tokens.&lt;/p&gt;

&lt;h5&gt;
  
  
  Low-bit palletization#
&lt;/h5&gt;

&lt;p&gt;Palletizaiton is a quantization technique to reduce the memory usage (and as a consequence reduce power usage and improve performance) by "compressing" the weight vectors using a fixed lookup table.&lt;/p&gt;

&lt;p&gt;For example if we had a vector of floating point weights such as &lt;code&gt;[0.1, 0.1, 0.2, 0.2]&lt;/code&gt; and a 1 bit look up table &lt;code&gt;{0.1: 0, 0.2: 1}&lt;/code&gt; we could compress the weights into a 1bit vector &lt;code&gt;[0, 1]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In particular, Apple says that they are mixing 2-bit and 4-bit palettes to achieve an average size of 3.5 bit per weight (which is much cheaper to use than the original 16 bits the model has been trained on). This usually comes at an output quality tradeoff cost, but it seems that from Apple's own benchmarks that the results are good enough (they mention that they measure the impact of these optimization with &lt;a href="https://arxiv.org/html/2404.03085v1" rel="noopener noreferrer"&gt;Talaria&lt;/a&gt;, a custom developed tool that analyses model latency and power usage to select the best bit rate).&lt;/p&gt;

&lt;h5&gt;
  
  
  Activation quantization#
&lt;/h5&gt;

&lt;p&gt;Activations are the outputs of the neurons after applying an activation function, common activation functions are ReLU, Tanh, or Sigmoid. The goal of these activation functions is to introduce a non-linearity in the neural network and allow the model to learn more complex patterns (there is a really good &lt;a href="https://www.youtube.com/watch?v=P6sfmUTpUmc" rel="noopener noreferrer"&gt;video on activations&lt;/a&gt; in Karpathy's series NN Zero to Hero).&lt;/p&gt;

&lt;p&gt;Quantizing the activation reduces the precision of it, again reducing the memory and compute footprint. You can see how to quantize the activation function in Lei Mao's "Quantization for Neural Network" book, in the &lt;a href="https://leimao.github.io/article/Neural-Networks-Quantization/#Quantized-Deep-Learning-Layers" rel="noopener noreferrer"&gt;Quantized Deep Learning Layers&lt;/a&gt; section.&lt;/p&gt;

&lt;h5&gt;
  
  
  Embedding quantization#
&lt;/h5&gt;

&lt;p&gt;As mentioned before, embeddings are vector representations of tokens. They can also be quantized to reduce the memory footprint and improve performance!&lt;/p&gt;

&lt;p&gt;In particular with embeddings, there is a now popular technique called "binary quantization", which converts these embeddings from float32 values into 1 bit values, significantly reducing the embeddings size. Quantizing embeddings here literally means setting a threshold of 0, and any value below 0 gets mapped to 0, and any value above is mapped to 1. This seems like a very large loss of information, but in practice for information retrieval it yields really good results (see &lt;a href="https://arxiv.org/abs/2106.00882" rel="noopener noreferrer"&gt;this paper&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Another big advantage of binary embedding quantization, is that retrieval is super fast using Hamming Distance, which only requires 2 cpu cycles to retrieve the information we need.&lt;/p&gt;

&lt;p&gt;For more details on embedding quantization, i suggest reading this &lt;a href="https://huggingface.co/blog/embedding-quantization" rel="noopener noreferrer"&gt;HuggingFace blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  Efficient key-value cache update on neural engines#
&lt;/h5&gt;

&lt;p&gt;Neural engines are a particular part of Apple silicon that are optimized to run computations for neural networks. Here Apple doesn't give a lot of information, but certainly having a way to quickly update the cache on a GPU-like device is something that's critical for performance.&lt;/p&gt;

&lt;h5&gt;
  
  
  Token speculation#
&lt;/h5&gt;

&lt;p&gt;Apple mentions they also employ token speculation techniques, which means that they are predicting multiple likely tokens at the same time, allowing the model to explore multiple path simultaneously. This is for example useful to provide a more "real time" experience to autocompletion, by speculating on a few paths that the user could take in composing the text.&lt;/p&gt;

&lt;h4&gt;
  
  
  Model adaptation#
&lt;/h4&gt;

&lt;p&gt;Apple created foundation models for language and image generation, these models are fine-tuned for different activities that the user might do on their device and specialize themselves just in time for the task at hand. To achieve a variety of specialization personalised for the user quickly Apple uses &lt;em&gt;adapters&lt;/em&gt;, small neural network models that can be attached to the foundation models to make them specialized on a specific task. These adapter models can be dynamically loaded into memory, cached and swapped.&lt;/p&gt;

&lt;h5&gt;
  
  
  Types of adaptation#
&lt;/h5&gt;

&lt;p&gt;Apple &lt;a href="https://machinelearning.apple.com/research/introducing-apple-foundation-models#model-adaptation:~:text=token%20generation%20rate.-,Model%20Adaptation,-Our%20foundation%20models" rel="noopener noreferrer"&gt;mentions&lt;/a&gt; that they adapt the attention matrices, the attention projection matrix and the fully connected layers in the point-wise feedforward networks.&lt;/p&gt;

&lt;p&gt;Let's break down the adapation techniques that Apple mentions.&lt;/p&gt;

&lt;h6&gt;
  
  
  Attention matrix adaptation#
&lt;/h6&gt;

&lt;p&gt;As explained &lt;a href="https://alessandromarrella.com/posts/apple-intelligence/#grouped-query-attention" rel="noopener noreferrer"&gt;above&lt;/a&gt;, the attention matrix is what makes the model "focus" on specific tokens or sequences to predict the next one. To adapt the attention matrix means for example updating how the attention scores are computed or modify the scaling factors, to ultimately change how attention is distributed. This can be achieved either by more training on a specific domain, or training by updating only the attention scores, or using techniques such as knowledge distillation, with a "teacher" model and a "student" model, where we aim for the student model to have a similar error between teacher and student attention matrices.&lt;/p&gt;

&lt;h6&gt;
  
  
  Attention projection matrices adaptation#
&lt;/h6&gt;

&lt;p&gt;Attention projection transforms the input sequence into matrices in usually three layers (Queries, Keys and Values), which are the main components of the attention mechanism.&lt;/p&gt;

&lt;p&gt;By inserting an adapter at the attention projection matrices layer, we add a small neural network specialized for a task that will change the matrices that are produced, influencing the final output to be more in line with the task that we have in mind. Low-rank (LoRa) adaptation is a way of achieving that, by replacing the original matrices with smaller ones, trained on the task-specific dataset. You can read more about LoRa &lt;a href="https://huggingface.co/docs/peft/main/en/conceptual_guides/lora" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h6&gt;
  
  
  Fully connected layers in the point-wise feedforward networks adaptation#
&lt;/h6&gt;

&lt;p&gt;Point-wise feed-forward networks are a component of transformer models (introduced in &lt;a href="https://arxiv.org/abs/1706.03762" rel="noopener noreferrer"&gt;Attention is all you need&lt;/a&gt;). They consist in two fully connected layers with an activation function in the middle. They are generally used to learn more complex transformations.&lt;/p&gt;

&lt;p&gt;Inserting an adapter here means injecting them between those layers to again make the model specialise on completing specific tasks.&lt;/p&gt;

&lt;h4&gt;
  
  
  Evaluation and fine-tuning improvements#
&lt;/h4&gt;

&lt;p&gt;Given that the focus of Apple Intelligence is to augment the user experience, the main focus of Apple's evaluation pipeline is Human Evaluation. In their &lt;a href="https://machinelearning.apple.com/research/introducing-apple-foundation-models" rel="noopener noreferrer"&gt;foundation models&lt;/a&gt; document, Apple provides a useful example on how they conduct the study (the example is based on the summarization task for emails vs notifications):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the local model summarization adapter is trained on synthetic summaries generated by the more powerful server models (this is known as knowledge distillation, also mentioned previously on the optimization section)&lt;/li&gt;
&lt;li&gt;to evaluate the product-specific summarization (email vs notification), they sample responses for each use case, with diverse inputs, with datasets that resemble real use cases&lt;/li&gt;
&lt;li&gt;they run explicit tests to reduce risks related to the task (e.g. for summarization, omitting important information), and also conduct adversarial tests to identify unknown harms&lt;/li&gt;
&lt;li&gt;also foundation models are tested and evaluated &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I suggest looking through the Evaluation section of their doc to read more about the benchmarks they ran and how they are evaluating their model performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion#
&lt;/h2&gt;

&lt;p&gt;Apple Intelligence showcases the cutting-edge AI and ML techniques that drive the next generation of user experiences, which we can all use to improve our products. By integrating a sophisticated architecture with the Semantic Index (vector store of embeddings) and App Intents Toolbox (tools for function calling and agents), Apple has created a seamless interaction environment to augment the user capabilities while using their devices.&lt;/p&gt;

&lt;p&gt;With their optimization techniques, including Grouped Query Attention, shared embedding tables, and various quantization methods, they enhance performance and efficiency.&lt;/p&gt;

&lt;p&gt;Apple’s use of adapters for model specialization ensures tailored user interactions while maintaining high-speed processing and low power consumption.&lt;/p&gt;

&lt;p&gt;Finally, their evaluation loop based on a combination of human feedback and fine-tuning, allows them to further improve the user experience, which is a key focus of Apple's differentiation.&lt;/p&gt;

&lt;p&gt;These advancements not only enhance user experience but also pave the way for more intuitive and intelligent applications. As developers, exploring these techniques can inspire innovative solutions in our own projects. I'm excited for the way forward, and cannot wait to see the finished product that will come out with iOS 18 and the new MacOs. In the meantime, I'll try to apply some of these techniques and optimizations to my projects to understand them further.&lt;/p&gt;

&lt;p&gt;UPDATE: Apple released more details in their &lt;a href="https://arxiv.org/pdf/2407.21075" rel="noopener noreferrer"&gt;Apple Intelligence Foundation Language Models paper&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The images in this blog post are © 2024 Apple and are used under the doctrine of fair use for purposes of commentary.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>generativeai</category>
    </item>
    <item>
      <title>BigQuery performance best practice: use semi joins when possible</title>
      <dc:creator>Alessandro Marrella</dc:creator>
      <pubDate>Sun, 02 Jun 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/amarrella/bigquery-performance-best-practice-use-semi-joins-when-possible-2m0j</link>
      <guid>https://dev.to/amarrella/bigquery-performance-best-practice-use-semi-joins-when-possible-2m0j</guid>
      <description>&lt;p&gt;SQL is an amazing language, it lets you declaratively say what you want, and the engine figures out for you the best way to return it to you. Or should I say, it figures out the best way to return it to you &lt;em&gt;given the information it has and the capabilities of the engine itself&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In this post, we’ll discuss a performance optimization technique for BigQuery (also other advanced enough Enteprise Data Warehouses and databases support SEMI JOINS, but I'll focus on BigQuery since it's the one I use the most these days): using semi joins.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A SEMI JOIN returns rows from the first table (left table) where one or more matches are found in the second table (right table), but it does not return rows from the second table. This can significantly improve query performance in certain scenarios.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's look at an example query using their public datasets, the NYC taxi dataset, which contains a log of taxi trips in NYC.&lt;/p&gt;

&lt;p&gt;Suppose that we want to know the distinct pickup_location_id values where both yellow and green taxis picked up clients in 2022.&lt;/p&gt;

&lt;p&gt;One way to express this query might be the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT DISTINCT yellow.pickup_location_id
  FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2022` yellow
  join `bigquery-public-data.new_york_taxi_trips.tlc_green_trips_2022` green
  on yellow.pickup_location_id = green.pickup_location_id

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

&lt;/div&gt;



&lt;p&gt;If we look at the performance, on my project at the time of running this query's performance is the following (results might change a bit based on your google cloud project, slot availability, time of day, etc.):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total elapsed time&lt;/strong&gt; : 4min 45sec&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slot time consumed&lt;/strong&gt; : 3h40min (A &lt;a href="https://cloud.google.com/bigquery/docs/slots" rel="noopener noreferrer"&gt;slot&lt;/a&gt; in BigQuery is a unit of computational capacity required to execute SQL queries)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we look at the execution graph, we see the following &lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94vu7fvobrr67miyjcy2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94vu7fvobrr67miyjcy2.png" alt="Execution Graph, showing that the join took more than 4 minutes" width="800" height="783"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The join is definitely the step that takes the longest! If we click the join step in the graph, BigQuery really helpfully shows us more information:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgaid897uhpne664dfr2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgaid897uhpne664dfr2.png" alt="Join Detail, showing the join stats with a lot more rows produced than consumed" width="591" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The UI shows that the join produced a lot more rows than it consumed, due to how the join was applied. You can also see that it uses a &lt;code&gt;INNER HASH JOIN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In theory, this is a very efficient kind of join, as it builds a hash table with the join keys of one table (the smaller one), and then probes the other table (the larger one) to find the row keys that have a match in the hash table.&lt;/p&gt;

&lt;p&gt;The problem in this case is not much in how the join happens, but in what it produces. As you can peek from the screenshot above, the number of rows produced is 83 million! This is due to the many-to-many relationship that we have in this join, where a &lt;code&gt;pickup_location_id&lt;/code&gt; can happen multiple times in either table.&lt;/p&gt;

&lt;p&gt;In a sense, BigQuery here does way more than we need to, as it would be enough to find one match in the "green" table to consider the row in yellow valid. In other words, we need a &lt;code&gt;SEMI JOIN&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A &lt;code&gt;SEMI JOIN&lt;/code&gt; returns rows from the first table (left table) where one or more matches are found in the second table (right table), but it does not return rows from the second table.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;How do we rewrite the previous query to make BigQuery use a semi join?&lt;/p&gt;

&lt;p&gt;For this specific query we have (at least) three options, which all make use of the &lt;code&gt;SEMI HASH JOIN&lt;/code&gt; in the query plan:&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: use &lt;code&gt;EXISTS&lt;/code&gt;#
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT DISTINCT yellow.pickup_location_id
  FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2022` yellow
  WHERE EXISTS (
    SELECT 1
    FROM `bigquery-public-data.new_york_taxi_trips.tlc_green_trips_2022` green
    WHERE green.pickup_location_id = yellow.pickup_location_id
  )

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option 2: use &lt;code&gt;IN&lt;/code&gt;#
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT DISTINCT yellow.pickup_location_id
  FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2022` yellow
  WHERE yellow.pickup_location_id IN (
    SELECT DISTINCT green.pickup_location_id
    FROM `bigquery-public-data.new_york_taxi_trips.tlc_green_trips_2022` green
  )

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option 3: use &lt;code&gt;INTERSECT DISTINCT&lt;/code&gt;#
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT DISTINCT yellow.pickup_location_id
FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2022` yellow
INTERSECT DISTINCT 
SELECT DISTINCT green.pickup_location_id
FROM `bigquery-public-data.new_york_taxi_trips.tlc_green_trips_2022` green

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Comparison#
&lt;/h3&gt;

&lt;p&gt;The three options all return the same result of the original query, and they all produce a query plan with a semi hash join, with a much better performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total elapsed time&lt;/strong&gt; : 1sec&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slot time consumed&lt;/strong&gt; : 40sec&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While they generate a similarly shaped plan, and in this case produce the same result, they are not the same from a logical point of view.&lt;/p&gt;

&lt;h4&gt;
  
  
  EXISTS#
&lt;/h4&gt;

&lt;p&gt;Option 1 (&lt;code&gt;EXISTS&lt;/code&gt;) is the most flexible, because it lets you write multiple predicates in the WHERE clause. So you can for example write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT DISTINCT yellow.pickup_location_id
  FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2022` yellow
  WHERE EXISTS (
    SELECT 1
    FROM `bigquery-public-data.new_york_taxi_trips.tlc_green_trips_2022` green
    WHERE green.pickup_location_id = yellow.pickup_location_id
    -- add another predicate
    AND green.dropoff_location_id = yellow.dropoff_location_id
  )

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

&lt;/div&gt;



&lt;p&gt;This would still do a &lt;code&gt;SEMI HASH JOIN&lt;/code&gt;, but now we also filter the rows so that the result returns locations where both the pickup and dropoff was the same.&lt;/p&gt;

&lt;p&gt;From a performance point of view, even if this is fast, this still scans &lt;code&gt;83,869,625&lt;/code&gt; rows in the join phase.&lt;/p&gt;

&lt;p&gt;If we want to reduce the number of rows scanned, in this case we can do it with a WITH statement, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WITH green AS (
  SELECT DISTINCT pickup_location_id
  FROM `bigquery-public-data.new_york_taxi_trips.tlc_green_trips_2022`
)
SELECT DISTINCT yellow.pickup_location_id
  FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2022` yellow
  WHERE EXISTS (
    SELECT 1
    FROM green
    WHERE green.pickup_location_id = yellow.pickup_location_id
  )

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

&lt;/div&gt;



&lt;p&gt;This query usually performs a bit faster (for me it's around 900ms), uses slightly less slot seconds (for me about 30s), and scans less rows in the join phase (now &lt;code&gt;36,272,535&lt;/code&gt;). The main difference is that we do a &lt;code&gt;DISTINCT&lt;/code&gt; before joining.&lt;/p&gt;

&lt;p&gt;To reduce the join to a minimum, we can also do one more step and do a distinct on yellow too, and we get&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WITH green AS (
  SELECT DISTINCT pickup_location_id
  FROM `bigquery-public-data.new_york_taxi_trips.tlc_green_trips_2022`
),
yellow AS (
  SELECT DISTINCT yellow.pickup_location_id
  FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2022` yellow
)
SELECT DISTINCT yellow.pickup_location_id
  FROM yellow
  WHERE EXISTS (
    SELECT 1
    FROM green
    WHERE green.pickup_location_id = yellow.pickup_location_id
  )

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

&lt;/div&gt;



&lt;p&gt;Now the performance is even faster (for me around 800ms), uses even less slot seconds (about 20s), and more deterministically we can say that it only scans &lt;code&gt;16,813&lt;/code&gt;in the join phase.&lt;/p&gt;

&lt;h4&gt;
  
  
  IN#
&lt;/h4&gt;

&lt;p&gt;With &lt;code&gt;IN&lt;/code&gt; we are a bit more constrained (unless we do ugly string concatenation things), as we can really only compare one element per statement.&lt;/p&gt;

&lt;p&gt;So to express a query where we want &lt;code&gt;pickup_location_id&lt;/code&gt; and &lt;code&gt;dropoff_location_id&lt;/code&gt; to be the same you'd have to write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT DISTINCT yellow.pickup_location_id
  FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2022` yellow
  WHERE yellow.pickup_location_id IN (
    SELECT DISTINCT green.pickup_location_id
    FROM `bigquery-public-data.new_york_taxi_trips.tlc_green_trips_2022` green
  )
  AND yellow.dropoff_location_id IN (
    SELECT DISTINCT green.dropoff_location_id
    FROM `bigquery-public-data.new_york_taxi_trips.tlc_green_trips_2022` green
  )

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

&lt;/div&gt;



&lt;p&gt;Since this uses two statements, in the &lt;code&gt;JOIN&lt;/code&gt; step in the query it does two joins!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5uwxtxc2l1b4d75s5jrh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5uwxtxc2l1b4d75s5jrh.png" alt="Join Detail, showing that it does two joins" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;IN&lt;/code&gt; is more perfomant than the original &lt;code&gt;EXISTS&lt;/code&gt; query written above, and is more comparable to the &lt;code&gt;EXISTS&lt;/code&gt; query where we use &lt;code&gt;WITH green AS...&lt;/code&gt; to do the select distinct. Also in the &lt;code&gt;IN&lt;/code&gt; case we scan &lt;code&gt;36,272,535&lt;/code&gt;rows (like in the first improved exists).&lt;/p&gt;

&lt;p&gt;We can reach a comparable performance to the second improved exists (the one with &lt;code&gt;WITH green AS..., yellow AS...&lt;/code&gt;) if we do&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WITH yellow AS (
  SELECT DISTINCT yellow.pickup_location_id
  FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2022` yellow
)
SELECT DISTINCT yellow.pickup_location_id
FROM yellow
WHERE yellow.pickup_location_id IN (
  SELECT DISTINCT green.dropoff_location_id
  FROM `bigquery-public-data.new_york_taxi_trips.tlc_green_trips_2022` green
)

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

&lt;/div&gt;



&lt;p&gt;Also here, we scan &lt;code&gt;16,819&lt;/code&gt; rows in the JOIN.&lt;/p&gt;

&lt;p&gt;There is one more caveat with &lt;code&gt;IN&lt;/code&gt;, as sometimes &lt;code&gt;IN&lt;/code&gt; and &lt;code&gt;EXISTS&lt;/code&gt; don't always produce the same result! See &lt;a href="https://alessandromarrella.com/posts/in-vs-exists/" rel="noopener noreferrer"&gt;NOT IN and NOT EXISTS don't always produce the same result&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  INTERSECT DISTINCT#
&lt;/h4&gt;

&lt;p&gt;I'll admit it, I crafted the query so that intersect distinct would make the cut as well, as I particularly like it syntax wise.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;INTERSECT DISTINCT&lt;/code&gt; in our case produces exactly the same result as &lt;code&gt;IN&lt;/code&gt; and &lt;code&gt;EXISTS&lt;/code&gt; but has a different limitation: the columns selected need to be the same in both tables AND the result needs to be distinct (there is no such thing as an &lt;code&gt;INTERSECT ALL&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;From a performance profile, this immediately produces the most efficient result, scanning only &lt;code&gt;16,819&lt;/code&gt; rows in the JOIN (which again, is a &lt;code&gt;SEMI HASH JOIN&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion#
&lt;/h3&gt;

&lt;p&gt;In conclusion, we saw that &lt;code&gt;SEMI HASH JOIN&lt;/code&gt; can be a powerful optimization, especially when dealing with many-to-many relationships, and can be done in several ways. Each method has its strengths and limitations, but in the end the main optimization is the impact on the query plan of moving from a &lt;code&gt;INNER&lt;/code&gt; to a &lt;code&gt;SEMI&lt;/code&gt; join.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>bigquery</category>
      <category>gcp</category>
    </item>
    <item>
      <title>NOT IN and NOT EXISTS don't always produce the same result</title>
      <dc:creator>Alessandro Marrella</dc:creator>
      <pubDate>Sat, 01 Jun 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/amarrella/not-in-and-not-exists-dont-always-produce-the-same-result-3ih2</link>
      <guid>https://dev.to/amarrella/not-in-and-not-exists-dont-always-produce-the-same-result-3ih2</guid>
      <description>&lt;p&gt;&lt;code&gt;IN&lt;/code&gt; and &lt;code&gt;EXISTS&lt;/code&gt; often produce the same result, but when negated and dealing with &lt;code&gt;NULL&lt;/code&gt; values, they behave differently.&lt;/p&gt;

&lt;p&gt;Let's see an example, assume we have these two tables:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;orders&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;order_id&lt;/th&gt;
&lt;th&gt;customer_id&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;50.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;75.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;30.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;20.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;customers&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;customer_id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Martha&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;John&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Ignore the fact that a well formed customers table would need to have the id always specified and ideally as a constraint. This is just an example.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now, let’s say we want to find orders where the customer_id is not present in the customers table.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;NOT EXISTS&lt;/code&gt; we would write something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT *
FROM orders o
WHERE NOT EXISTS (SELECT 1 FROM customers c WHERE c.customer_id = o.customer_id);

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

&lt;/div&gt;



&lt;p&gt;The output, would probably be what we expect:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;order_id&lt;/th&gt;
&lt;th&gt;customer_id&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;20.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Using &lt;code&gt;NOT IN&lt;/code&gt; we would write something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT *
FROM orders
WHERE customer_id NOT IN (SELECT customer_id FROM customers);

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

&lt;/div&gt;



&lt;p&gt;The output here will be empty!&lt;/p&gt;

&lt;p&gt;When we use &lt;code&gt;NOT IN&lt;/code&gt;, SQL checks each value in the orders table against the list of values returned by the subquery. If any value in the subquery result is &lt;code&gt;NULL&lt;/code&gt;, the entire &lt;code&gt;NOT IN&lt;/code&gt; comparison will result in &lt;code&gt;NULL&lt;/code&gt; for each row in the orders table. This is because any comparison with &lt;code&gt;NULL&lt;/code&gt; yields &lt;code&gt;NULL&lt;/code&gt; and &lt;code&gt;NOT IN&lt;/code&gt; needs &lt;strong&gt;all comparisons&lt;/strong&gt; to be &lt;code&gt;TRUE&lt;/code&gt; for a row to be included in the result.&lt;/p&gt;

&lt;p&gt;This is not a problem with &lt;code&gt;NOT EXISTS&lt;/code&gt;, because the &lt;code&gt;NOT EXISTS&lt;/code&gt; clause checks for the non-existence of rows that meet the criteria specified in the subquery. It does not perform direct comparisons with &lt;code&gt;NULL&lt;/code&gt; in the same way &lt;code&gt;NOT IN&lt;/code&gt; does. Instead, it simply checks if there are any rows that match the condition. If no such rows exist, the condition is true.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>bigquery</category>
      <category>gcp</category>
    </item>
    <item>
      <title>How to run and serve a webserver in Google Colab without ngrok</title>
      <dc:creator>Alessandro Marrella</dc:creator>
      <pubDate>Mon, 20 May 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/amarrella/how-to-run-and-serve-a-webserver-in-google-colab-without-ngrok-5a8a</link>
      <guid>https://dev.to/amarrella/how-to-run-and-serve-a-webserver-in-google-colab-without-ngrok-5a8a</guid>
      <description>&lt;p&gt;Today I learned how to run a webserver in Google Colab, without needing external services like ngrok. I'm using Dagster here as an example but any webserver should work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you just want to create a public url for a webserver running in colab you can jump to step 4&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install the Required Packages#
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;!pip install dagster dagster-webserver

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Scaffold a New Dagster Project#
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;!dagster project scaffold --name test

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Run Dagster in the Background#
&lt;/h2&gt;

&lt;p&gt;To run Dagster's webserver, you'll need to start it in the background. This can be done using Python's subprocess module. The following code navigates to the project directory and starts the webserver on port 3000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import subprocess

subprocess.Popen(
    [
        "bash",
        "-c",
        "cd /content/test &amp;amp;&amp;amp; dagster-webserver -h 0.0.0.0 -p 3000 &amp;amp;"
    ]
)

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Create a Public URL for the Webserver#
&lt;/h2&gt;

&lt;p&gt;Google Colab provides a way to create a public URL for your webserver. Use the output.serve_kernel_port_as_window function to expose the webserver running on port 3000.&lt;/p&gt;

&lt;p&gt;This does the real magic. Note that the URL is authenticated, and only the user running the notebook has access to the webserver.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from google.colab import output
output.serve_kernel_port_as_window(3000, path='/')

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

&lt;/div&gt;



&lt;p&gt;you can also run it in an iframe with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output.serve_kernel_port_as_iframe(3000)

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Putting it all together#
&lt;/h2&gt;

&lt;p&gt;You can check out a full example in this &lt;a href="https://colab.research.google.com/drive/1SpHIlpWCeZ9nstRPUAvXLpOcTwt-P-Q9?usp=sharing" rel="noopener noreferrer"&gt;Colab Notebook&lt;/a&gt;&lt;/p&gt;

</description>
      <category>todayilearned</category>
    </item>
  </channel>
</rss>
