Phase 01 — PyTorch & TensorFlow Framework Internals
Difficulty: ⭐⭐⭐⭐☆
Estimated Time: 4 weeks (80–100 hours)
Roles Supported: All — this is the bedrock phase. Every downstream topic (quantization, torch.compile, NPU deployment) requires internalizing what happens below the Python API.
Why This Phase Exists
Every Senior Staff ML engineer who influences Qualcomm's framework stack will be asked — in an interview or on the first week of the job — to trace execution from loss.backward() down to the CUDA kernel. If you can't do that, you cannot meaningfully review PRs touching the dispatcher, write a custom operator that behaves correctly under torch.compile, or diagnose why a quantized model produces NaN after a specific graph transformation.
PyTorch's execution model has three distinct layers that every practitioner conflates: the Python eager frontend, the dispatcher (C++ c10), and the backend kernels. TensorFlow's execution model has evolved through three paradigms (eager, tf.function + AutoGraph, tf.experimental.mlir_bridge) that coexist in production code. Understanding both frameworks at this depth is what separates framework users from framework engineers.
This phase will make you dangerous enough to write test cases that expose correctness bugs in framework patches, write custom ops that are safe under all dispatch keys (CPU, CUDA, autograd, torch.compile, functional), and reason about TF's tracing semantics when a colleague reports that "the model gives different results in graph mode."
Concepts
- PyTorch dispatch stack: Python → C++ dispatcher (
c10::Dispatcher) → dispatch keys (CPU, CUDA, Autograd, Functionalize, Meta) → kernel registration viaTORCH_LIBRARY/TORCH_LIBRARY_IMPL - Autograd engine: DAG construction during forward pass,
AccumulateGrad,Engine::execute(), gradient accumulation,retain_graph,create_graphfor higher-order gradients torch.autograd.Function: customforward/backward,save_for_backward, composite functions, double backward- Eager vs compiled mode: what
torch.compilechanges at the boundary —TorchDynamocaptures bytecode into FX IR,TorchInductorlowers to Triton/C++ torch.fxsymbolic tracing:Interpreter,Transformer,Graph,Node, proxy mechanism, limitations (control flow, data-dependent shapes)- Custom C++ operators:
TORCH_LIBRARYschema,TORCH_LIBRARY_IMPL, pybind11 vstorch::Library,OpCheck, structured kernels - TF graph execution:
Session.run()(TF1) vstf.function+tf.Graph(TF2),ConcreteFunction,FuncGraph, AutoGraph transformation rules tf.functiontracing semantics: Python-side effects,tf.Variablevs Python variables, retracing triggers (new input signatures, Python closures)- TF SavedModel format:
tf.saved_model.save,SignatureDef, asset handling, interop with TFLite/ONNX - Memory management: PyTorch caching allocator, CUDA unified memory, TF tensor lifetimes in sessions vs eager
- Numerical precision: FP32 vs FP16 vs BF16 accumulation, catastrophic cancellation, Kahan summation, determinism flags
- Operator decomposition: how PyTorch's
_decompregistry breaks complex ops into primitive ops for portability
Labs
Lab 01 — Custom Autograd Engine from Scratch
| Field | Value |
|---|---|
| Goal | Implement a mini automatic differentiation library (minigrad) that supports reverse-mode autodiff over a computation graph, passing numerical gradient checks against PyTorch on 15 standard test cases |
| Concepts | Reverse-mode AD, topological sort, Function subclassing, Tensor wrapper, backward() dispatch |
| Steps | 1. Implement Tensor class wrapping np.ndarray with grad, grad_fn, requires_grad; 2. Implement Function base class with forward, backward, save_for_backward; 3. Implement add, mul, matmul, relu, softmax, log_softmax, cross_entropy as Function subclasses; 4. Implement backward() via topological sort + reverse traversal; 5. Implement no_grad() context manager; 6. Run gradcheck style numerical verification vs PyTorch |
| Stack | Python 3.10, NumPy 1.26, PyTorch (for verification only) |
| Datasets | Synthetic: random MLPs on MNIST features (loaded from sklearn) |
| Output | minigrad/ package; all 15 gradient checks pass within 1e-5 relative error |
| How to Test | python test_lab.py — runs gradcheck on all Function subclasses; trains a 2-layer MLP for 100 steps and validates loss decreases monotonically |
| Talking Points | Why topological sort? What breaks with cycles? How does PyTorch's C++ engine differ from this pure Python version? When would you use create_graph=True? |
| Resume Bullet | Implemented reverse-mode AD engine from scratch in Python; passed numerical gradient checks for 15 op types within 1e-5 relative tolerance, demonstrating mastery of PyTorch autograd internals |
| Extensions | Add second-order gradient support; implement vmap style batched AD; add XLA-style HLO emission |
Lab 02 — Custom C++ PyTorch Operator
| Field | Value |
|---|---|
| Goal | Write a custom warp_softmax C++ operator registered via TORCH_LIBRARY, including a CPU kernel (SIMD-friendly), a CUDA kernel (one warp per row), and metadata for torch.compile compatibility |
| Concepts | TORCH_LIBRARY, TORCH_LIBRARY_IMPL, dispatch keys, at::Tensor, structured kernels, OpCheck, pybind11, CMake build system, torch.compile compatibility rules |
| Steps | 1. Define operator schema in TORCH_LIBRARY; 2. Implement CPU kernel with vectorized exp+sum; 3. Implement CUDA kernel (32 threads/warp, __shfl_xor_sync reduction); 4. Register both impls via TORCH_LIBRARY_IMPL; 5. Write pybind11 PYBIND11_MODULE wrapper; 6. Add Abstract (Meta) kernel for torch.compile shape propagation; 7. Run torch.library.opcheck(); 8. Benchmark vs torch.nn.functional.softmax |
| Stack | C++17, CUDA 12.x, CMake 3.20+, PyTorch 2.3+ C++ extension API, torch.utils.cpp_extension |
| Datasets | Synthetic: torch.randn(batch, seq_len, vocab_size) tensors |
| Output | warp_softmax.so loadable via torch.ops.load_library(); 10–20% latency improvement vs baseline on sequence length ≥ 4096 |
| How to Test | pytest test_lab.py — tests: (1) output matches F.softmax within 1e-6, (2) OpCheck passes all dispatch keys, (3) works under torch.compile, (4) gradients match via gradcheck |
| Talking Points | What is a dispatch key and why does order matter? What is the Meta kernel and why does torch.compile require it? How does TORCH_LIBRARY_IMPL(..., CUDA) interact with device placement? |
| Resume Bullet | Wrote production-ready custom CUDA softmax op in C++/CUDA registered via TORCH_LIBRARY; passed opcheck on all dispatch keys and achieved 1.18x speedup over F.softmax at vocab=32K |
| Extensions | Add torch.amp (AMP) support; add FP8 kernel path; add torch.vmap batch support via vmap dispatch key |
Lab 03 — TF tf.function Tracing & Graph Analysis
| Field | Value |
|---|---|
| Goal | Build a tool that (a) traces a TF2 model under tf.function, (b) visualizes the concrete FuncGraph node-by-node, (c) identifies retracing triggers, and (d) exports to TFLite and validates accuracy delta |
| Concepts | tf.function, AutoGraph, ConcreteFunction, FuncGraph, tracing vs execution, Python side effects, input_signature, reduce_retracing, SavedModel, TFLite conversion |
| Steps | 1. Implement a trace_and_inspect(fn, inputs) utility that captures the FuncGraph, counts nodes by op type, and prints a summary; 2. Demonstrate 3 retracing trigger scenarios (new shape, new Python value, new closure); 3. Build a custom BERT-tiny model with tf.function on call(); 4. Use tf.saved_model.save then reload and verify outputs match; 5. Convert to TFLite (dynamic range quant) and measure accuracy delta on MNLI dev set; 6. Annotate the FuncGraph with compute cost estimates |
| Stack | TensorFlow 2.15, TFLite 2.15, Python 3.10, tensorflow_datasets |
| Datasets | MNLI dev set (from tensorflow_datasets); BERT-tiny weights from HuggingFace |
| Output | graph_inspector.py tool; FuncGraph visualization (JSON + text); MNLI accuracy: FP32 baseline vs TFLite dynamic-range within 0.5% |
| How to Test | python test_lab.py — checks: retracing count matches expected, FuncGraph node count is deterministic, TFLite output matches TF output within 1e-4 for 100 test samples |
| Talking Points | When does tf.function retrace vs reuse? What are tf.Variable constraints inside traced code? How does AutoGraph transform Python if/for? When is TFLite conversion not lossless? |
| Resume Bullet | Built TF2 graph analysis tool that identifies retracing triggers and validates TFLite conversion accuracy; demonstrated <0.3% MNLI accuracy degradation under dynamic-range quantization |
| Extensions | Add MLIR bridge analysis (tf.experimental.mlir_bridge); profile XLA compilation cost; instrument with TF Profiler |
Deliverables Checklist
-
minigradpackage with all 15 gradient checks passing -
warp_softmax.sowith OpCheck passing and benchmarks documented - TF graph inspector tool with FuncGraph visualization output
-
All
test_lab.pysuites pass:pytest phase-01-*/lab-0*/test_lab.py -
Personal notes in each
HITCHHIKERS-GUIDE.mdsection annotated with your insights
Interview Relevance
- "Explain how PyTorch's
backward()traverses the computation graph." (you can draw the DAG, explain topological sort, explain AccumulateGrad nodes) - "What is a dispatch key in PyTorch? How does
torch.compileuse it?" (you can explain Meta kernels, Functionalize key, and why OpCheck tests all of them) - "When does
tf.functionretrace?" (you can enumerate all 5 retracing scenarios from memory after Lab 03) - "How would you debug a NaN appearing after quantization in only graph mode?" (you have the mental model to distinguish Python-side bugs from kernel-side bugs)
- "Write a custom backward for a fused attention operation." (Lab 01 gives you the foundations to extend on a whiteboard)