Deep Learning for Product Engineers
Learn deep learning choices that matter most for product behavior and reliability.
Most engineers who use deep learning models do not train them from scratch. They fine-tune, adapt, or call via API. But the engineers who ship the best ML-powered products are the ones who understand enough about how these models work to make good architecture and debugging decisions. This article covers what matters - not for research, but for building.
Architecture Choices That Affect Your Product
When you choose a model, you are making implicit engineering decisions. The architecture affects speed, memory, and what the model is good at.
Encoder-only (BERT, RoBERTa): Best for tasks where you need a single vector representation of a text - classification, semantic search, embeddings. Faster and cheaper per inference than generative models. No generation capability.
Decoder-only (GPT-4, Llama, Mistral): Best for generation tasks. Autoregressive: each token depends on all previous tokens. Slow per token. KV cache makes longer contexts more expensive.
Encoder-decoder (T5, BART): Best for transformation tasks - translation, summarization, structured extraction where input and output are different lengths.
pythonfrom transformers import pipeline # Classifier: encoder-only (fast, cheap) classifier = pipeline("text-classification", model="distilbert-base-uncased-finetuned-sst-2-english") result = classifier("This product is excellent") # [{'label': 'POSITIVE', 'score': 0.9998}] # Embedder: encoder-only (returns vectors, not tokens) from sentence_transformers import SentenceTransformer embedder = SentenceTransformer("BAAI/bge-small-en-v1.5") vector = embedder.encode("This product is excellent") # numpy array of shape (384,) # Generative: decoder-only (flexible, expensive) generator = pipeline("text-generation", model="Qwen/Qwen2.5-1.5B-Instruct")
Use the simplest model that works. A distilbert classifier runs in 5ms; GPT-4o runs in 2,000ms. For binary classification, the encoder wins on every dimension except flexibility.
Batch Size Effects
Batch size affects training stability and throughput. For inference, it affects GPU utilization.
pythonimport torch import time def benchmark_batch_size(model, tokenizer, texts, batch_sizes): model.eval() results = {} with torch.no_grad(): for bs in batch_sizes: batch = texts[:bs] inputs = tokenizer(batch, padding=True, truncation=True, max_length=128, return_tensors="pt").to(model.device) start = time.perf_counter() for _ in range(20): _ = model(**inputs) elapsed = (time.perf_counter() - start) / 20 results[bs] = { "latency_ms": elapsed * 1000, "throughput_rps": bs / elapsed, } return results
A batch size of 1 wastes GPU parallelism. A batch size of 32 on a 40ms workload adds 40ms of queuing time but quadruples throughput. For async workloads, always batch. For interactive requests, serve individually or with a short dynamic batching window (5–10ms).
Overfitting Signals
Overfitting is easy to see if you are looking. The training loss keeps going down; the validation loss levels off or starts rising.
pythondef plot_training_curves(train_losses, val_losses): import matplotlib.pyplot as plt epochs = range(1, len(train_losses) + 1) plt.figure(figsize=(8, 4)) plt.plot(epochs, train_losses, 'b-', label='Train loss') plt.plot(epochs, val_losses, 'r-', label='Val loss') plt.xlabel('Epoch') plt.ylabel('Loss') plt.title('Training curves - watch for val loss plateau or rise') plt.legend() plt.tight_layout() plt.show() # Warning signs: # val_loss rises while train_loss falls → overfitting # both plateau early → learning rate too low or insufficient data # both diverge → learning rate too high
For fine-tuning, typical remedies are: reduce epochs (early stopping), add dropout, use a smaller learning rate, or add more training data. LoRA fine-tuning is particularly resistant to overfitting because it trains very few parameters.
When to Use a Smaller Model
Bigger is not always better. Use a smaller model when:
-
Latency is your constraint. A 1.5B model is 10x faster than a 13B model. For classification or extraction where you have labeled data, fine-tune the small model.
-
You have domain-specific labeled data. A fine-tuned 7B model often outperforms GPT-4o on narrow tasks with a training set of 500+ examples.
-
Cost matters. Serving a local 1.5B model costs 10/1M input tokens. At 10M daily tokens, that is 100/day.
python# Quick quality check: compare models on your task from openai import OpenAI import json test_cases = [ {"input": "Order #1234 was damaged", "expected": "complaint"}, {"input": "Where is my package?", "expected": "tracking"}, {"input": "I love your service!", "expected": "positive"}, ] def evaluate_classifier(model_name: str, test_cases: list) -> float: client = OpenAI() correct = 0 for case in test_cases: resp = client.chat.completions.create( model=model_name, messages=[ {"role": "system", "content": "Classify as complaint/tracking/positive. Return JSON: {\"label\": ...}"}, {"role": "user", "content": case["input"]}, ], response_format={"type": "json_object"}, ) label = json.loads(resp.choices[0].message.content).get("label") correct += (label == case["expected"]) return correct / len(test_cases)
Benchmark your specific task. The cost of running this evaluation is $0.01. The cost of deploying the wrong model is weeks of latency debugging.
Common Mistakes
Using generative models for classification tasks. If your task has a fixed output space, a classifier is almost always faster, cheaper, and more accurate. Use generative models for open-ended tasks.
Not setting a max_length. Models with no truncation will silently handle long inputs differently across versions. Always set max_length and truncation=True.
Ignoring the tokenizer's behavior. Different tokenizers split text differently. "ChatGPT" might be 1 token in one tokenizer and 3 in another. This affects cost estimates, context utilization, and chunking strategies.
Where to Go Next
See also: [transformers-attention-in-practice], [inference-optimization-performance], [transformer-inference-context-engineering]
Stay in the loop
Get new ML/AI lessons in your inbox.
No account needed. We will send curriculum updates, launch notes, and practical learning resources.
Related Posts
More postsFine-Tuning and Post-Training: LoRA, SFT, DPO, and Reasoning RL
What actually happens after pretraining, and when you should do any of it yourself. Parameter-efficient fine-tuning with LoRA, supervised fine-tuning data, preference optimization, and the reinforcement learning recipe behind reasoning models, with a decision framework and a project you can run on one GPU.
LLM Context Windows: What They Mean for System Design
Context window size shapes every architectural decision in LLM applications. This post covers how to reason about context allocation, the limits that still matter even with large windows, and the patterns that scale.
Common ML Architectures Reference: CNN, RNN, Transformer, MoE
A concise technical reference for the neural network architectures that power modern ML - what each one does, how it works, when to use it, and what to watch out for.