My Work

Language models from scratch, LLM-driven game agents, computer vision, and tools that explain how they work.

01

Deep learning

LLM from Scratch

In progress: fine-tuning for classification

A GPT-2 style decoder-only transformer written by hand in PyTorch, following Sebastian Raschka's Build a Large Language Model From Scratch.

PythonPyTorchtiktokenJupyter
params
124M
layers
12
heads
12
emb dim
768
vocab
50,257
context
1,024

How it works

  1. 1Text is tokenized with GPT-2's BPE vocabulary and sampled into input/target pairs with a sliding window.
  2. 2Multi-head causal self-attention, LayerNorm, GELU and the feed-forward block are each implemented as their own nn.Module, not imported.
  3. 3Pretrained on unlabeled text with next-token prediction; a sibling repo, shakespeareGPT, trains the same architecture on Shakespeare.
src/GPT.py
class MultiHeadAttention(nn.Module):
    def forward(self, x):
        b, num_tokens, d_in = x.shape

        keys = self.W_key(x)  # Shape: (b, num_tokens, d_out)
        queries = self.W_query(x)
        values = self.W_value(x)

        # (b, num_tokens, d_out) -> (b, num_heads, num_tokens, head_dim)
        keys = keys.view(b, num_tokens, self.num_heads, self.head_dim).transpose(1, 2)
        queries = queries.view(b, num_tokens, self.num_heads, self.head_dim).transpose(1, 2)
        values = values.view(b, num_tokens, self.num_heads, self.head_dim).transpose(1, 2)

        # Scaled dot-product attention with a causal mask
        attn_scores = queries @ keys.transpose(2, 3)
        mask_bool = self.mask.bool()[:num_tokens, :num_tokens]
        attn_scores.masked_fill_(mask_bool, -torch.inf)

        attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
        attn_weights = self.dropout(attn_weights)

        context_vec = (attn_weights @ values).transpose(1, 2)
        context_vec = context_vec.contiguous().view(b, num_tokens, self.d_out)
        return self.out_proj(context_vec)
02

Interactive explainer

Inside the Transformer

A step-by-step walkthrough of what GPT-2 does to a prompt, from raw text to the next-token distribution. Type a sentence and follow it through the model.

JavaScriptHTMLCSSNo framework
stages
11
dependencies
0

How it works

  1. 1Eleven stages: BPE tokenization, vocabulary lookup, token and positional embeddings, the transformer block, Q·K·V self-attention, the feed-forward network, the 12-layer stack and the output distribution.
  2. 2Every stage renders tensors at GPT-2's real shapes (768-dim embeddings, 12 heads, 50,257-token vocabulary).
  3. 3The numbers are deterministic illustrations, not a live model: the goal is to show the mechanics, not to run inference.
03

Games + LLM agents

Court of Whispers

A political espionage RPG where every NPC is an LLM agent with its own goals. You have five days to talk four of them into a coup while one of them gathers proof against you. Built with Usaib Ahmed.

Godot 4GDScriptTanStack StartCloudflare WorkersLLM API
agents
4
days
5
whispers / day
5
targets
Web + Android

How it works

  1. 1The Godot client only handles input, rendering and physics. A stateless server on Cloudflare Workers generates dialogue and is the arbiter of game state.
  2. 2Each conversation moves trust, suspicion and proof scores; the win condition is computed server-side and synced to the client at day transitions.
  3. 3NPCs run a physics-driven state machine: bounded wander zones, navigation-mesh snapping on spawn and displacement-based stuck recovery.
  4. 4Agent replies are capped to a short sentence, which keeps the cost of several concurrent LLM calls per player under control.
04

Computer vision

Physiotherapy Guidance System

A doctor records an exercise once; the system learns the correct form from that video and then scores a patient's reps live from their webcam.

Next.jsTypeScriptMediaPipe PoseSupabaseK-MeansOne Euro Filter
landmarks
33 × 3D
inference
in-browser
form score
0 to 100

How it works

  1. 1MediaPipe Pose Landmarker extracts 33 3D landmarks per frame in the browser; a custom One Euro Filter removes jitter without lagging fast movement.
  2. 2Joint and segment angles are computed from the landmarks, then K-Means clusters the doctor's reference video into a sequence of exercise states with expected angle ranges.
  3. 3The patient's live angles are matched to those states, per-rep deviation is normalized against each angle's range and rolled into a form score.
  4. 4Feedback comes back as audio cues and a skeleton overlay, and sessions are stored in Supabase.
pipeline
Webcam feed
  -> MediaPipe Pose Landmarker      L ∈ ℝ^(33×3)
  -> One Euro Filter                adaptive smoothing
  -> Angle computation              θ_joint, θ_segment
  -> K-Means state matching         s*(t) = argmin |x_primary − μ_primary|
  -> Deviation vector               ε(t), normalized per angle range
  -> Form score                     F = max(0, 100 − mean(‖ε‖₁) / 2)
  -> Feedback                       audio cues + skeleton overlay
05

Tooling

gif2ascii

Converts animated GIFs into ASCII animations, entirely in the browser. Nothing is uploaded; a Rust CLI is in the works.

TypeScriptReactCanvas APIgifencRust (CLI, WIP)
char ramp
@%#*+=-:.
server
none

How it works

  1. 1Each frame is drawn to a canvas and downsampled to a character grid.
  2. 2Per-cell brightness is 0.299R + 0.587G + 0.114B, shifted by the luminosity setting and stretched around the midpoint by the contrast setting.
  3. 3Brightness is normalized against the frame's own min and max, then mapped onto a 10-character ramp.
  4. 4Frames are re-encoded to a GIF with gifenc, or exported as text.
gif2ascii animated demo

Design

Side quests

Ghost Pastel Omarchy theme screenshot

Ghost Pastel

A dark pastel theme for Omarchy with a companion VS Code theme.

Featured on

Pastel plant site inspiration board

Fictional plant/farm site

A pastel farm and plant-themed site concept, rebuilt as an interactive HTML page: toggle the theme, drag pots, pick seed cards and plant the grid. A personal design exercise, not a real product.

Archive

Everything else

Time Venturers - Text Based RPG

Time Venturers - Text Based RPG

Time Venturers is a text-based role-playing game set in the future. You've been pulled into the year 2094 — but you don't know who did it or why. Your mission? Figure it out as you explore this strange new world!

Python
Neutral - Detecting subconscious biases

Neutral - Detecting subconscious biases

Flags possible bias in hiring decisions: Gemini parses the resume into features, a logistic regression model trained on a Kaggle hiring dataset scores the decision, and LIME shows the reasoning.

GeminiLogistic RegressionLIMEStreamlit
Pethia

Pethia

Sassiest discord bot known to man

PythonDiscord.pyExternal APIs
Bool

Bool

One of the first things I ever coded. Bool is just a simple rule based bot bud

Python
Relic

Relic

Search through an entire historical archive of data to correlate events from the past to history

PHPMySQLNode.js
Gold Fish

Gold Fish

Tool to track, manage, and store expenses

StreamlitPython
Portfolio v3

Portfolio v3

The third iteration of my personal portfolio, focusing on modern UI/UX and storytelling

Next.jsReactTailwind CSSFramer Motion
MalamaAI- Skin Disease Detection

MalamaAI- Skin Disease Detection

MalamaAI detects skin diseases using ml model based off of dinov2 deployed on a webapp built with Next.js and Flask, with Llama 3.3 70B on top for the analysis.

MLNext.jsFlaskLlama+1
Accessible UI

Accessible UI

An agent that iteratively edits a site's design to make it more accessible for people with conditions like dyslexia or arthritis.

LangChainLangflowPython
After School

After School

A practical guide to essential life skills rarely taught in school — from social dynamics and time management to sustainable living and mental health.

Vertex AIReact
Tic Tac Toe AI

Tic Tac Toe AI

An unbeatable Tic-Tac-Toe AI that uses Minimax with Alpha-Beta pruning. It thinks ahead, plays smart, and never loses — ever.

PythonSearchMinimax
Portfolio v2

Portfolio v2

Second iteration - I ditched it because it look wayy too generic

ReactNextjsTypeScript