I build retrieval systems and fine-tuned models, then push them from a rough baseline to something I can actually measure. Every project here has a before/after you can check yourself, not just numbers I'm asking you to trust.
open to ml / llm engineering rolesabout
Software and ML engineer. I ship systems, then measure whether they actually work.
I'm a CS grad who's built production SaaS back-ends and ML models, everything from microservices and GraphQL APIs to generative-AI reporting and resume-parsing systems. The parts I actually enjoy are the ones tutorials skip: a bug that only shows up once you're on a real GPU, a metric that won't move and makes you figure out why, a dumb heuristic that quietly beats the fancy model.
The two case studies in ./projects both started as rough prototypes and ended with numbers attached. I kept the failures in the writeups too, since that's usually where I actually learned something.
skills
Toolkit
education & certifications
- TensorFlow Developer Professional CertificateDeepLearning.AI
- Generative AI with Large Language ModelsDeepLearning.AI
- Deep Learning SpecializationDeepLearning.AI
- Quantum Computing Fundamentals · Introduction to Quantum Computing · Quantum Algorithms for Cybersecurity, Chemistry & OptimizationMIT xPRO
- Data Analytics using Deep LearningNUS x HPE
contact
Let's build something.
Open to ML / LLM engineering roles. The fastest way to reach me is below.
01 · retrieval-augmented generation
Proving that RAG retrieval actually got better, not just assuming it did.
A document retrieval pipeline built in three stages, scored with RAGAS against a frozen eval set the whole way through. Every improvement here is a number, not a vibe.
the problem
A RAG agent is only as good as the chunks it retrieves.
Feed the model the wrong passages and it'll answer confidently anyway, just from the wrong source, which is exactly the kind of thing that kills trust in an AI assistant. Naive RAG (embedding similarity by itself) is the usual starting point, and it misses a whole class of queries without anyone noticing.
The interesting question isn't whether RAG works, it's how much better retrieval can get, and whether you can actually show it. That's what this project tests, with a controlled three-stage experiment over a car dealership knowledge base (inventory, financing, warranty policies).
the method
Three stages, one frozen measuring stick
Same 26-question eval set at every stage. The retriever is the only thing that changes, so any difference in score comes from that change alone.
Dense retrieval only
Embed the query and pull the nearest chunks by vector similarity. This is where every RAG system starts, and the score to beat.
Dense plus BM25 sparse, fused
Add keyword retrieval and fuse the two rankings together. Embeddings don't handle rare tokens well, a stock number or promo code has no real semantic neighborhood, but keyword search catches them exactly.
Cross-encoder reranking
Re-score the fused candidates with a cross-encoder that reads the query and chunk together, then keep the top results. This fixes ordering problems, cases where the right chunk got retrieved but buried too far down the list.
every stage, measured
Four RAGAS metrics across three stages
All four panels share the same vertical axis (0.70 to 0.95) so you can compare them directly, no need to take my word for the trend.
The two retrieval metrics keep climbing through reranking. The two generation metrics peak at hybrid and then hold steady, reranking changes which chunks show up and in what order, but the generator was already handling things fine once the right chunk made it into context. Either way, all four end up above the naive baseline.
try it yourself
Retrieval Explorer
These are real outputs from the actual pipeline, running entirely in your browser. Pick a question and flip between the three strategies to see how the retrieved chunks and RAGAS scores shift, or just search the knowledge base yourself with live BM25.
what each stage buys
Two changes, two distinct wins
Catches what embeddings miss
Ask for the price on stock #U-2214, or the APR on promo code SUMMER26, and dense embeddings struggle. Those tokens are rare enough that they don't have a real semantic neighborhood, so the embedding blurs them. BM25 just matches them exactly.
On exact-match questions, context precision jumps from 0.83 to 0.96. A gate keeps BM25 from firing on conversational queries, so 19 of 26 questions still retrieve identically to naive. Basically a free win, nothing gets worse elsewhere.
Fixes the ordering
The cross-encoder reads each query/chunk pair together instead of scoring them separately, so it judges relevance much more precisely than a raw similarity score. That lifts context precision by +0.05 and recall by +0.03 over hybrid.
Paraphrase recall goes from 0.88 to 1.00, picking up the one question the earlier two stages both missed. Generation metrics barely move, which makes sense, the fix happened upstream in retrieval, not in generation.
findings, not marketing
What I learned by looking at the failures
A simple rule beat an always-on feature
Running BM25 on everything hurts paraphrase-style queries, it just adds keyword noise. Gating it to only fire on rare-token queries means hybrid falls back to naive exactly where BM25 would've hurt, which shows up as identical retrieved contexts on 19 of 26 questions.
The reranker can't reason about a set
It scores each chunk on its own, so it can't optimize for a pair of chunks that only answer a two-document question together. Multi-document precision is the weakest number here (0.71), and at this point that's an expected ceiling, not something I'm still puzzling over.
Relevance models reward topical overlap
Ask whether the Silver plan covers alignments, and the cross-encoder ranks the Gold-plan chunk higher, just because it literally contains the word "alignments." Both chunks still make it to the generator so the answer stays correct, but it's a good example of what a reranker is actually optimizing for.
I don't trust the scores more than they deserve
The RAGAS judge here is a small 7B model. It's noisy, I saw it score byte-identical contexts 1.0 in one run and 0.5 in another, and it's from the same model family as the generator, which isn't ideal. I treat these numbers as directional, not gospel. Swapping in a frontier model as judge is a one-line config change, and the answers are already cached for it.
02 · llm fine-tuning · qlora
Teaching a support-agent model to actually sound like the brand it works for.
I fine-tuned an open model on real support conversations so replies come out in a consistent, on-brand voice, and measured the before/after instead of just eyeballing it.
the problem
Off-the-shelf models are competent but generic.
Every off-the-shelf deployment ends up sounding like the same assistant. For a customer-facing agent, what actually differentiates you isn't a bigger model, it's getting it to sound like your specific support team.
Fine-tuning gets you there, and doing it with LoRA keeps it cheap at scale. The 3-billion-parameter base stays frozen and shared across brands, each brand's voice is just a 57 MB adapter you swap in, rather than shipping a separate 5.7 GB model per brand. Merge the adapter into the weights before deployment and there's no added latency at inference time.
it measurably learned
Loss falls fast, then converges
Supervised fine-tuning on roughly 25k support exchanges, formatted into the model's own chat template. That flat tail at the end is exactly why I capped the run at 500 steps rather than grinding through a full epoch for basically nothing.
the proof
Same held-out question. Base model vs fine-tuned.
None of these prompts showed up in training. The base model is competent but flat. The fine-tune answers like someone who actually works for the company.
"Is it possible to check the termination charges?"
"Yes, you can check termination charges by contacting your current service provider directly. They will have the most accurate information based on your specific plan and usage."
"I'm on the same page, you'd like to check the termination charges. To assist you with this, please provide your account details or specific information related to your contract, and I'll give you accurate, up to date charges for your situation."
That one exchange is basically the whole business case. The base model tells the customer to go contact their provider, it just hands them off. The fine-tune keeps the conversation and owns the resolution instead, and that difference is the whole point of doing this.
"I am trying to see your accepted payment methods."
"Hello! Our accepted payment methods include credit cards (Visa, Mastercard), debit cards, and PayPal. For more detailed information or assistance, feel free to ask!"
"Thank you for reaching out! I completely understand you'd like to explore our accepted payment methods. We offer major credit and debit cards (Visa, Mastercard, Amex), PayPal, bank transfers, Apple Pay, and Google Wallet. If you have questions about any of these, I'm here to help. Your satisfaction is our priority."
The fine-tune also picked up the training data's {{placeholder}} convention for entity names, expected, and I flag it in the limitations below.
try it yourself
Before / After Explorer
Real generations from the base model and the fine-tuned adapter on held-out prompts the model never trained on. Pick a customer message to compare.
what changed
Four repeatable dimensions of voice
| dimension | base model | fine-tuned |
|---|---|---|
| Voice | Third party, neutral | First person "we, us, our", speaks as the company |
| Openers | Jumps straight to the answer | Acknowledges first, "I'm happy to help" |
| Ownership | Deflects, "contact your provider" | Retains the customer and owns the resolution |
| Close | Ends abruptly | Reassures, "your satisfaction is our priority" |
engineering, not a tutorial
It wasn't a clean run, and honestly that's the more useful part to write up.
I validated the data and training logic first with a small 0.5B stand-in model. The actual 4-bit path then surfaced a handful of failures that only show up on real GPU hardware. I traced each one back through the stack trace instead of guessing.
Rejected the conventional wisdom
The usual advice is to force fp16 on an older GPU, but that path uses a gradient scaler that crashed on this model's bf16 gradients. The giveaway: forward and backward passes both completed fine before the crash, so the GPU clearly handled bf16 okay. Switching to bf16 end-to-end removed the scaler entirely and the crashes stopped.
Found a 5 GB memory spike
In-loop evaluation was materializing full vocabulary logits, about 152k classes, for an entire batch at once, which blew up GPU memory. I moved evaluation out of the training loop and switched it to token-by-token generation instead.
A stale dependency broke adapter loading, after 5 hours
Pinned the environment and recovered the finished run without retraining, just by re-running evaluation separately.
Made it finish in a single session
Ran it headless as a batch job with a step cap based on where loss actually plateaued, so the whole thing finishes reliably without me babysitting it.
03 · more projects
Other things I have built
Deep-learning model for detecting brain tumors, comparing a few different feature-extraction approaches. Got to 97.13% accuracy with a 1.67% false-negative rate.
Cross-platform mobile app for reporting local crime with photo/video evidence. Has SOS alerts, one-tap calls to police or ambulance, directions to the nearest station or hospital, live location sharing, and area-safety prediction.
A study companion students talk to out loud instead of typing into. Ask a doubt about your coursework by voice and get walked through it in a live back-and-forth conversation, the same way you'd ask a tutor in person.
experience
Experience
- Built and deployed the end-to-end RAG pipeline for a CV-screening platform, letting recruiters filter candidates by arbitrary natural-language criteria (skills, domain experience, project history) via semantic search over parsed resume documents.
- Designed the document ingestion and embedding layer, parsing, chunking, and indexing uploaded CVs into a vector store for fast semantic retrieval across candidate profiles.
- Developed the AI interviewer module that automatically conducts structured screening interviews for shortlisted candidates, handling question generation, response evaluation, and conversation state.
- Took both systems from prototype to production, owning deployment, error handling, and reliability for live recruiter traffic.
- Ran applied research spikes on retrieval, reranking, and evaluation strategies to inform architecture decisions on adjacent products.
- Designed and implemented microservices architecture for scalable, high-availability SaaS platforms, reducing server downtime by 50%.
- Built and optimized RESTful APIs and GraphQL endpoints, cutting response time by 35% and enabling real-time data retrieval for dynamic reporting and analytics dashboards.
- Implemented serverless, event-driven functions, increasing system efficiency by 20% and lowering maintenance overhead.
- Integrated generative AI models for content creation, automating client-specific report generation and boosting content production efficiency by 60%.
- Developed and deployed client-tailored ML models, including an advanced resume-parsing system that improved candidate-matching accuracy by 45%.
- Supervised and mentored the back-end team, improving productivity by 30%; led code reviews and established best practices that cut production issues by 40%.
- Managed sprint planning and cross-functional coordination for on-time delivery, and engineered secure, compliant data pipelines for large-scale client data.
- Analyzed marketing and client data, identifying trends that increased client conversion rates by 25%.
- Ran extensive A/B testing and data modeling, optimizing marketing strategies and improving client retention by 30%.
- Built machine learning models for resume parsing, improving candidate-matching accuracy and recruitment processes.
- Implemented predictive analytics and reporting dashboards, delivering actionable insights for decision-making.
- Performed data cleansing and ETL, ensuring data integrity, and automated reporting to cut report generation time by 50%.
- Segmented customers with K-means clustering for a portfolio-management project, enabling more tailored investment strategies.
- Built a CNN-LSTM deep-learning model for stock-market analysis and prediction, achieving a high forecasting accuracy rate.
- Led a small team in data preprocessing and feature engineering, ensuring data quality across tasks, and collaborated with researchers to refine model accuracy.
- Led creative marketing strategy and social content production, increasing brand engagement by 40%.
- Served as lead photographer and cinematographer, producing visually compelling campaign content.
- Directed creative across digital and print, aligning outputs with marketing goals and driving a 25% increase in lead generation.