<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Kornesh Kanan]]></title><description><![CDATA[Yolo]]></description><link>https://kornesh.com/blog/</link><image><url>http://kornesh.com/blog/favicon.png</url><title>Kornesh Kanan</title><link>https://kornesh.com/blog/</link></image><generator>Ghost 3.35</generator><lastBuildDate>Tue, 14 Apr 2026 21:34:41 GMT</lastBuildDate><atom:link href="https://kornesh.com/blog/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[SIREN: Implicit Neural Representations with Sine]]></title><description><![CDATA[<p>Machine learning is just a function takes encoded representation of input $x$ and maps it to encoded representation of output $y$. Does the way we choose to encode the input has an impact on the model? Can we represent an image better than a grid of pixels with 3 channels</p>]]></description><link>https://kornesh.com/blog/siren-implicit-neural-representations-with-sine/</link><guid isPermaLink="false">6120bc01357e1a0083b462c7</guid><category><![CDATA[Machine Learning]]></category><dc:creator><![CDATA[Kornesh Kanan]]></dc:creator><pubDate>Fri, 26 Jun 2020 18:27:00 GMT</pubDate><content:encoded><![CDATA[<p>Machine learning is just a function takes encoded representation of input $x$ and maps it to encoded representation of output $y$. Does the way we choose to encode the input has an impact on the model? Can we represent an image better than a grid of pixels with 3 channels for RGB? Can we represent an audio clip better than a spectrogram? </p><p>Imagine we hadn't chosen to encode images as grids of pixels, would we be using CNN today? I think this a very intriguing question to understand how much the method of encoding has impacted the algorithm. If you had a different representations, would you come up with different algorithms? Because to me, it's not at all obvious that one particular representation is particularly good or a particularly bad. Maybe there are other better ones.</p><p>Perhaps we can train a neural network to represent them better? That's key idea behind the recent, mind blowing paper <a href="https://vsitzmann.github.io/siren/">SIREN</a>; they encode an image in the weights of a single neural network. Think of overfitting an entire neural network by training it with a single image. The neural network has to learn a compact representation of just a single image. If neural network are capable of learning to differentiate between thousand of image classes in ImageNet, it surely can overfit a single image, right?</p><p>In this paper they used a very simple MLP for this but the conventional non-linear activation functions like ReLU and TanH is replaced with the periodic sine function. Because ReLus are not that great at learning representations.</p><!--kg-card-begin: html-->
<video width="100%" playsinline autoplay loop preload muted>
                    <source src="https://vsitzmann.github.io/siren/img/image_convergence_15s_label.mp4" type="video/mp4">
                </video><!--kg-card-end: html--><p>They also seemed to suggest a initialization scheme in the paper but it seems similar to the default initializer in Tensorflow. Why sine is outperforming ReLU and what's so special about sine?</p><h2 id="what-is-sine">What is sine?</h2><p>I have not bothered to understand or appreciate sine beyond the "SOH CAH TOA" until now. It is beautifully explained in detail at <a href="https://betterexplained.com/articles/intuitive-understanding-of-sine-waves/">here</a>, with some missing pieces. I will attempt to summarize it here.</p><p>Sine is a repeating pattern that is one dimensional. It moves up and down. Starts from 0, moves to 1 and then dives to -1, finally returns to 0. Sine is a gentle back and forth rocking pattern.</p><figure class="kg-card kg-image-card"><img src="https://betterexplained.com/wp-content/uploads/2016/12/Simple_harmonic_motion_animation.gif" class="kg-image"></figure><p>The speed of sine is non-linear, it speeds up &amp; slows down in cycles. Let's say it takes 10 seconds for sine to move from 0 to 1. After the first 5 seconds it would have traveled 70% distance. It will take another 5 seconds to travel the remaining 30%. And going from 98% to 100%, the final 2% takes almost a full second! </p><p>How sine is difference between circles? Just like how squares are examples of lines, circles are examples of sine.</p><p>Let's define $\pi$ as the time sine takes from 0 to 1 and back to 0. Similarly, $\pi$ is the time from 0 to -1 and back to 0. $\pi$ is about returning to center or 0. So it takes 2 * $\pi$ for a full cycle. </p><figure class="kg-card kg-image-card"><img src="https://upload.wikimedia.org/wikipedia/commons/a/a2/Sine.svg" class="kg-image"></figure><h2 id="what-s-special-about-sine">What's special about sine?</h2><p>The derivative of a sine is also a sine — as cosine is just a shifted sine. Whut?</p><p>$ \frac{d}{dx} sin(x) = cos(x) \\ \frac{d}{dx} cos(x) = - sin(x) $</p><p>If we plot the graphs, $cos(x)$ is just $sin(x)$ horizontally shifted by $\frac{\pi}{2}$</p><p>$\begin{aligned} sin(x)  &amp;= cos(x - \frac{\pi}{2}) \\ cos(x)  &amp;= sin(x + \frac{\pi}{2}) \end{aligned}$</p><figure class="kg-card kg-image-card"><img src="https://upload.wikimedia.org/wikipedia/commons/7/71/Sine_cosine_one_period.svg" class="kg-image"></figure><p>None of other commonly used non-linear activation functions has this property. This allows us to not only represent the image itself but its derivatives too!</p><p>Another benefit is neural representations are continuous and sort of has unlimited resolution, just like reality. When we take a picture, the camera sensor is actually sampling reality discretely couple of micrometers apart from one pixel to another. Let's say if you want to know the color of an image at coordinate (x, y), you can't ask for the RGB value the between those two discrete pixels. You'd have a constraint that x and y must be an integer within the range of the image's height and width. Neural representation doesn't have this limitation and you can query what's the color of an image at (1.2, 200.5)? You can query multiple resolutions of the image with the same representation!</p><p>Alright. Let's dive into the code. The original paper was implemented in Pytorch but the following is my attempt to reproduce it in Tensorflow.</p><pre><code class="language-python">import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

in_features = 2
out_features = 1
hidden_features = 256
hidden_layers = 3
outermost_linear = True
first_omega_0=30.
hidden_omega_0=30.

class Siren(keras.layers.Layer):
    def __init__(self, in_features = 2, hidden_features=256, is_first=False, is_linear=False, omega_0=30.):
        super(Siren, self).__init__()
        self.omega_0 = omega_0
        self.is_first = is_first
        self.is_linear = is_linear

        if is_first:
          init = tf.keras.initializers.RandomUniform(minval=-1 / in_features, maxval=1 / in_features)
        else:
          init = tf.keras.initializers.RandomUniform(minval=-np.sqrt(6 / in_features) / omega_0, maxval=np.sqrt(6 / in_features) / omega_0)

        #From https://www.tensorflow.org/guide/keras/custom_layers_and_models
        self.w = self.add_weight(shape=(in_features, hidden_features), initializer=init, trainable=True)
        self.b = self.add_weight(shape=(hidden_features,), initializer="zeros", trainable=True)

    def call(self, inputs):
        if self.is_linear:
          return tf.matmul(inputs, self.w) + self.b
        return tf.sin(tf.multiply(self.omega_0, tf.matmul(inputs, self.w) + self.b))</code></pre><p>That's it, the only different part is we're using the initializer as exactly referenced in the paper. Before we train the model, we need couple of helper functions (which I have not ported over to Tensorflow).</p><pre><code class="language-python">import torch
from torch import nn
from PIL import Image
import skimage
from torchvision.transforms import Resize, Compose, ToTensor, Normalize
import time
import matplotlib.pyplot as plt

def get_mgrid(sidelen, dim=2):
    '''Generates a flattened grid of (x,y,...) coordinates in a range of -1 to 1.
    sidelen: int
    dim: int'''
    tensors = tuple(dim * [torch.linspace(-1, 1, steps=sidelen)])
    mgrid = torch.stack(torch.meshgrid(*tensors), dim=-1)
    mgrid = mgrid.reshape(-1, dim)
    return mgrid

def get_cameraman_tensor(sidelength):
    img = Image.fromarray(skimage.data.camera())        
    transform = Compose([
        Resize(sidelength),
        ToTensor(),
        Normalize(torch.Tensor([0.5]), torch.Tensor([0.5]))
    ])
    img = transform(img)
    return img</code></pre><p>Finally, we can train the model and the output should be close to the input image.</p><pre><code class="language-python">BATCH_SIZE = 8192
EPOCHS = 100
sidelength = 256
img = get_cameraman_tensor(sidelength)
print(img.shape)
pixels = img.permute(1, 2, 0).view(-1, 1)
coords = get_mgrid(sidelength, 2)

strategy = tf.distribute.MirroredStrategy()

with strategy.scope():
  inputs = tf.keras.Input(shape=(2,))
  x = Siren(in_features=2, is_first=True)(inputs)
  x = Siren(in_features=256)(x)
  x = Siren(in_features=256)(x)
  x = Siren(in_features=256)(x)
  outputs = Siren(in_features=256, hidden_features=1, is_linear=True)(x)

  model = keras.Model(inputs=inputs, outputs=outputs, name="Siren")
  model.summary()

  train_dataset = tf.data.Dataset.from_tensor_slices((coords, pixels))
  train_dataset = train_dataset.batch(BATCH_SIZE).cache() #.shuffle(10000)
  train_dataset = train_dataset.prefetch(tf.data.experimental.AUTOTUNE)

  optimizer = tf.keras.optimizers.Adam(learning_rate=1e-4)
  loss = tf.keras.losses.MeanSquaredError(reduction=tf.keras.losses.Reduction.NONE)
  model.compile(optimizer, loss=loss)
  model.fit(train_dataset, epochs=EPOCHS, verbose=0)
  result = model.predict(train_dataset)
  plt.imshow(result.reshape(256,256))</code></pre>]]></content:encoded></item><item><title><![CDATA[Setting up nginx-ingress without Google Load Balancer in GKE]]></title><description><![CDATA[<p>It's nice to have a load balancer to dynamically distribute incoming traffic across nodes inside Kubernetes cluster. However, it is a bit of overkill when you are running your side projects on GKE with a single node and it costs additional ~$20 a month for an otherwise inexpensive cluster.</p><p>We</p>]]></description><link>https://kornesh.com/blog/setting-up-gke-nginx-ingress-without-google-loadbalancer/</link><guid isPermaLink="false">5ee9fe3840800900f48bdee9</guid><category><![CDATA[Kubernetes]]></category><dc:creator><![CDATA[Kornesh Kanan]]></dc:creator><pubDate>Wed, 17 Jun 2020 12:45:14 GMT</pubDate><content:encoded><![CDATA[<p>It's nice to have a load balancer to dynamically distribute incoming traffic across nodes inside Kubernetes cluster. However, it is a bit of overkill when you are running your side projects on GKE with a single node and it costs additional ~$20 a month for an otherwise inexpensive cluster.</p><p>We could avoid this by running ingress controller on <code>hostPort</code> which exposes http ports via the host IP. Changing the service type to <code>NodePort</code> prevents GCP from creating forwarding rules in Google Load Balancer. Using nginx-ingress Helm chart we can easily achieved this by setting the values as below: </p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">controller:
  kind: DaemonSet
  daemonset:
    useHostPort: true
  service:
    type: NodePort</code></pre><figcaption>values.yml</figcaption></figure><p>I'm not a big fan of Helm charts in general, so here I'm just using it to generate the yaml configurations and then manually apply it using  <code>kubectl</code>.</p><pre><code class="language-bash">helm fetch stable/nginx-ingress --untar --untardir nginx
helm template nginx/nginx-ingress --name nginx-ingress --values values.yml &gt; k8s.yml
kubectl apply -f k8s.yml</code></pre><p>Ideally, in a production system I would recommend you to use a load balancer as the cost out weights the benefits.</p>]]></content:encoded></item><item><title><![CDATA[Programmatically accessing Google Sheets via Service Account]]></title><description><![CDATA[<p>I was working on automation that involves reading Google Sheets from server side and then writing some values back.</p><p>Initially this seemed straight forward but led me to waste a couple of hours pursuing the false start of "Domain Wide Delegation" which would allow anyone impersonate as a user without</p>]]></description><link>https://kornesh.com/blog/programmatically-accessing-google-sheets-via-service-account/</link><guid isPermaLink="false">610be65dd3d10e0091bfbe19</guid><category><![CDATA[DevBytes]]></category><dc:creator><![CDATA[Kornesh Kanan]]></dc:creator><pubDate>Tue, 19 May 2020 13:41:00 GMT</pubDate><content:encoded><![CDATA[<p>I was working on automation that involves reading Google Sheets from server side and then writing some values back.</p><p>Initially this seemed straight forward but led me to waste a couple of hours pursuing the false start of "Domain Wide Delegation" which would allow anyone impersonate as a user without the whole UI based OAuth2 flow. Turns out you don't actually need this.</p><p>Instead, simply create a service account and then share your Google Sheet with email address of that service account (<code>sa-name@project-id.iam.gserviceaccount.com</code>) just like how you would share a document with an actual person.</p><p>Now, the official docs are pretty confusing and vague on how to authenticate Google Sheets using a service account. Ironically, after ton of Googling, I finally manage to include Google Sheet's access scope with the service account in Python API client via <code>from_service_account_file</code>.</p><pre><code class="language-python">from googleapiclient.discovery import build
from google.oauth2 import service_account
import pandas as pd
from datetime import datetime

credentials = service_account.Credentials.from_service_account_file(
    '/tmp/serviceaccount.json', scopes=['https://www.googleapis.com/auth/spreadsheets'])
service = build('sheets', 'v4', credentials=credentials)
sheet = service.spreadsheets()

sheetId = 'XXXXXXXXXXXXXXXXX'
tabName = 'Sheet1'

# Read
result = sheet.values().get(spreadsheetId=sheetId, range=tabName).execute()
values = result.get('values', [])
df = pd.DataFrame(values)
print(df)

# Write
data = df.values.tolist()
data.append(["Updated at", datetime.now().isoformat()])
result = sheet.values().update(spreadsheetId=sheetId, range=tabName, valueInputOption='RAW', body={
    'values': data
}).execute()</code></pre><p>This reminds me of a favorite quote of my colleague</p><blockquote>Straight forward does not mean easy.</blockquote><p>Hope this helps someone save a couple of hours.</p>]]></content:encoded></item><item><title><![CDATA[On GKE Cluster Management Fee]]></title><description><![CDATA[<blockquote>On June 6, 2020, your Google Kubernetes Engine (GKE) clusters will start accruing a management fee, with an exemption for all Anthos GKE clusters and one free zonal cluster. ...</blockquote><p>I was utterly disappointed when I received this email last night. It introduces additional $75 per month per cluster. Apparently, I</p>]]></description><link>https://kornesh.com/blog/on-gke-cluster-management-fee/</link><guid isPermaLink="false">5efb012940800900f48bdf97</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Google Cloud Platform]]></category><dc:creator><![CDATA[Kornesh Kanan]]></dc:creator><pubDate>Thu, 05 Mar 2020 09:09:00 GMT</pubDate><content:encoded><![CDATA[<blockquote>On June 6, 2020, your Google Kubernetes Engine (GKE) clusters will start accruing a management fee, with an exemption for all Anthos GKE clusters and one free zonal cluster. ...</blockquote><p>I was utterly disappointed when I received this email last night. It introduces additional $75 per month per cluster. Apparently, I wasn't the only one who felt this way, folks at <a href="https://news.ycombinator.com/item?id=22485625">HackerNews</a> were furious. Some of them were willing to migrate their entire stack off GCP and others would even go to extremes of rolling out their own DIY cluster. However, after going through the thread, I think this fee is fairly justifiable. </p><p>I discovered people were creating tons of small clusters purely for isolation purposes without realizing the additional overhead that it implies. This fee will be a slap on the wrist (or face in my case) and will encourage teams to create larger clusters instead and use proper isolations at namespace level; perhaps injuntiction with Istio and gVisor.</p><p>This new pricing will match it’s AWS counterpart, EKS, which already charges $0.1 per hour per cluster. Given the maturity and feature sets on GKE far outweighs EKS, it is not completely unreasonable. Plus, there’s one free cluster exempted from this fee, per account.</p><p>In the thread, I noticed people tend to get irrational when they’re upset and feel powerless. Instead of fixing their existing architecture they would rather do something completely irrational. Weirdly enough, I could relate them. I think we sometimes tend resist change under the false clause of stability. Clearly, I’m guilty of this too; something for me to reflect on.</p><p>Also I discovered a gem on <a href="https://cloud.google.com/kubernetes-engine/docs/best-practices/enterprise-multitenancy">multi-tenancy on GKE,</a> hidden deep in the thread.</p>]]></content:encoded></item><item><title><![CDATA[10x faster RoBERTa tokenizer with Custom Tokens support]]></title><description><![CDATA[<p>For my Question Answering Kaggle competition, I wanted to experiment replacing the BERT model with RoBERTa. This means I need to reencode and retokenize the entire <a href="https://ai.google.com/research/NaturalQuestions/">Natural Questions</a> dataset into TFRecords. This process was already taking hours with the WordPiece tokenizer used for the BERT models. RoBERTa uses a faster</p>]]></description><link>https://kornesh.com/blog/10x-faster-roberta-tokenizer-with-custom-tokens-support/</link><guid isPermaLink="false">61054827e6154c0088729093</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[Tensorflow]]></category><dc:creator><![CDATA[Kornesh Kanan]]></dc:creator><pubDate>Tue, 21 Jan 2020 20:16:00 GMT</pubDate><content:encoded><![CDATA[<p>For my Question Answering Kaggle competition, I wanted to experiment replacing the BERT model with RoBERTa. This means I need to reencode and retokenize the entire <a href="https://ai.google.com/research/NaturalQuestions/">Natural Questions</a> dataset into TFRecords. This process was already taking hours with the WordPiece tokenizer used for the BERT models. RoBERTa uses a faster and language agnostic tokenizer called SentencePiece. However, in my experiment, SentencePiece tokenizer was significantly slower and would have took close to 12 hours to complete if I had let it continue.</p><p>Fortunately, I came across <a href="https://github.com/huggingface/tokenizers">HuggingFace’s Rust tokenizer</a> which was 10x faster but it is still in the early days and doesn't support Custom Tokens out of the box. While you might (rightly) think I clickbaited you (as I didn't actually write the 10x Rust tokenizer), I did wrote a wrapper on top of the Rust implementation to support Custom Tokens for separating Questions <code>[Q]</code> and Answers <code>[A]</code>. This might seem straightforward but can really tricky implement it right, if you're not aware how all these tokenizers actually work underneath the abstractions. Hope this helps someone.</p><pre><code class="language-python">import json
from transformers import RobertaTokenizer

from tokenizers import Tokenizer, pre_tokenizers, decoders
from tokenizers.models import BPE, WordPiece

class CustomRobertaTokenizer:
    def __init__(self, path):
        self.tokenizer = RobertaTokenizer.from_pretrained(path)
        vocab = path+"/vocab.json"
        merges = path+"/merges.txt"
        # Create a Tokenizer using BPE
        self.rust = Tokenizer(BPE.from_files(vocab, merges))
        # Use ByteLevel PreTokenizer
        self.rust.pre_tokenizer = pre_tokenizers.ByteLevel.new(add_prefix_space=True)
        # Use ByteLevel Decoder
        self.rust.decoder = decoders.ByteLevel.new()

        with open(path+'/added_tokens.json', 'r') as f:
            self.added_token = json.load(f)

        special_tokens = {"bos_token": "&lt;s&gt;", "eos_token": "&lt;/s&gt;", "unk_token": "&lt;unk&gt;", "sep_token": "&lt;/s&gt;", "pad_token": "&lt;pad&gt;", "cls_token": "&lt;s&gt;", "mask_token": "&lt;mask&gt;"}
        # self.special_token_map = {v: k for k, v in special_tokens.items()}

        for k, v in special_tokens.items():
            self.added_token[v] = k
            setattr(self, k, v)

    def tokenize(self, txt, add_prefix_space=True):
        streams = []
        tmp = []
        for w in txt.split():
            # print(w, tmp, streams)
            # if w in self.added_token or w in self.special_token_map:
            if w in self.added_token:
                if len(tmp) != 0:
                    streams.append(tmp)
                    tmp = []
                streams.append(w)
            else:
                tmp.append(w)

        # print(streams)
        if len(tmp) != 0:
            streams.append(tmp)
            tmp = []

        bpes = [" " + " ".join(x) for x in streams if isinstance(x, list)]

        # print("bpes", bpes)
        bpes_result = self.rust.encode_batch(bpes)
        # print(bpes_result)

        final_result = []
        for w in streams:
            if isinstance(w, list):
                final_result.extend(bpes_result.pop(0).tokens)
            else:
                final_result.append(w)
        return final_result

    def convert_tokens_to_ids(self, tokens):
        return self.tokenizer.convert_tokens_to_ids(tokens)</code></pre><p>And you can use it as below:</p><pre><code class="language-python"># Provide the custom tokens [Q] and [A] in added_tokens.json
txt = "&lt;s&gt; [Q] Who founded Google? [A] Google was founded on September 4, 1998, by Larry Page and Sergey Brin while they were Ph.D. students at Stanford University in California. &lt;/s&gt;"

#Default tokenizer
tokenizer = RobertaTokenizer.from_pretrained('./nq-vocab')

# With Custom Tokenizer support
tokenizer = CustomRobertaTokenizer('./nq-vocab')
print(tokenizer.tokenize(txt))
</code></pre><p>Also, remember that the SentencePiece tokenizer requires additional effort for post processing as we don't know what encoding rules were used. We have to use some heuristics like longest-common-substring algorithm to map the outputs to the input tokens but this is not guaranteed to work everytime. I'm not sure how others have solved this problem.</p><p>You can get an excellent overview of various tokenizers from <a href="https://blog.floydhub.com/tokenization-nlp/">here</a> and <a href="https://huggingface.co/transformers/tokenizer_summary.html">here</a>.</p>]]></content:encoded></item><item><title><![CDATA[Supporting clients that does not support SNI in EKS]]></title><description><![CDATA[<p>SNI is something that's been enabled by default for most modern browsers and http clients. It allows us to serve multiple different SSL certs on the same IP address and TCP port. This is incredibly useful for multi tenancy in EKS when used along side Nginx Ingress Controller and cert-manager.</p>]]></description><link>https://kornesh.com/blog/supporting-clients-without-sni-in-eks/</link><guid isPermaLink="false">610d5d8ed3d10e0091bfc09d</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[DevBytes]]></category><dc:creator><![CDATA[Kornesh Kanan]]></dc:creator><pubDate>Tue, 12 Nov 2019 16:06:00 GMT</pubDate><content:encoded><![CDATA[<p>SNI is something that's been enabled by default for most modern browsers and http clients. It allows us to serve multiple different SSL certs on the same IP address and TCP port. This is incredibly useful for multi tenancy in EKS when used along side Nginx Ingress Controller and cert-manager. Unsurprisingly, there are still some legacy clients that do not support this feature. When such clients make a request to an endpoints that uses SNI to route requests to their respective services (think multi tenancy), we might be getting a SSL connection error.</p><pre><code class="language-bash">$ java -Djsse.enableSNIExtension=true SSLPoke example.com 443 # works
$ java -Djsse.enableSNIExtension=false SSLPoke example.com 443 # SSL Error</code></pre><p>To support such clients, we just need to give the endpoint a dedicated IP address (or set it as default) and avoid using any services that require SNIs like Nginx Ingress Controller. There are couple of options to achieve this in AWS.</p><h2 id="service-level-classical-elb">Service level Classical ELB</h2><p>The most straight forward option is to simply create a <em>Service</em> level ELB with <code>type: LoadBalancer</code>. You can use <code>external-dns</code> annotation to link the ELB to a domain in Route53. Or you can manually create a CNAME record in Route53 and map a subdomain (<em>eg</em><code>non-sni.example.com</code>) to the ELB that will be provisioned  (<em>eg</em><code>elb-url.us-west-2.elb.amazonaws.com</code>). The following is all yaml we need for this method to work.</p><pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  annotations:
    #external-dns.alpha.kubernetes.io/hostname: non-sni.example.com
    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-west-2:0000:certificate/XXXX
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
    service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https"
  labels:
    app: frontend
  name: frontend-proxy-svc
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 8080
    name: http
    protocol: TCP
  - port: 443
    targetPort: 8080
    name: https
    protocol: TCP
  selector:
    app: frontend
</code></pre><p>Note that we are completely bypassing Nginx Ingress Controller and cert-manager here as that would require us to create a dedicated Kubernetes <em>Ingress</em> level ELB.</p><pre><code>www.example.com -&gt; ELB -&gt; ingress-nginx + cert-manager -&gt; service

non-sni.example.com -&gt; ELB -&gt; service
</code></pre><ul><li>Requires <em>Service</em> level ELB</li><li>We need dedicated ACM cert, we can't use Let's Encrypt</li><li>Requires manual CNAME record update every time ELB changes, can cook up some bash script to automated it tho</li><li>Additional ELB cost</li><li>Terminates SSL at ELB level</li></ul><h2 id="ingress-level-alb">Ingress level ALB</h2><p>This option has a high maintenance cost if you're not already using ALB as we need to install additional <em>Operator</em> called <a href="https://github.com/kubernetes-sigs/aws-alb-ingress-controller">AWS ALB Ingress Controller</a>.</p><ul><li>Still needs a dedicated SSL cert</li></ul><blockquote>Although the AWS Application Load Balancer (ALB) is a modern load balancer offered by AWS that can can be provisioned from within EKS, at the time of writing, the alb-ingress-controller; is only capable of serving sites using certificates stored in AWS Certificate Manager (ACM). <a href="https://docs.cert-manager.io/en/latest/tutorials/venafi/securing-ingress.html">source</a></blockquote><ul><li>Requires manual CNAME record updates (according to <a href="https://docs.cert-manager.io/en/latest/tutorials/venafi/securing-ingress.html">the only cert-manager docs on ALB</a>) but probably can be achieved using <code>external-dns</code></li><li>Incurs additional cost of ALB</li></ul><pre><code class="language-yaml">apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  labels:    
    app: frontend
  name: frontend-proxy-ingress
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-west-2:0000:certificate/XXXX
spec:
  tls:
  - hosts:
    - "non-sni.example.com"
    secretName: ca-star-example-com-key-pair
  rules:
  - host: "non-sni.example.com"
    http:
      paths:
      - path: /
        backend:
          serviceName: frontend-proxy-svc
          servicePort: 80
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: frontend
  name: frontend-proxy-svc
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 8080
    name: http
    protocol: TCP
  selector:
    app: frontend
</code></pre><h2 id="ingress-level-classical-elb">Ingress level classical ELB</h2><p>Another potential solution but I didn't spend time investigating this.</p><h2 id="how-we-could-have-done-it-in-gcp">How we could have done it in GCP</h2><p>I can't help myself but to compare how this whole ordeal could have been so much easier in GCP. Simply create a reserved static IP named <code>frontend-proxy-static-ip</code></p><pre><code class="language-bash">$ gcloud compute addresses create frontend-proxy-static-ip --global 
$ gcloud compute addresses describe frontend-proxy-static-ip --global --format 'value(address)'
# 35.186.228.000</code></pre><p>Attach the static IP to <em>Ingress</em> by just adding one line annotation <code>kubernetes.io/ingress.global-static-ip-name</code>. <em>Service</em> just needs the usual<code>type: NodePort</code>. Then we create an <em>A</em> Record pointing to reserved static IP from <code>non-sni.example.com</code>.</p><pre><code class="language-yaml">apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  labels:    
    app: frontend
  name: frontend-proxy-ingress
  annotations:
    certmanager.k8s.io/cluster-issuer: ca-issuer-ent-frontend-com
    kubernetes.io/ingress.global-static-ip-name: frontend-proxy-static-ip
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  tls:
  - hosts:
    - "non-sni.example.com"
    secretName: ent-default-ssl-certificate
  rules:
  - http:
      paths:
      - path: /
        backend:
          serviceName: frontend-svc
          servicePort: 80
</code></pre><ul><li>No load balancers are involved</li><li>We can still use Let's Encrypt certs via cert-manager, we don't need dedicated certs</li><li>No additional cost, in-use static IPs are free</li><li>No need to create additional service, note that we're using <code>frontend-svc</code> instead of <code>frontend-proxy-svc</code></li></ul>]]></content:encoded></item><item><title><![CDATA[Understanding Computational Model of Quantum Computers]]></title><description><![CDATA[<p>When I look at computers I would at least have some intuition how it all works in theory. There was bunch of theories we had to learn in university to reason with the mathematical models of it's computation. However, I had no idea theoretically how the quantum computers are supposed</p>]]></description><link>https://kornesh.com/blog/understanding-computational-model-of-quantum-computers/</link><guid isPermaLink="false">5ee20c80a3320900f4a291f8</guid><category><![CDATA[Quantum]]></category><dc:creator><![CDATA[Kornesh Kanan]]></dc:creator><pubDate>Mon, 07 Oct 2019 10:51:00 GMT</pubDate><content:encoded><![CDATA[<p>When I look at computers I would at least have some intuition how it all works in theory. There was bunch of theories we had to learn in university to reason with the mathematical models of it's computation. However, I had no idea theoretically how the quantum computers are supposed to be faster. So I went down the rabbit hole and I want to share what I have learnt so far before I forget it all.</p><h1 id="the-intuition">The intuition</h1><p>Most explanations on how quantum computers work are usually presented from a physicist's perspective. We often hear that quantum computers have qbits which can be both 0 and 1 at the same time, called a superposition. And when we measure it using a filter, it collapses to either 0 or 1.</p><p>Based on that explanation itself, I got the wrong idea that quantum computers can represent 3 distinct states any given time. But this doesn't really explain how it would outperform classical computers by a substantial margin. In complexity theory terms, the ability to represent n bits in $2^n$ vs $3^n$ isn't that significant.</p><p>The important piece of information that's often missing is that each qbits has some <em>probability</em> to be 0 and some <em>probability</em> to be 1 at the same time -- in other words a qbit can be both 0 and 1 with some <strong>probability</strong> to collapse to either one state when we measure it. We exploit this quantum property in our favor.</p><p>Trying to explain how quantum computers work with metaphors and analogies (like in pop science articles) makes it hard for us grasp the intuition behind quantum computers. I think our natural language is not equipped to deal with the level of intricacy required in the quantum world. So let's start with something more formal.</p><h2 id="single-cbit-vs-single-qbit">Single cbit vs Single qbit</h2><p>Let's consider a classical bit (cbit) with the value 0 which can be written as $\begin{pmatrix} 1 \\ 0\end{pmatrix}$ or $| 0 \rangle$ using Dirac vector notation. A cbit with the value 1 can be written as $\begin{pmatrix} 0 \\ 1\end{pmatrix}$ or $| 1 \rangle$. Formally, a cbit can represented by  $\begin{pmatrix} a \\ b \end{pmatrix}$  where $a$ and $b$ are numbers and $\lVert a \rVert^2 + \lVert b \rVert^2 =  1$. </p><p>We can use the same notation to represent quantum bits (qbit). However, the qbits lives in a richer space where it exists as both 0 <em>and</em> 1 over a probabily of each bit collapsing to one <em>or</em> another. We denote that probability coefficients instead of binary 0s and 1s.</p><p>For example, a qbit $\begin{pmatrix} 1 \\ 0 \end{pmatrix}$  has a 100% chance of collapsing to 0, and a qbit $\begin{pmatrix} 0 \\ 1 \end{pmatrix}$  has a 100% chance of collapsing to 1. Similarly,  a qbit $\begin{pmatrix} 0.25 \\ 0.75 \end{pmatrix}$  has a 25% chance of collapsing into 0 and 75% chance of collapsing into 1. Accordingly, a qbit $\begin{pmatrix} 0.5 \\ 0.5 \end{pmatrix}$  has a 50% chance of collapsing to either 0 or 1.  We can summarize this as, if a qbit has value $\begin{pmatrix} a \\ b \end{pmatrix}$ then it collapses to 0 with probability $\lVert a \rVert^2$ and 1 with probability $\lVert b \rVert^2$.</p><h2 id="two-cbits-vs-two-qbits">Two cbits vs two qbits</h2><p>Two cbits can exist in any one of $|00\rangle, |01\rangle, |10\rangle or |11\rangle$ states; it's important to emphasise that <em>only one</em> of the those state can exist at any given time. It's probable states can represented as the tensor products as shown below.<br>$| 00 \rangle = \begin{pmatrix} 1 \\ 0 \end{pmatrix} \otimes \begin{pmatrix} 1 \\ 0 \end{pmatrix} = \begin{pmatrix} 1 \\ 0 \\ 0 \\ 0 \end{pmatrix}$</p><p>$| 01 \rangle = \begin{pmatrix} 1 \\ 0 \end{pmatrix} \otimes \begin{pmatrix} 0 \\ 1 \end{pmatrix} = \begin{pmatrix} 0 \\ 1 \\ 0 \\ 0 \end{pmatrix}$</p><p>$| 10 \rangle = \begin{pmatrix} 0 \\ 1 \end{pmatrix} \otimes \begin{pmatrix} 1 \\ 0 \end{pmatrix} = \begin{pmatrix} 0 \\ 0 \\ 1 \\ 0 \end{pmatrix}$</p><p>$| 11 \rangle = \begin{pmatrix} 0 \\ 1 \end{pmatrix} \otimes \begin{pmatrix} 0 \\ 1 \end{pmatrix} = \begin{pmatrix} 0 \\ 0 \\ 0 \\ 1 \end{pmatrix}$</p><p>A single qbit can exist as both  $|0\rangle$ and  $|1\rangle$. Two qbits exists in as all four states  $|00\rangle, |01\rangle, |10\rangle and |11\rangle$ simultaneously 🤯. For example, qbits $\begin{pmatrix} 0.5 \\ 0.5 \end{pmatrix}$ and $\begin{pmatrix} 0.25 \\ 0.75 \end{pmatrix}$ exists in all four possible states  $|00\rangle, |01\rangle, |10\rangle, |11\rangle$ simultaneously with probability of $0.125, 0.375, 0.125$ and $0.375$ respectively.</p><p>$\begin{pmatrix} 0.5 \\ 0.5 \end{pmatrix} \otimes \begin{pmatrix} 0.25 \\ 0.75 \end{pmatrix} = \begin{pmatrix} 0.125 \\ 0.375 \\ 0.125 \\ 0.375 \end{pmatrix}$</p><p>2 cbits contains 2 bits of information. 2 qbits contains 4 bits of information that represents the current state of the system -- ie you need to give 4 pieces of information (the four coefficients $0.125, 0.375, 0.125$ and $0.375$) to describe the current state.</p><h2 id="eleven-cbits-vs-eleven-qbits">Eleven cbits vs eleven qbits</h2><p>If you have $n$ qbits you get to work with $2^n$ probabilities.  So every single additional qbit doubles the number of probabilities you get work with. Let's see why this is a big deal.</p><p>Imagine if you have 11 cbits and 11 qbits. With 11 cbits, we can store a single number at any given time, eg. 2019 as $|11111100011\rangle$. With 11 qbits, we can store all the numbers from 1 to 2048 ($2^{11}$) simultaneously, over a probability distribution. With just 300 qbits we could perform more calculations at once than there are atoms in the observable universe 🤯 🤯.</p><h2 id="operations-on-cbits-vs-qbits">Operations on cbits vs qbits</h2><p>When we instruct a quantum computers to do something, we're just changing these probabilities to favor some computation that we're trying to do. We can combine multiple primitive logic gates like AND, OR, XOR and NAND to perform operations on cbits to compute almost anything. Similarly, we can use quantum logic gates like NOT, CNOT, Z and Hadamard gate to perform quantum operations. By carefully manipulating the probabilities so that correct answers are amplified and incorrect ones cancel out, a quantum algorithm can solve exponential problems in polynomial time, eg. in simulating complicated chemical interactions.</p><p>We will have to take some classical bits, put them into quantum computer, do a bunch of quantum operations. And at the end of it, we can collapse them to zero or one, so that our whole computation is deterministic. We cannot measure all the states in a quantum computer but we can measure few final states to get the answer that we are after. We usually run the program multiple times to gather statistics to deal with the noise caused by stability issues with current gen quantum computers.</p><p>Quantum computers are in still in their early days and there are plenty of Turing Awards waiting to be collected.</p>]]></content:encoded></item><item><title><![CDATA[Improving slow EFS throughput when copying thousands of small files]]></title><description><![CDATA[<p>We were in the process of migrating our customers from a private cloud to AWS EKS. During our dry-runs, we noticed Customer A with 21GB worth of files had it transferred in 32 minutes from a local disk in the private cloud to EFS. However, it took us 2.5</p>]]></description><link>https://kornesh.com/blog/extremely-slow-efs-throughput/</link><guid isPermaLink="false">610d3fcdd3d10e0091bfbf68</guid><category><![CDATA[DevBytes]]></category><dc:creator><![CDATA[Kornesh Kanan]]></dc:creator><pubDate>Wed, 25 Sep 2019 14:58:00 GMT</pubDate><content:encoded><![CDATA[<p>We were in the process of migrating our customers from a private cloud to AWS EKS. During our dry-runs, we noticed Customer A with 21GB worth of files had it transferred in 32 minutes from a local disk in the private cloud to EFS. However, it took us 2.5 hours to transfer Customer B with only 12GB worth of files. </p><p>This shouldn't be an issue if we were only planning to migrate one or two customer instances per week. Furthermore, we have a pretty narrow weekly deployment window that is only a few hours long. In order to migrate more customers in the same week, we had to do something about this bottleneck.</p><p>First, I repeated the dry-run to make sure this is not a one off event. It wasn't. However this time, I noticed that the EFS throughput was not reaching anywhere near the dedicated maximum throughput that we had allocated, which was 40Mbps.</p><p>So I ran couple of benchmarks on EFS using the fio tool. Strangely now the throughput was actually hitting the max.</p><p>It finally hit me and I checked the file counts for both customers using a simple <code>find /data | wc -l</code>. Turns out Customer A only had a total of 10,000 files whereas Customer B had a whopping 122,000 files!</p><p>I realized rsync and cp are a single threaded processes and we’d need to parallelize it using something like GNU Parallel. After some googling, came across AWS’s EBS to EFS throughput guide. In which they suggested the use of fpart + cpio + GNU Parallel for optimal performance.</p><p>fpart is a tool that gets of list of all files in a directory and splits the list into equal sized partitions based on number of files and total file size. We could then take the output lists and feed it into individual rsync or cpio threads to speed up copying process. So each thread would have similar workloads.</p><p>However, fpart binary was not readily available and I couldn't find any docker images that had it. So I had to create our own docker image and build it from source.</p><pre><code class="language-docker">FROM ubuntu:18.04

RUN apt-get update &amp;&amp; \
apt-get install -y build-essential autoconf rsync ca-certificates cpio parallel nload &amp;&amp; \
gcc --version &amp;&amp; make --version

ADD https://github.com/martymac/fpart/archive/fpart-1.1.0.tar.gz /

# Install fpart
RUN tar -xvzf /fpart-1.1.0.tar.gz &amp;&amp; \
ls -lsh / &amp;&amp; \
cd /fpart-fpart-1.1.0/ &amp;&amp; \
autoreconf -i &amp;&amp; \
./configure &amp;&amp; \
make &amp;&amp; \
make install &amp;&amp; \
PATH=$PATH:/usr/local/bin &amp;&amp; which fpart &amp;&amp; fpart -V

# Test fpart
RUN export THREADS=$(($(nproc --all) * 16)) &amp;&amp; \
echo $THREADS &amp;&amp; \
ls -lsh /home &amp;&amp; \
fpsync -n $THREADS -v -o "-achv --delete" /fpart-fpart-1.1.0/ /home/ &amp;&amp; \
ls -lsh /home &amp;&amp; \
rm -rf /home/* &amp;&amp; \
ls -lsh /home

CMD ["/bin/sh"]</code></pre><p>I couldn't get the exact command that was mentioned in the AWS guide to work  – even after hours of trial and error. We finally settled for using just fpsync (rsync with fpart) for simplicity. fpsync is part of the fpart package that we have built in the docker image above.</p><pre><code class="language-bash">export THREADS=$(($(nproc --all) * 16))
echo $THREADS
fpsync -n $THREADS -v -o "-achv --delete" /tmp/data /data</code></pre><p>Even with this suboptimal solution, we managed to reduce the EFS copying time from 2h 30mins to just 20 mins.</p><p>Ideally we should move these files to S3 and serve them from there but this requires application level changes and we don't have that kind of bandwidth right now.</p>]]></content:encoded></item></channel></rss>