LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 177 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/8
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/11
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageClustering and PCACore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/24
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernanceData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/16
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/19
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning Agent System Design
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnComputing FoundationsGit, Shell, Linux for AI
⚙️EasyMLOps & Deployment

Git, Shell, Linux for AI

Build a reproducible AI repo with safe Git defaults, a clean-clone eval gate, shell diagnostics, and Linux process habits.

11 min read
Learning path
Step 1 of 177 in the full curriculum
Docker for Reproducible AI

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

Before Python, Docker, or tests, you need a repo that survives a fresh clone. This baseline includes safe Git defaults, one reproducible eval command, shell checks that tell you what machine and dataset you're using, and Linux habits that keep long jobs alive.

One tiny access-request eval file runs through the whole lesson. Another machine should be able to clone the repo, run one command, and get the same 0.667 result instead of "it worked on my laptop." Git's snapshot model is built so a clean clone gets the same tracked files and history.[1]Reference 1Pro Git (2nd ed.)https://git-scm.com/book/en/v2 Identical eval behavior still needs an explicit environment contract (activation script, pinned deps, and later a container), not the Git tree alone.

Tracked vs local-only artifacts for access-rag: eval fixture and activate/repro scripts travel with a clean clone, while .venv, .env, and Git hooks stay machine-local; laptop and GPU box both print 2/3 correct and score 0.667.
Follow the three-row access-request eval.jsonl. On the laptop it becomes a committed, gated artifact. On the GPU box the same three rows produce the same 0.667 because the environment contract (gitignore, activation script, shell helpers) traveled with the repo.

Start with the smallest useful repo

Create a new directory and initialize Git as you would for any real AI project.

terminal
1mkdir access-rag && cd access-rag 2git init

The .git directory is the repository's memory. Everything that follows will be tracked or explicitly ignored.

The .gitignore that protects AI work

Create .gitignore with the patterns that real LLM projects need:

.gitignore
1# Python 2__pycache__/ 3*.py[cod] 4*$py.class 5.venv/ 6env/ 7ENV/ 8 9# Environment & secrets (do not commit these) 10.env 11.env.local 12*.pem 13secrets/ 14 15# Generated model and vector artifacts that do not travel with the repo 16checkpoints/ 17models/local/ 18*.pt 19*.pth 20chroma/ 21faiss_index/ 22*.db 23*.sqlite3 24 25# OS and editor noise 26.DS_Store 27.idea/ 28.vscode/ 29*.swp 30 31# Evaluation caches that should be regenerated 32eval_cache/ 33runs/ 34wandb/ 35mlruns/ 36 37# Do not ignore *.gguf, *.safetensors, or *.bin here. 38# .gitattributes routes those extensions through Git LFS when configured.

Track large files with Git LFS (Large File Storage) so the repo stays small while model weights travel with the project when needed. GitHub warns on files over 50 MiB and hard-blocks any single file over 100 MiB; LFS replaces the file in history with a small pointer and stores the bytes separately.[2]Reference 2About large files on GitHubhttps://docs.github.com/en/repositories/working-with-files/managing-large-files/about-large-files-on-github A path can't be both ignored and LFS-tracked, so the LFS-managed extensions stay out of .gitignore; generated checkpoints and local model directories remain ignored. Git LFS is a separate tool, so check for it first. If it isn't installed yet, don't add those model files. Without git lfs pull (or with GIT_LFS_SKIP_SMUDGE=1), clones keep tiny pointer files that look like valid paths until a weight load fails. Production datasets often outgrow LFS quotas; then use object-store manifests or tools such as DVC, and keep only the pointer/manifest in Git.

terminal-2
1cat > .gitattributes << 'EOF' 2# Install Git LFS before committing model weights: 3# git lfs track "*.gguf" "*.safetensors" "*.bin" 4EOF 5 6if command -v git-lfs >/dev/null 2>&1; then 7 git lfs install 8 git lfs track "*.gguf" "*.safetensors" "*.bin" 9else 10 echo "Git LFS is not installed. Safe for now: do not commit model weights yet." 11fi 12 13git add .gitattributes

Commit the skeleton.

terminal-3
1git add .gitignore .gitattributes 2git commit -m "chore: initial AI project skeleton with safe .gitignore and LFS"

This repo can be cloned without leaking keys or placing a 7 GB model blob in ordinary Git history. When LFS-managed weights exist, document whether setup should fetch them immediately or only for workflows that need them.

The eval that must travel with the code

Place the three-row access-request evaluation file that the rest of the curriculum will reuse.

terminal-4
1mkdir -p eval 2cat > eval/access_requests.jsonl << 'EOF' 3{"prompt": "Access request 101 status?", "expected": "approved", "prediction": "approved"} 4{"prompt": "Access request 102 status?", "expected": "blocked", "prediction": "escalated"} 5{"prompt": "Access request 103 status?", "expected": "restored", "prediction": "restored"} 6EOF

This tiny file is the contract. The same three rows also live in assets/access_requests.jsonl so the runnable scorer below can open a real fixture without depending on your laptop path.

Add the provided scorer

The shell gate needs a small program that reads JSONL rows and computes exact-match accuracy. Don't treat this Git-and-shell chapter as a Python prerequisite: copy the standard-library scorer as a provided utility and focus on its command-line contract. The later Python lesson explains functions, exceptions, tests, and CI before asking you to extend it.

Diagram showing git clone, install hooks, source activate.sh, and ./repro.sh.
git clone, install hooks, source activate.sh, and ./repro.sh.

The utility has one job: validate the three required fields, print the exact-match receipt, and exit nonzero when the fixture is missing, malformed, empty, or below the 2/3 gate. Save it as scripts/score_access_requests.py; scripts/run_eval.sh will invoke it on every clone.

scripts/score_access_requests.py
1import json 2import sys 3from pathlib import Path 4 5REQUIRED = ("prompt", "expected", "prediction") 6EVAL_FILE = Path("eval/access_requests.jsonl") 7ASSET_FILE = Path("assets/access_requests.jsonl") 8 9path = EVAL_FILE if EVAL_FILE.exists() else ASSET_FILE 10if not path.exists(): 11 print(f"ERROR: {path} missing; commit the eval fixture", file=sys.stderr) 12 raise SystemExit(1) 13 14rows = [] 15for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1): 16 if not line.strip(): 17 continue 18 try: 19 row = json.loads(line) 20 except json.JSONDecodeError as error: 21 print(f"ERROR: line {line_number}: {error.msg}", file=sys.stderr) 22 raise SystemExit(1) 23 missing = [field for field in REQUIRED if field not in row] 24 if missing: 25 print(f"ERROR: line {line_number} missing {missing}", file=sys.stderr) 26 raise SystemExit(1) 27 rows.append(row) 28 29if not rows: 30 print("ERROR: eval file is empty", file=sys.stderr) 31 raise SystemExit(1) 32 33def normalize(label: str) -> str: 34 return str(label).strip().lower() 35 36correct = sum(normalize(row["expected"]) == normalize(row["prediction"]) for row in rows) 37total = len(rows) 38score = correct / total 39print(f"Eval rows: {total}") 40print(f"Exact-match accuracy on tiny fixture: {score:.3f} ({correct}/{total})") 41# Count gate: fail below 2/3 correct (same rule Docker and Python reuse). 42if correct * 3 < total * 2: 43 print("Gate failed: score regressed below 2/3", file=sys.stderr) 44 raise SystemExit(1) 45print("Gate passed. You may commit.")
Output
1Eval rows: 3 2Exact-match accuracy on tiny fixture: 0.667 (2/3) 3Gate passed. You may commit.

Normalization is part of the shared contract: labels are compared after strip().lower(), and the gate uses counts (correct * 3 < total * 2), not a float equality check against 0.667. Success prints 3, 0.667, and exits zero; bad input or a regression exits nonzero.

Why does the gate compare integer counts instead of testing whether a floating-point score equals 0.667?

Answer

2 / 3 is not exactly 0.667. The count rule correct * 3 >= total * 2 expresses the threshold without rounding ambiguity.

The pre-commit gate that protects the score

Create a tiny executable that the pre-commit hook and clean-clone reproduction command will run.

terminal-5
1mkdir -p scripts 2cat > scripts/run_eval.sh << 'EOF' 3#!/usr/bin/env bash 4set -euo pipefail 5 6EVAL_FILE="eval/access_requests.jsonl" 7if [[ ! -f "$EVAL_FILE" ]]; then 8 echo "ERROR: $EVAL_FILE missing. Did you forget to commit the fixture or pull the latest repo?" 9 exit 1 10fi 11 12PYTHON_BIN="${PYTHON_BIN:-python3}" 13if ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then 14 echo "ERROR: Python 3 is required by the provided scorer. Install it or set PYTHON_BIN." 15 exit 1 16fi 17 18if [[ -f scripts/score_access_requests.py ]]; then 19 "$PYTHON_BIN" scripts/score_access_requests.py 20else 21 echo "ERROR: scripts/score_access_requests.py missing. Add the Python scorer from this chapter." 22 exit 1 23fi 24EOF 25chmod +x scripts/run_eval.sh

Create a repo-local reproduction command. This is important: shell aliases and Git hooks are local machine state, but repro.sh travels with the repo.

terminal-6
1cat > repro.sh << 'EOF' 2#!/usr/bin/env bash 3set -euo pipefail 4 5./scripts/run_eval.sh 6EOF 7chmod +x repro.sh

Now wire the same gate as a pre-commit hook. Don't try to commit .git/hooks/pre-commit; files under .git/ are Git internals, not normal tracked project files. Commit the hook source under scripts/, then install it into .git/hooks/ on each clone.

terminal-7
1cat > scripts/pre-commit-ai-eval.sh << 'EOF' 2#!/usr/bin/env bash 3set -euo pipefail 4 5echo "Running AI eval gate before commit..." 6./scripts/run_eval.sh 7echo "Eval gate passed." 8EOF 9 10cat > scripts/install_hooks.sh << 'EOF' 11#!/usr/bin/env bash 12set -euo pipefail 13 14mkdir -p .git/hooks 15cp scripts/pre-commit-ai-eval.sh .git/hooks/pre-commit 16chmod +x .git/hooks/pre-commit 17echo "Installed .git/hooks/pre-commit" 18EOF 19 20chmod +x scripts/pre-commit-ai-eval.sh scripts/install_hooks.sh 21./scripts/install_hooks.sh

Test it.

terminal-8
1./repro.sh 2git add scripts/score_access_requests.py scripts/run_eval.sh scripts/pre-commit-ai-eval.sh scripts/install_hooks.sh repro.sh eval/access_requests.jsonl 3git commit -m "feat: add three-row eval and pre-commit gate that protects 0.667"
Output
1Eval rows: 3 2Exact-match accuracy on tiny fixture: 0.667 (2/3) 3Gate passed. You may commit. 4Running AI eval gate before commit... 5Eval rows: 3 6Exact-match accuracy on tiny fixture: 0.667 (2/3) 7Gate passed. You may commit. 8Eval gate passed.

If the scorer reports a regression or the file disappears, the commit is rejected before the broken state enters history.

Shell one-liners that make the invisible visible

Add a few reusable functions to ~/.zshrc or ~/.bashrc.

For NVIDIA machines, nvidia-smi --query-gpu with --format=csv is the scriptable query interface you'll want in shell helpers.[3]Reference 3nvidia-smi documentationhttps://docs.nvidia.com/deploy/nvidia-smi/index.html

terminal-9
1# GPU snapshot (works on NVIDIA, falls back gracefully) 2gpu() { 3 if command -v nvidia-smi >/dev/null 2>&1; then 4 nvidia-smi --query-gpu=index,name,memory.used,memory.total,utilization.gpu --format=csv,noheader 5 else 6 echo "No NVIDIA GPU or nvidia-smi not in PATH" 7 fi 8} 9 10# Dataset size at a glance 11ds() { 12 du -sh "${1:-.}" 2>/dev/null | awk '{print $1 " " $2}' 13 echo "JSONL rows: $(find "${1:-.}" -name '*.jsonl' -exec wc -l {} + 2>/dev/null | tail -1 | awk '{print $1}')" 14} 15 16# One-command reproduction of the current eval 17repro() { 18 if [[ -x ./repro.sh ]]; then 19 ./repro.sh 20 elif [[ -x ./scripts/run_eval.sh ]]; then 21 ./scripts/run_eval.sh 22 elif [[ -x ./reproduce.sh ]]; then 23 ./reproduce.sh 24 else 25 echo "No reproducible entrypoint found (looked for repro.sh, scripts/run_eval.sh, or reproduce.sh)" 26 return 1 27 fi 28}

After sourcing, gpu, ds, and repro become muscle memory. You type one word and immediately know whether the machine has the resources the workload expects. In a clean clone, use ./repro.sh; aliases should make the common path faster, not hide the real entry point.

Inspect data and processes without crashing the box

Two shell checks cover common remote-machine failures: inspecting a dataset that's too large to open and finding a process that still holds GPU memory. For the first, never run cat train.jsonl on a multi-gigabyte file. It floods the terminal and stalls a remote host. Stream the file instead so each tool reads a little, passes it on, and keeps memory use flat.

terminal-inspect
1head -n 1 eval/access_requests.jsonl # peek at the schema of one row 2wc -l eval/access_requests.jsonl # count rows without loading the file 3grep -c '"expected"' eval/access_requests.jsonl # how many rows have the field

The pipe | chains these into one pass. grep '"restored"' eval/access_requests.jsonl | wc -l filters, then counts, without ever holding the whole file in memory.

The second reflex is reclaiming a GPU from a hung or detached job. A PyTorch process can remain alive after its controlling shell exits, and nvidia-smi reports currently active compute processes.[3]Reference 3nvidia-smi documentationhttps://docs.nvidia.com/deploy/nvidia-smi/index.html When the process table shows a PID, inspect it, ask it to exit cleanly, and only force-kill if it refuses.

terminal-kill
1nvidia-smi # read the PID in the bottom "Processes" table 2kill 12345 # SIGTERM (15): let the process flush and release VRAM 3for _ in {1..10}; do # give graceful shutdown up to ten seconds 4 kill -0 12345 2>/dev/null || break 5 sleep 1 6done 7kill -0 12345 2>/dev/null && kill -9 12345 # escalate only if it is still alive

If VRAM remains allocated but nvidia-smi shows no owning compute PID, there's no process ID to kill. Check container or PID-namespace visibility and other driver clients, then escalate to container-runtime or driver diagnosis instead of signaling a nonexistent process.

Start with SIGTERM. Signal 15 lets the process clean up, close files, and release CUDA memory. SIGKILL (signal 9) can't be caught or handled and risks leaving lock files or corrupt checkpoints behind, so reserve it for a process that refuses to exit.[4]Reference 4The Open Group Base Specifications: signal.hhttps://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html

You find a live training PID holding GPU memory. Which signal should you send first, and when should you escalate?

Answer

Send SIGTERM first so the process can flush files and release resources. Wait for a bounded grace period, then use SIGKILL only if the same PID remains alive.

Linux fundamentals that keep long jobs alive

Training and evaluation jobs can run for hours, often through an SSH session. These commands keep the job alive and make its resource use visible:

  • Detach a process that survives logout: nohup python train.py > train.log 2>&1 &
  • Manage sessions across SSH disconnects: tmux new -s training, tmux attach -t training, Ctrl-b d to detach.
  • Give the job lower priority so your laptop remains usable: nice -n 10 python train.py
  • Find the files using disk right now: du -ah /workspace | sort -rh | head -20
  • Check which Python process is using the GPU: nvidia-smi + ps aux | grep python

Together, these commands keep a job alive across disconnects, lower its CPU priority when needed, and show which files or processes are consuming resources.

The reproducible activation contract

Create an activation script that each teammate and each CI job can source. This first chapter doesn't require PyTorch yet, so the script reports CUDA when torch is already installed.

terminal-10
1cat > requirements.txt << 'EOF' 2# Empty in this first chapter. 3# Later chapters will add pinned runtime packages here. 4EOF 5 6cat > .env.example << 'EOF' 7# Copy to .env locally. Never commit .env. 8ACCESS_API_KEY=replace-me 9HF_HOME= 10EOF 11 12cat > activate.sh << 'EOF' 13#!/usr/bin/env bash 14 15_activate_fail() { 16 echo "ERROR: $1" >&2 17} 18 19_activate_main() { 20 # Prefer an explicit interpreter so clones don't silently use 3.10 vs 3.12. 21 # Optional: pin with a `.python-version` file and tools like uv or pyenv. 22 PYTHON_BIN="${PYTHON_BIN:-python3}" 23 if ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then 24 _activate_fail "install python3 or set PYTHON_BIN=/path/to/python" 25 return 1 26 fi 27 28 # 1. Create or reuse a local virtualenv 29 if [[ ! -d .venv ]]; then 30 "$PYTHON_BIN" -m venv .venv || { 31 _activate_fail "could not create .venv" 32 return 1 33 } 34 fi 35 source .venv/bin/activate || { 36 _activate_fail "could not activate .venv" 37 return 1 38 } 39 40 # 2. Install declared dependencies only when requirements.txt has entries. 41 # Prefer a locked install once you add packages: `uv lock` + `uv sync`, 42 # or `pip-compile` + `pip install -r requirements.txt --require-hashes`. 43 grep -Ev '^\s*(#|$)' requirements.txt >/dev/null 2>&1 44 _requirements_status=$? 45 if [[ $_requirements_status -eq 0 ]]; then 46 python -m pip install -r requirements.txt || { 47 _activate_fail "could not install requirements.txt" 48 return 1 49 } 50 elif [[ $_requirements_status -gt 1 ]]; then 51 _activate_fail "could not read requirements.txt" 52 return 1 53 fi 54 unset _requirements_status 55 56 # 3. Print the local environment without requiring GPU packages yet 57 if ! python - << 'PY' 58import os, sys 59print("Python:", sys.version.split()[0]) 60if sys.version_info[:2] < (3, 12): 61 print("WARNING: this project targets Python 3.12+; set PYTHON_BIN to a 3.12 interpreter") 62visible_devices = os.environ.get("CUDA_VISIBLE_DEVICES") 63print( 64 "CUDA_VISIBLE_DEVICES:", 65 visible_devices if visible_devices is not None else "<unset; no mask applied>", 66) 67try: 68 import torch 69except ModuleNotFoundError: 70 print("PyTorch: not installed yet (OK for this chapter)") 71else: 72 print("PyTorch:", torch.__version__) 73 print("CUDA available:", torch.cuda.is_available()) 74 if torch.cuda.is_available(): 75 print("GPU:", torch.cuda.get_device_name(0)) 76print("HF_HOME:", os.environ.get("HF_HOME", "(default ~/.cache/huggingface)")) 77PY 78 then 79 _activate_fail "environment probe failed" 80 return 1 81 fi 82 83 echo "Environment ready. Run './repro.sh' to execute the eval gate." 84} 85 86_activate_main 87_activate_status=$? 88unset -f _activate_main _activate_fail 89if [[ $_activate_status -ne 0 ]]; then 90 return "$_activate_status" 2>/dev/null || exit "$_activate_status" 91fi 92unset _activate_status 93EOF 94chmod +x activate.sh

The outer status check is essential for a sourced script. A helper that merely return 1s can fail while the rest of activate.sh continues and its final echo makes the source command look successful. Here every setup failure returns from _activate_main, and the file-level return propagates that nonzero status to source activate.sh without closing an interactive shell.

Document it in README.md:

README.md
1## Quick start 2 3git clone [email protected]:your-org/access-rag.git 4cd access-rag 5./scripts/install_hooks.sh 6if command -v git-lfs >/dev/null 2>&1; then 7 git lfs pull 8fi 9source activate.sh && ./repro.sh

Now a fresh engineer (or a fresh GPU box provisioned by your platform team) can reproduce the same 0.667 result through the tracked setup path.

Activation fail-early pipeline: tracked activate.sh and repro.sh create the environment, print Python CUDA and HF_HOME truth, stop on missing setup, and only then run the three-row eval gate that must print 0.667.
The activation contract is small but strict. Tracked files create environment, print machine truth, and route every clone to same repro command instead of hidden laptop state.

The failure modes you'll see in real life

SymptomMost common causeFix that belongs in the repo
CUDA not found on the GPU boxHost driver or container runtime is missing, the image lacks a compatible CUDA stack, or CUDA_VISIBLE_DEVICES is explicitly empty/invalid and hides the GPUCompare nvidia-smi, the printed CUDA_VISIBLE_DEVICES, and torch.cuda.is_available(); remove an accidental mask or choose a valid device index, then document the required image and runtime. An unset variable normally exposes all available devices.
ModuleNotFoundError for a package that worked on the laptoprequirements.txt is incomplete or uses unpinned versionsRebuild from a clean env, pin direct deps (and prefer a lockfile such as uv.lock or hashed requirements.txt), commit the pins, and reinstall from that file
Eval returns 0.000 because the three-row JSONL is missing.gitignore did not protect the generated cache directory that the author had on diskMove the fixture to eval/ and add the directory to the committed tree; don't rely on "I had it in my downloads folder"
Pre-commit hook doesn't run on a fresh cloneHooks under .git/hooks/ are local machine files, not tracked project filesCommit scripts/pre-commit-ai-eval.sh and scripts/install_hooks.sh, then run the installer after cloning
Pre-commit hook fails with "permission denied"The hook script was installed without chmod +x or the clone was on a filesystem that strips execute bitschmod +x scripts/*.sh .git/hooks/* + a one-line check in the installer
"It worked yesterday" after a git pullteammate committed a new large model without LFS or changed the expected schema of the eval fileLFS tracking + a schema validation step in the scorer + git diff before each git pull on data files

Encode each diagnosis and prevention in the repo so the next clone fails clearly instead of requiring machine-specific guesswork.

The repo contract

The first real engineering loop is in place:

  1. git clone + source activate.sh produces a working environment on any machine with the declared dependencies.
  2. repro (or the pre-commit hook) guarantees that the tiny contract (three rows, 0.667) is still satisfied after each change.
  3. gpu, ds, and the Linux session commands let you see what the hardware is doing.
  4. The .gitignore + LFS rules + activation script travel with the code, so the next person doesn't have to reverse-engineer your laptop.

Self check: clone the repo into a fresh temporary directory and run ./scripts/install_hooks.sh && source activate.sh && ./repro.sh. The expected output is the same 0.667 score plus a visible environment summary. If the command needs a hidden laptop file, name that missing contract, add it to .env.example, README, LFS, or the activation script, and rerun the clean-clone check.

Why is a clean temporary clone stronger evidence than rerunning repro in your working directory?

Answer

It removes untracked files, shell aliases, installed hooks, and other laptop state. If the clean clone passes, the required fixture, installer, environment contract, and command traveled through Git.

Reproducible change checklist

Before sharing a commit, inspect git status, run the repo-local eval, clone into a temporary directory, install tracked hooks, source activate.sh, and run ./repro.sh. Any dependency on an untracked file becomes a named repo contract before handoff.

Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.A teammate clones the access-rag repo on a new machine. The repo includes scripts/install_hooks.sh, activate.sh, requirements.txt, eval/access_requests.jsonl, scripts/run_eval.sh, and repro.sh. Which command sequence proves the clone is not relying on hidden laptop state?

Correct answer: Run ./scripts/install_hooks.sh, source activate.sh, then ./repro.sh; the environment is reported and the eval prints 3 rows with 0.667 accuracy.

A reproducible clone uses tracked setup and entrypoint files, not shell aliases or copied laptop state. The activation script creates or reuses .venv, installs requirements, prints the local environment, and ./repro.sh runs the same eval gate that reports the three-row fixture score of 0.667.

2.A repo contains .env with API keys, models/access.gguf at several GB, chroma/ vector index files, runs/ experiment logs, and eval/access_requests.jsonl. Which Git policy fits a reproducible AI project?

Correct answer: Commit the eval fixture and scripts; ignore .env, chroma/, runs/, caches, and local checkpoints; leave *.gguf trackable and route it through Git LFS after LFS is installed.

The small eval fixture is part of the repo contract, while secrets and generated local artifacts stay ignored. A blanket *.gguf ignore would prevent Git LFS from tracking that model path. Large model artifacts that must travel therefore remain unignored and use LFS pointers after tracking is configured.

3.After a fresh clone, a teammate can commit without running the AI eval gate. You discover the original author only copied a hook into .git/hooks/pre-commit on their own machine. What repo change fixes the handoff?

Correct answer: Commit scripts/pre-commit-ai-eval.sh and scripts/install_hooks.sh; the installer copies the hook into .git/hooks/pre-commit and runs chmod +x after cloning.

Git hooks under .git/hooks are local machine state, so they do not travel as normal project files. The durable pattern is to commit the hook source and an installer, then have each clone install an executable copy into .git/hooks.

4.You need to inspect a multi-GB JSONL dataset over SSH, see one example row, count total rows, and count rows containing "restored" without loading the file into memory. Which command sequence is appropriate?

Correct answer: head -n 1 train.jsonl; wc -l train.jsonl; grep '"restored"' train.jsonl | wc -l

head, wc, grep, and a pipe stream through the file instead of materializing it all at once. Running cat on a huge file floods the terminal, and editors or notebook lists can consume memory proportional to the dataset size.

5.nvidia-smi shows PID 12345 still holding GPU VRAM after a PyTorch job became hung or detached. You want to release the memory while minimizing the chance of corrupting checkpoints or leaving messy state. What should you do?

Correct answer: Run kill 12345, poll kill -0 12345 for a bounded grace period, then escalate with kill -9 12345 only if the PID remains alive.

Plain kill sends SIGTERM, which gives the process a chance to handle shutdown, close files, and release resources. kill -0 only tests existence and permission; it does not wait. SIGKILL cannot be caught or handled, so it is the fallback only after a bounded graceful shutdown period.

6.A training run will take hours over SSH, and you need to survive disconnects and later reattach to the session. Which command pattern fits that goal?

Correct answer: Start a tmux session with tmux new -s training, run the job inside it, detach with Ctrl-b d, and later use tmux attach -t training.

tmux gives you a detachable terminal session that survives SSH disconnects and can be reattached later. nohup can keep a noninteractive process running after logout, nice only changes scheduling priority, and a foreground job in a normal SSH shell is not protected from disconnects.

7.A fresh GPU box reports ModuleNotFoundError for a package that worked on the author's laptop after source activate.sh. What repo change addresses the real cause?

Correct answer: Build from a clean environment, add exact pinned dependencies to requirements.txt, commit it, and let activate.sh install from that file.

A package that exists only on the author's machine means the dependency contract is incomplete. The reproducible fix is to pin the requirement in the repo so activate.sh can install it on a clean clone, not to copy a virtualenv, hide behavior in shell startup files, or rely on manual installs.

8.A clean clone's eval only succeeds after copying the author's untracked .env file with a required API key. Which change preserves reproducibility without leaking the secret?

Correct answer: Keep .env ignored, commit .env.example and README setup notes, and make setup or eval fail clearly when the variable is missing.

Secrets should stay out of Git, but required configuration still needs a tracked contract. A committed example file, README instructions, and a clear setup or eval failure tell the next machine what to provide without leaking the actual key or silently bypassing the gate.

9.A fresh GPU box reports CUDA not found. nvidia-smi works, but CUDA_VISIBLE_DEVICES is explicitly empty. Which diagnosis and repo change are correct?

Correct answer: The empty mask hides the GPUs; remove it or select a valid index, and document the GPU runtime contract.

CUDA_VISIBLE_DEVICES filters devices already exposed by the host or container runtime; it doesn't install CUDA. Unset normally means no mask, while an empty or invalid value can hide every GPU. A reproducible diagnosis records the mask, driver visibility, framework check, and required image/runtime contract.

9 questions remaining.

Next Step
Continue to Docker for Reproducible AI

Your repo now carries its fixture, setup path, and eval gate to a clean clone. Docker adds the missing runtime contract so the same command runs with <span data-glossary="dependency-lockfile">pinned dependencies</span>, isolated <span data-glossary="secrets-management">secrets</span>, and a predictable filesystem on teammate and CI machines.

Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

Pro Git (2nd ed.)

Chacon, S. & Straub, B. · 2014

https://git-scm.com/book/en/v2

About large files on GitHub

GitHub · 2026

https://docs.github.com/en/repositories/working-with-files/managing-large-files/about-large-files-on-github

nvidia-smi documentation

NVIDIA · 2026

https://docs.nvidia.com/deploy/nvidia-smi/index.html

The Open Group Base Specifications: signal.h

The Open Group · 2018

https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.