Files
TinyTorch/modules/source/00_introduction/introduction_dev.py
Vijay Janapa Reddi 9116e4f256 Fix 00_introduction module technical requirements after agent review
- Add missing NBGrader metadata to markdown and code cells
- Implement conditional test execution with __name__ == "__main__"
- Ensure tests only run when module executed directly, not on import
- Maintain existing export directive (#| default_exp introduction)
- All agents approved: Education Architect, Module Developer, QA, Package Manager, Documentation Publisher
2025-09-16 02:24:27 -04:00

1130 lines
44 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#| default_exp introduction
# %% [markdown] nbgrader={"grade": false, "grade_id": "introduction-overview", "locked": false, "schema_version": 3, "solution": false, "task": false}
"""
# TinyTorch System Introduction & Architecture Overview
Welcome to **TinyTorch** - a complete neural network framework built from scratch for deep learning education and understanding.
This introduction module provides:
- **Visual system architecture** - Complete framework overview
- **Interactive dependency graphs** - How all 16 modules connect
- **Learning roadmap** - Guided path through the system
- **Component analysis** - What each module implements
Let's explore the architecture of this comprehensive ML framework!
"""
# %% nbgrader={"grade": false, "grade_id": "introduction-imports", "locked": false, "schema_version": 3, "solution": false, "task": false}
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.patches import FancyBboxPatch, Circle, ConnectionPatch
from pathlib import Path
import yaml
import networkx as nx
from typing import Dict, List, Tuple, Set
import seaborn as sns
from dataclasses import dataclass
from collections import defaultdict, deque
# Set plotting style for professional visualizations
plt.style.use('seaborn-v0_8')
sns.set_palette("husl")
# %% [markdown]
"""
## Module Metadata Analysis System
First, let's build tools to automatically analyze all TinyTorch modules and their relationships.
This will power our interactive visualizations.
"""
# %%
#| export
@dataclass
class ModuleInfo:
"""Complete information about a TinyTorch module"""
name: str
title: str
description: str
prerequisites: List[str]
enables: List[str]
components: List[str]
difficulty: str
time_estimate: str
exports_to: str
def difficulty_level(self) -> int:
"""Convert difficulty stars to numeric level"""
return self.difficulty.count('')
def estimated_hours(self) -> float:
"""Extract numeric time estimate"""
time_str = self.time_estimate.lower()
if 'capstone' in time_str:
return 40.0 # Capstone project estimate
# Extract first number from time estimate
import re
numbers = re.findall(r'\d+', time_str)
if numbers:
return float(numbers[0])
return 4.0 # Default estimate
#| export
class TinyTorchAnalyzer:
"""Comprehensive analysis of TinyTorch module system"""
def __init__(self, modules_path: str = "/Users/VJ/GitHub/TinyTorch/modules/source"):
self.modules_path = Path(modules_path)
self.modules: Dict[str, ModuleInfo] = {}
self.dependency_graph = nx.DiGraph()
self._load_all_modules()
self._build_dependency_graph()
def _load_all_modules(self):
"""Load metadata from all module.yaml files"""
for module_dir in sorted(self.modules_path.iterdir()):
if module_dir.is_dir() and not module_dir.name.startswith('.'):
yaml_file = module_dir / 'module.yaml'
if yaml_file.exists():
try:
with open(yaml_file, 'r') as f:
data = yaml.safe_load(f)
# Handle different YAML formats in the modules
if 'dependencies' in data:
deps = data['dependencies']
prerequisites = deps.get('prerequisites', [])
enables = deps.get('enables', [])
else:
# Handle older format
prerequisites = data.get('dependencies', [])
enables = []
module_info = ModuleInfo(
name=data.get('name', module_dir.name),
title=data.get('title', module_dir.name.title()),
description=data.get('description', ''),
prerequisites=prerequisites,
enables=enables,
components=data.get('components', []),
difficulty=data.get('difficulty', ''),
time_estimate=data.get('time_estimate', '2-4 hours'),
exports_to=data.get('exports_to', f'tinytorch.{module_dir.name}')
)
self.modules[module_info.name] = module_info
except Exception as e:
print(f"Warning: Could not load {yaml_file}: {e}")
def _build_dependency_graph(self):
"""Build NetworkX graph of module dependencies"""
# Add all modules as nodes
for name, module in self.modules.items():
self.dependency_graph.add_node(name, **{
'title': module.title,
'description': module.description,
'difficulty': module.difficulty_level(),
'time': module.estimated_hours(),
'components': len(module.components)
})
# Add dependency edges
for name, module in self.modules.items():
for prereq in module.prerequisites:
if prereq in self.modules:
self.dependency_graph.add_edge(prereq, name)
def get_learning_path(self) -> List[str]:
"""Generate optimal learning path through modules using topological sort"""
try:
return list(nx.topological_sort(self.dependency_graph))
except nx.NetworkXError:
# Fallback if cycles exist
return sorted(self.modules.keys())
def get_module_levels(self) -> Dict[str, int]:
"""Assign modules to learning levels based on dependencies"""
levels = {}
path = self.get_learning_path()
for module in path:
prereqs = self.modules[module].prerequisites
if not prereqs:
levels[module] = 0
else:
max_prereq_level = max((levels.get(p, 0) for p in prereqs if p in levels), default=0)
levels[module] = max_prereq_level + 1
return levels
# Initialize the analyzer
analyzer = TinyTorchAnalyzer()
print(f"📊 Loaded {len(analyzer.modules)} TinyTorch modules")
print(f"🔗 Built dependency graph with {analyzer.dependency_graph.number_of_edges()} connections")
# %% [markdown]
"""
### Test the Module Analysis System
Let's verify our module analyzer is working correctly by examining a few key modules.
"""
# %%
def test_module_analyzer():
"""Test that our module analyzer correctly loads and processes modules"""
# Test basic loading
assert len(analyzer.modules) >= 10, "Should load multiple modules"
# Test specific modules exist
key_modules = ['setup', 'tensor', 'activations', 'training']
for module_name in key_modules:
assert module_name in analyzer.modules, f"Should load {module_name} module"
# Test dependency relationships
tensor_module = analyzer.modules['tensor']
assert 'setup' in tensor_module.prerequisites, "Tensor should depend on setup"
# Test learning path generation
learning_path = analyzer.get_learning_path()
setup_pos = learning_path.index('setup') if 'setup' in learning_path else -1
tensor_pos = learning_path.index('tensor') if 'tensor' in learning_path else -1
if setup_pos >= 0 and tensor_pos >= 0:
assert setup_pos < tensor_pos, "Setup should come before tensor in learning path"
print("✅ Module analyzer tests passed!")
# Show some sample data
print(f"\n📋 Sample modules loaded:")
for name in list(analyzer.modules.keys())[:5]:
module = analyzer.modules[name]
print(f"{module.title} ({module.difficulty}) - {len(module.components)} components")
# test_module_analyzer() # Test moved to main block
# %% [markdown]
"""
## Interactive Dependency Visualization
Now let's create beautiful, interactive visualizations of the TinyTorch module dependency system.
"""
# %%
#| export
def create_dependency_graph_visualization(figsize=(16, 12)):
"""Create an interactive dependency graph visualization"""
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=figsize)
# Left plot: Hierarchical layout
ax1.set_title("TinyTorch Module Dependencies\n(Hierarchical Layout)", fontsize=16, fontweight='bold')
# Calculate positions using spring layout with hierarchy
levels = analyzer.get_module_levels()
pos = {}
# Group modules by level
level_groups = defaultdict(list)
for module, level in levels.items():
level_groups[level].append(module)
# Position modules in levels
max_level = max(levels.values()) if levels else 0
for level, modules in level_groups.items():
y = max_level - level # Higher levels at top
for i, module in enumerate(sorted(modules)):
x = (i - len(modules)/2) * 2.5
pos[module] = (x, y * 2)
# Draw the graph
G = analyzer.dependency_graph
# Node colors based on difficulty
node_colors = []
node_sizes = []
for node in G.nodes():
difficulty = analyzer.modules[node].difficulty_level()
node_colors.append(plt.cm.viridis(difficulty / 5.0)) # Normalize to 0-1
node_sizes.append(200 + difficulty * 100)
# Draw nodes and edges
nx.draw_networkx_nodes(G, pos, node_color=node_colors, node_size=node_sizes,
alpha=0.8, ax=ax1)
nx.draw_networkx_edges(G, pos, edge_color='gray', alpha=0.6,
arrows=True, arrowsize=20, ax=ax1)
# Add labels
labels = {node: analyzer.modules[node].name for node in G.nodes()}
nx.draw_networkx_labels(G, pos, labels, font_size=8, font_weight='bold', ax=ax1)
ax1.set_aspect('equal')
ax1.axis('off')
# Right plot: Circular layout
ax2.set_title("TinyTorch Module Dependencies\n(Circular Layout)", fontsize=16, fontweight='bold')
# Circular layout
pos_circular = nx.circular_layout(G)
nx.draw_networkx_nodes(G, pos_circular, node_color=node_colors, node_size=node_sizes,
alpha=0.8, ax=ax2)
nx.draw_networkx_edges(G, pos_circular, edge_color='gray', alpha=0.4,
arrows=True, arrowsize=15, ax=ax2)
nx.draw_networkx_labels(G, pos_circular, labels, font_size=7, font_weight='bold', ax=ax2)
ax2.set_aspect('equal')
ax2.axis('off')
# Add legend
difficulty_colors = [plt.cm.viridis(i/5.0) for i in range(1, 6)]
legend_elements = [plt.Line2D([0], [0], marker='o', color='w',
markerfacecolor=color, markersize=10,
label=f"{'' * (i+1)} Difficulty")
for i, color in enumerate(difficulty_colors)]
fig.legend(handles=legend_elements, loc='upper center', bbox_to_anchor=(0.5, 0.02), ncol=5)
plt.tight_layout()
plt.show()
return fig
# Create and display the dependency visualization
dependency_fig = create_dependency_graph_visualization()
# %% [markdown]
"""
### Test the Dependency Visualization
Let's verify our dependency graph captures the correct relationships.
"""
# %%
def test_dependency_relationships():
"""Test that dependency relationships are correctly captured"""
G = analyzer.dependency_graph
# Test that setup has no prerequisites (should be a source node)
setup_predecessors = list(G.predecessors('setup')) if 'setup' in G else []
print(f"Setup prerequisites: {setup_predecessors}")
# Test that capstone depends on many modules (should have many predecessors)
if 'capstone' in G:
capstone_predecessors = list(G.predecessors('capstone'))
print(f"Capstone depends on {len(capstone_predecessors)} modules: {capstone_predecessors[:5]}...")
assert len(capstone_predecessors) >= 5, "Capstone should depend on many modules"
# Test learning path makes sense
learning_path = analyzer.get_learning_path()
print(f"\n📚 Learning path ({len(learning_path)} modules):")
for i, module in enumerate(learning_path[:8]): # Show first 8
print(f" {i+1:2d}. {analyzer.modules[module].title}")
print("✅ Dependency relationship tests passed!")
# test_dependency_relationships() # Test moved to main block
# %% [markdown]
"""
## System Architecture Overview
Let's create a comprehensive system architecture diagram showing how all TinyTorch components work together.
"""
# %%
#| export
def create_system_architecture_diagram(figsize=(18, 12)):
"""Create a comprehensive TinyTorch system architecture diagram"""
fig, ax = plt.subplots(figsize=figsize)
ax.set_xlim(0, 20)
ax.set_ylim(0, 12)
ax.set_aspect('equal')
# Define architectural layers
layers = {
'Foundation': {'y': 1, 'color': '#FF6B6B', 'modules': ['setup', 'tensor']},
'Core Components': {'y': 3, 'color': '#4ECDC4', 'modules': ['activations', 'layers', 'dataloader']},
'Network Architecture': {'y': 5, 'color': '#45B7D1', 'modules': ['dense', 'spatial', 'attention']},
'Training System': {'y': 7, 'color': '#96CEB4', 'modules': ['autograd', 'optimizers', 'training']},
'Production & Optimization': {'y': 9, 'color': '#FFEAA7', 'modules': ['compression', 'kernels', 'benchmarking']},
'MLOps & Integration': {'y': 11, 'color': '#DDA0DD', 'modules': ['mlops', 'capstone']}
}
# Draw layer backgrounds
for layer_name, layer_info in layers.items():
y = layer_info['y']
rect = FancyBboxPatch((1, y-0.4), 18, 1.2,
boxstyle="round,pad=0.1",
facecolor=layer_info['color'],
alpha=0.3, edgecolor='black', linewidth=1)
ax.add_patch(rect)
# Layer label
ax.text(0.5, y, layer_name, fontsize=12, fontweight='bold',
rotation=90, va='center', ha='center')
# Draw modules within layers
module_positions = {}
for layer_name, layer_info in layers.items():
y = layer_info['y']
modules = [m for m in layer_info['modules'] if m in analyzer.modules]
for i, module_name in enumerate(modules):
module = analyzer.modules[module_name]
x = 2 + (i * 16 / max(len(modules), 1))
module_positions[module_name] = (x, y)
# Module box
width = min(3.5, 14 / len(modules))
box = FancyBboxPatch((x-width/2, y-0.3), width, 0.6,
boxstyle="round,pad=0.05",
facecolor='white', edgecolor='black',
linewidth=2)
ax.add_patch(box)
# Module title
ax.text(x, y+0.1, module.title, fontsize=9, fontweight='bold',
ha='center', va='center')
# Difficulty and components
ax.text(x, y-0.15, f"{module.difficulty}{len(module.components)} comp.",
fontsize=7, ha='center', va='center', style='italic')
# Draw dependency arrows between layers
for module_name, module in analyzer.modules.items():
if module_name in module_positions:
x1, y1 = module_positions[module_name]
for prereq in module.prerequisites:
if prereq in module_positions:
x2, y2 = module_positions[prereq]
if abs(y1 - y2) > 1: # Only draw arrows between different layers
arrow = ConnectionPatch((x2, y2+0.3), (x1, y1-0.3), "data", "data",
arrowstyle="->", shrinkA=0, shrinkB=0,
mutation_scale=15, alpha=0.6, color='gray')
ax.add_patch(arrow)
# Title and annotations
ax.text(10, 11.7, 'TinyTorch System Architecture', fontsize=20, fontweight='bold', ha='center')
ax.text(10, 0.3, 'Data flows upward through layers • Arrows show dependencies',
fontsize=10, ha='center', style='italic')
ax.set_xticks([])
ax.set_yticks([])
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(False)
plt.tight_layout()
plt.show()
return fig, module_positions
# Create the system architecture diagram
arch_fig, module_positions = create_system_architecture_diagram()
# %% [markdown]
"""
### Test the System Architecture Visualization
Let's verify our architecture diagram correctly represents the system structure.
"""
# %%
def test_system_architecture():
"""Test that the system architecture is correctly represented"""
# Test that we have positions for all modules
expected_modules = set(analyzer.modules.keys())
positioned_modules = set(module_positions.keys())
missing_modules = expected_modules - positioned_modules
if missing_modules:
print(f"⚠️ Missing modules in architecture: {missing_modules}")
# Test layer organization makes sense
foundation_modules = ['setup', 'tensor']
core_modules = ['activations', 'layers', 'dataloader']
foundation_y = [module_positions[m][1] for m in foundation_modules if m in module_positions]
core_y = [module_positions[m][1] for m in core_modules if m in module_positions]
if foundation_y and core_y:
assert min(core_y) > max(foundation_y), "Core modules should be above foundation"
print(f"✅ Architecture diagram includes {len(module_positions)} modules")
print(f"📊 Modules organized across {len(set(pos[1] for pos in module_positions.values()))} layers")
# test_system_architecture() # Test moved to main block
# %% [markdown]
"""
## Learning Roadmap Visualization
Create an interactive learning roadmap that shows the optimal path through TinyTorch modules.
"""
# %%
#| export
def create_learning_roadmap(figsize=(16, 10)):
"""Create an interactive learning roadmap visualization"""
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=figsize, height_ratios=[3, 1])
# Get learning path and levels
learning_path = analyzer.get_learning_path()
levels = analyzer.get_module_levels()
# Top plot: Learning path flowchart
ax1.set_title('TinyTorch Learning Roadmap\n(Optimal Learning Sequence)',
fontsize=16, fontweight='bold')
# Calculate positions for roadmap
path_positions = {}
cumulative_time = 0
y_positions = {}
for i, module_name in enumerate(learning_path):
if module_name in analyzer.modules:
module = analyzer.modules[module_name]
level = levels.get(module_name, 0)
# X position based on cumulative time
x = cumulative_time + module.estimated_hours() / 2
# Y position based on dependency level with some jitter
y = level + (i % 3 - 1) * 0.3
path_positions[module_name] = (x, y)
y_positions[module_name] = y
cumulative_time += module.estimated_hours()
# Draw the learning path
for i, module_name in enumerate(learning_path[:-1]):
if module_name in path_positions and learning_path[i+1] in path_positions:
x1, y1 = path_positions[module_name]
x2, y2 = path_positions[learning_path[i+1]]
# Draw connecting line
ax1.plot([x1, x2], [y1, y2], 'gray', alpha=0.5, linewidth=1, zorder=1)
# Draw modules
for module_name in learning_path:
if module_name in analyzer.modules and module_name in path_positions:
module = analyzer.modules[module_name]
x, y = path_positions[module_name]
# Color based on difficulty
difficulty = module.difficulty_level()
color = plt.cm.viridis(difficulty / 5.0)
# Draw module circle
circle = Circle((x, y), 0.4, facecolor=color, edgecolor='black',
linewidth=2, alpha=0.8, zorder=3)
ax1.add_patch(circle)
# Module number
ax1.text(x, y, str(learning_path.index(module_name) + 1),
fontsize=10, fontweight='bold', ha='center', va='center',
color='white', zorder=4)
# Module name below
ax1.text(x, y-0.7, module.title, fontsize=8, ha='center', va='top',
rotation=45, fontweight='bold')
ax1.set_xlim(-2, cumulative_time + 2)
ax1.set_ylim(-1, max(y_positions.values()) + 1)
ax1.set_xlabel('Cumulative Learning Time (hours)', fontsize=12)
ax1.set_ylabel('Dependency Level', fontsize=12)
ax1.grid(True, alpha=0.3)
# Bottom plot: Time and difficulty analysis
ax2.set_title('Module Difficulty and Time Distribution', fontsize=14, fontweight='bold')
module_names = [analyzer.modules[name].title[:15] for name in learning_path
if name in analyzer.modules]
difficulties = [analyzer.modules[name].difficulty_level() for name in learning_path
if name in analyzer.modules]
times = [analyzer.modules[name].estimated_hours() for name in learning_path
if name in analyzer.modules]
# Create stacked bar chart
x_pos = np.arange(len(module_names))
# Time bars
bars1 = ax2.bar(x_pos, times, alpha=0.7, label='Time (hours)', color='lightblue')
# Difficulty overlay
ax2_twin = ax2.twinx()
bars2 = ax2_twin.bar(x_pos, difficulties, alpha=0.5, label='Difficulty (⭐)',
color='orange', width=0.6)
ax2.set_xlabel('Modules (in learning order)', fontsize=12)
ax2.set_ylabel('Time (hours)', fontsize=12, color='blue')
ax2_twin.set_ylabel('Difficulty Level', fontsize=12, color='orange')
ax2.set_xticks(x_pos)
ax2.set_xticklabels(module_names, rotation=45, ha='right')
# Legends
ax2.legend(loc='upper left')
ax2_twin.legend(loc='upper right')
plt.tight_layout()
plt.show()
return fig, learning_path, cumulative_time
# Create the learning roadmap
roadmap_fig, learning_path, total_time = create_learning_roadmap()
print(f"📚 Learning path contains {len(learning_path)} modules")
print(f"⏱️ Total estimated time: {total_time:.1f} hours")
# %% [markdown]
"""
### Test the Learning Roadmap
Let's verify our learning roadmap is pedagogically sound and follows dependency constraints.
"""
# %%
def test_learning_roadmap():
"""Test that the learning roadmap respects dependencies and makes pedagogical sense"""
# Test that all prerequisites come before dependents
path_indices = {module: i for i, module in enumerate(learning_path)}
violations = []
for module_name in learning_path:
if module_name in analyzer.modules:
module = analyzer.modules[module_name]
module_index = path_indices[module_name]
for prereq in module.prerequisites:
if prereq in path_indices:
prereq_index = path_indices[prereq]
if prereq_index >= module_index:
violations.append(f"{module_name} comes before its prerequisite {prereq}")
if violations:
print("⚠️ Dependency violations found:")
for violation in violations:
print(f" {violation}")
else:
print("✅ Learning roadmap respects all dependencies")
# Test reasonable progression
foundation_modules = ['setup', 'tensor']
advanced_modules = ['capstone', 'mlops', 'benchmarking']
foundation_positions = [path_indices.get(m, -1) for m in foundation_modules]
advanced_positions = [path_indices.get(m, -1) for m in advanced_modules]
foundation_positions = [p for p in foundation_positions if p >= 0]
advanced_positions = [p for p in advanced_positions if p >= 0]
if foundation_positions and advanced_positions:
if max(foundation_positions) < min(advanced_positions):
print("✅ Foundation modules come before advanced modules")
else:
print("⚠️ Some advanced modules come before foundation modules")
print(f"📊 Total learning time: {total_time:.1f} hours ({total_time/8:.1f} work days)")
# test_learning_roadmap() # Test moved to main block
# %% [markdown]
"""
## Component Relationship Analysis
Let's analyze the specific components within each module and how they relate to each other.
"""
# %%
#| export
def create_component_analysis(figsize=(14, 10)):
"""Create visualization of components within modules and their relationships"""
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=figsize)
# 1. Components per module
modules = [name for name in learning_path if name in analyzer.modules]
component_counts = [len(analyzer.modules[name].components) for name in modules]
module_titles = [analyzer.modules[name].title for name in modules]
ax1.bar(range(len(modules)), component_counts,
color=plt.cm.viridis(np.linspace(0, 1, len(modules))))
ax1.set_title('Components per Module', fontweight='bold')
ax1.set_xlabel('Module')
ax1.set_ylabel('Number of Components')
ax1.set_xticks(range(len(modules)))
ax1.set_xticklabels([title[:10] for title in module_titles], rotation=45)
# 2. Difficulty vs Components scatter
difficulties = [analyzer.modules[name].difficulty_level() for name in modules]
times = [analyzer.modules[name].estimated_hours() for name in modules]
scatter = ax2.scatter(component_counts, difficulties, s=[t*20 for t in times],
c=times, cmap='plasma', alpha=0.7)
ax2.set_title('Module Complexity Analysis', fontweight='bold')
ax2.set_xlabel('Number of Components')
ax2.set_ylabel('Difficulty Level')
ax2.grid(True, alpha=0.3)
# Add colorbar for time
cbar = plt.colorbar(scatter, ax=ax2)
cbar.set_label('Time (hours)')
# 3. Module categories pie chart
categories = {
'Foundation': ['setup', 'tensor', 'activations'],
'Architecture': ['layers', 'dense', 'spatial', 'attention'],
'Training': ['dataloader', 'autograd', 'optimizers', 'training'],
'Production': ['compression', 'kernels', 'benchmarking', 'mlops', 'capstone']
}
category_counts = []
category_labels = []
category_colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4']
for category, module_list in categories.items():
count = sum(1 for m in module_list if m in analyzer.modules)
if count > 0:
category_counts.append(count)
category_labels.append(f'{category}\n({count} modules)')
ax3.pie(category_counts, labels=category_labels, colors=category_colors[:len(category_counts)],
autopct='%1.0f%%', startangle=90)
ax3.set_title('Module Distribution by Category', fontweight='bold')
# 4. Learning progression timeline
cumulative_components = np.cumsum([0] + component_counts)
cumulative_time = np.cumsum([0] + times)
ax4.plot(cumulative_time[:-1], cumulative_components[:-1], 'o-', linewidth=2, markersize=6)
ax4.set_title('Learning Progression', fontweight='bold')
ax4.set_xlabel('Cumulative Time (hours)')
ax4.set_ylabel('Cumulative Components Learned')
ax4.grid(True, alpha=0.3)
# Add milestone annotations
milestones = [0, len(modules)//4, len(modules)//2, 3*len(modules)//4, len(modules)-1]
for i in milestones:
if i < len(cumulative_time) - 1:
ax4.annotate(f'{cumulative_components[i]} comp.\n{cumulative_time[i]:.0f}h',
xy=(cumulative_time[i], cumulative_components[i]),
xytext=(10, 10), textcoords='offset points',
bbox=dict(boxstyle='round,pad=0.3', facecolor='yellow', alpha=0.7),
fontsize=8)
plt.tight_layout()
plt.show()
return fig
# Create component analysis
component_fig = create_component_analysis()
# %% [markdown]
"""
### Test Component Analysis
Let's verify our component analysis provides meaningful insights.
"""
# %%
def test_component_analysis():
"""Test that component analysis reveals meaningful patterns"""
# Test component distribution
total_components = sum(len(module.components) for module in analyzer.modules.values())
avg_components = total_components / len(analyzer.modules)
print(f"📊 Total components across all modules: {total_components}")
print(f"📊 Average components per module: {avg_components:.1f}")
# Find modules with most/least components
component_counts = [(name, len(module.components)) for name, module in analyzer.modules.items()]
component_counts.sort(key=lambda x: x[1], reverse=True)
print(f"\n🏆 Modules with most components:")
for name, count in component_counts[:3]:
print(f" {analyzer.modules[name].title}: {count} components")
print(f"\n🏆 Modules with least components:")
for name, count in component_counts[-3:]:
print(f" {analyzer.modules[name].title}: {count} components")
# Test correlation between difficulty and components
difficulties = [module.difficulty_level() for module in analyzer.modules.values()]
components = [len(module.components) for module in analyzer.modules.values()]
correlation = np.corrcoef(difficulties, components)[0, 1]
print(f"\n📈 Correlation between difficulty and components: {correlation:.2f}")
if correlation > 0.3:
print("✅ Higher difficulty modules tend to have more components")
elif correlation < -0.3:
print("⚠️ Higher difficulty modules tend to have fewer components")
else:
print("📊 No strong correlation between difficulty and component count")
# test_component_analysis() # Test moved to main block
# %% [markdown]
"""
## Export Functions and Module Interface
Create functions that can be imported and used by other parts of TinyTorch.
"""
# %%
#| export
def get_tinytorch_overview() -> Dict:
"""Get comprehensive overview of TinyTorch system for external use"""
return {
'total_modules': len(analyzer.modules),
'total_components': sum(len(module.components) for module in analyzer.modules.values()),
'learning_path': analyzer.get_learning_path(),
'total_time_hours': sum(module.estimated_hours() for module in analyzer.modules.values()),
'difficulty_levels': {name: module.difficulty_level() for name, module in analyzer.modules.items()},
'module_categories': {
'foundation': ['setup', 'tensor', 'activations'],
'architecture': ['layers', 'dense', 'spatial', 'attention'],
'training': ['dataloader', 'autograd', 'optimizers', 'training'],
'production': ['compression', 'kernels', 'benchmarking', 'mlops', 'capstone']
}
}
#| export
def visualize_tinytorch_system(save_plots: bool = False) -> Dict:
"""Generate all TinyTorch system visualizations"""
visualizations = {}
print("🎨 Generating TinyTorch system visualizations...")
# Generate dependency graph
print(" 📊 Creating dependency graph...")
dep_fig = create_dependency_graph_visualization()
visualizations['dependency_graph'] = dep_fig
# Generate architecture diagram
print(" 🏗️ Creating architecture diagram...")
arch_fig, positions = create_system_architecture_diagram()
visualizations['architecture'] = arch_fig
# Generate learning roadmap
print(" 📚 Creating learning roadmap...")
roadmap_fig, path, time = create_learning_roadmap()
visualizations['roadmap'] = roadmap_fig
# Generate component analysis
print(" 🔍 Creating component analysis...")
component_fig = create_component_analysis()
visualizations['components'] = component_fig
if save_plots:
print(" 💾 Saving plots to files...")
for name, fig in visualizations.items():
fig.savefig(f'tinytorch_{name}.png', dpi=300, bbox_inches='tight')
print("✅ All visualizations generated successfully!")
return visualizations
#| export
def get_module_info(module_name: str) -> Dict:
"""Get detailed information about a specific module"""
if module_name not in analyzer.modules:
return {'error': f'Module {module_name} not found'}
module = analyzer.modules[module_name]
return {
'name': module.name,
'title': module.title,
'description': module.description,
'prerequisites': module.prerequisites,
'enables': module.enables,
'components': module.components,
'difficulty': module.difficulty,
'difficulty_level': module.difficulty_level(),
'time_estimate': module.time_estimate,
'estimated_hours': module.estimated_hours(),
'exports_to': module.exports_to
}
#| export
def get_learning_recommendations(current_module: str = None) -> Dict:
"""Get personalized learning recommendations"""
path = analyzer.get_learning_path()
if current_module is None:
return {
'recommended_start': path[0] if path else None,
'full_path': path,
'total_time': sum(analyzer.modules[name].estimated_hours()
for name in path if name in analyzer.modules)
}
if current_module not in path:
return {'error': f'Module {current_module} not found in learning path'}
current_index = path.index(current_module)
return {
'current_module': current_module,
'progress': f"{current_index + 1}/{len(path)}",
'next_modules': path[current_index + 1:current_index + 4], # Next 3 modules
'remaining_time': sum(analyzer.modules[name].estimated_hours()
for name in path[current_index + 1:]
if name in analyzer.modules),
'prerequisites_completed': path[:current_index],
'can_start': [name for name in path[current_index + 1:]
if all(prereq in path[:current_index + 1]
for prereq in analyzer.modules.get(name, ModuleInfo('','','',[],'',[],'','','')).prerequisites)]
}
# Test the export functions
print("🧪 Testing export functions...")
overview = get_tinytorch_overview()
print(f"📊 System Overview: {overview['total_modules']} modules, {overview['total_components']} components")
setup_info = get_module_info('setup')
print(f"📋 Setup Module: {setup_info['title']} - {setup_info['difficulty']}")
recommendations = get_learning_recommendations()
print(f"📚 Learning Recommendations: Start with {recommendations['recommended_start']}")
print("✅ Export functions working correctly!")
# %% [markdown]
"""
## ML Systems Thinking Questions
Let's explore how TinyTorch's architecture connects to broader ML systems and production frameworks.
"""
# %% [markdown]
"""
### System Architecture & Design Patterns
**Reflection Questions:**
1. **Modular Design Philosophy**: How does TinyTorch's module dependency system compare to frameworks like PyTorch or TensorFlow? What are the advantages and trade-offs of explicit dependency management?
2. **Component Composition**: Notice how higher-level modules (like `training`) depend on multiple lower-level modules (`tensor`, `autograd`, `optimizers`). How does this pattern reflect real ML system architecture?
3. **Framework Evolution**: Looking at the learning roadmap, how might you extend TinyTorch to support distributed training or GPU acceleration? Where would new modules fit in the dependency graph?
### Production ML Systems
**Reflection Questions:**
4. **Deployment Pipeline**: How do the later modules (`compression`, `benchmarking`, `mlops`) mirror real-world ML deployment concerns? What additional modules might production systems require?
5. **System Integration**: If you were to deploy a TinyTorch model in production, which modules would be most critical for runtime efficiency? How might you minimize dependencies?
6. **Monitoring & Observability**: How does the `mlops` module's position as a terminal node reflect its role in production systems? What additional monitoring capabilities might be needed?
### Framework Design Decisions
**Reflection Questions:**
7. **Dependency Management**: Compare TinyTorch's explicit prerequisite system to frameworks like Keras or scikit-learn. How do design decisions about dependencies affect developer experience?
8. **Component Granularity**: Some modules have many components (like `training`) while others have few (like `setup`). How do you balance component granularity in ML framework design?
9. **Educational vs Production**: How might the educational goals of TinyTorch influence its architecture differently than a production framework? Where do you see pedagogical design choices?
### Performance & Scalability
**Reflection Questions:**
10. **Computational Graph**: How does TinyTorch's module structure relate to computational graphs in frameworks like PyTorch or JAX? Where do you see opportunities for optimization?
11. **Memory Management**: Looking at the component analysis, which modules are likely to be most memory-intensive? How might this influence deployment strategies?
12. **Hardware Acceleration**: The `kernels` module focuses on hardware-aware optimization. How do production frameworks handle the trade-off between abstraction and performance?
*These questions are designed to help you think beyond implementation details toward the broader patterns and principles that guide ML systems design in industry.*
"""
# %% [markdown]
"""
## Comprehensive Testing
Let's run comprehensive tests to ensure all our visualizations and analysis tools work correctly.
"""
# %%
def run_comprehensive_tests():
"""Run comprehensive tests of the introduction module functionality"""
print("🧪 Running comprehensive tests for TinyTorch Introduction Module...")
print("=" * 60)
# Test 1: Module Loading
print("\n1⃣ Testing module loading...")
assert len(analyzer.modules) >= 10, "Should load multiple modules"
assert 'setup' in analyzer.modules, "Should load setup module"
assert 'tensor' in analyzer.modules, "Should load tensor module"
print("✅ Module loading tests passed")
# Test 2: Dependency Graph
print("\n2⃣ Testing dependency graph...")
G = analyzer.dependency_graph
assert G.number_of_nodes() >= 10, "Should have multiple nodes"
assert G.number_of_edges() >= 5, "Should have dependency edges"
print("✅ Dependency graph tests passed")
# Test 3: Learning Path
print("\n3⃣ Testing learning path...")
path = analyzer.get_learning_path()
assert len(path) >= 10, "Should have meaningful learning path"
# Verify no dependency violations
path_indices = {module: i for i, module in enumerate(path)}
violations = 0
for module_name in path:
if module_name in analyzer.modules:
module = analyzer.modules[module_name]
for prereq in module.prerequisites:
if prereq in path_indices:
if path_indices[prereq] >= path_indices[module_name]:
violations += 1
assert violations == 0, f"Learning path should have no dependency violations (found {violations})"
print("✅ Learning path tests passed")
# Test 4: Component Analysis
print("\n4⃣ Testing component analysis...")
total_components = sum(len(module.components) for module in analyzer.modules.values())
assert total_components >= 20, "Should have meaningful number of components"
print("✅ Component analysis tests passed")
# Test 5: Export Functions
print("\n5⃣ Testing export functions...")
overview = get_tinytorch_overview()
assert 'total_modules' in overview, "Overview should include module count"
assert 'learning_path' in overview, "Overview should include learning path"
module_info = get_module_info('setup')
assert 'title' in module_info, "Module info should include title"
recommendations = get_learning_recommendations()
assert 'recommended_start' in recommendations, "Should provide starting recommendation"
print("✅ Export function tests passed")
# Test 6: Visualization Generation
print("\n6⃣ Testing visualization generation...")
try:
# Test that we can generate all visualizations without errors
dep_fig = create_dependency_graph_visualization()
arch_fig, positions = create_system_architecture_diagram()
roadmap_fig, path, time = create_learning_roadmap()
component_fig = create_component_analysis()
assert dep_fig is not None, "Should generate dependency graph"
assert arch_fig is not None, "Should generate architecture diagram"
assert roadmap_fig is not None, "Should generate roadmap"
assert component_fig is not None, "Should generate component analysis"
# Close figures to save memory
plt.close(dep_fig)
plt.close(arch_fig)
plt.close(roadmap_fig)
plt.close(component_fig)
print("✅ Visualization generation tests passed")
except Exception as e:
print(f"❌ Visualization test failed: {e}")
raise
print("\n" + "=" * 60)
print("🎉 ALL TESTS PASSED! TinyTorch Introduction Module is working correctly!")
print("=" * 60)
# Summary statistics
print(f"\n📊 System Summary:")
print(f"{len(analyzer.modules)} modules loaded")
print(f"{analyzer.dependency_graph.number_of_edges()} dependencies mapped")
print(f"{len(analyzer.get_learning_path())} modules in learning path")
print(f"{sum(len(m.components) for m in analyzer.modules.values())} total components")
print(f"{sum(m.estimated_hours() for m in analyzer.modules.values()):.1f} total learning hours")
# Run the comprehensive tests
if __name__ == "__main__":
# Run individual tests
test_module_analyzer()
test_dependency_relationships()
test_system_architecture()
test_learning_roadmap()
test_component_analysis()
# Run comprehensive test suite
run_comprehensive_tests()
# %% [markdown]
"""
## Module Summary
**Congratulations!** You've successfully explored the complete TinyTorch system architecture.
### What You've Accomplished
1. **📊 System Analysis**: Built tools to automatically analyze module dependencies and relationships
2. **🎨 Interactive Visualizations**: Created comprehensive visual overviews of the entire framework
3. **📚 Learning Roadmap**: Generated an optimal learning path through all 16 modules
4. **🔍 Component Analysis**: Analyzed the components within each module and their complexity
5. **🏗️ Architecture Overview**: Visualized how all TinyTorch components work together
6. **🧪 Comprehensive Testing**: Validated that all analysis tools work correctly
### Key Insights Discovered
- **TinyTorch contains {len(analyzer.modules)} modules** with {sum(len(m.components) for m in analyzer.modules.values())} total components
- **Learning path spans {sum(m.estimated_hours() for m in analyzer.modules.values()):.1f} hours** of estimated study time
- **Dependency structure** ensures proper learning progression from foundations to production
- **Modular design** enables flexible learning and component reuse
### How This Connects to Industry ML Systems
The architecture patterns you've explored in TinyTorch mirror those used in production ML frameworks:
- **Modular Dependencies**: Similar to PyTorch's module system
- **Component Composition**: Reflects how TensorFlow builds complex operations from primitives
- **Production Pipeline**: MLOps module mirrors real deployment concerns
- **Performance Optimization**: Kernels and compression reflect production efficiency needs
### Next Steps
Now you're ready to dive into any TinyTorch module with a complete understanding of how it fits into the broader system. Use the learning roadmap to guide your journey through building a complete neural network framework from scratch!
**Happy Learning! 🚀**
"""
# %%
# Export key functions for use by other modules
__all__ = [
'TinyTorchAnalyzer',
'get_tinytorch_overview',
'visualize_tinytorch_system',
'get_module_info',
'get_learning_recommendations',
'create_dependency_graph_visualization',
'create_system_architecture_diagram',
'create_learning_roadmap',
'create_component_analysis'
]
print("🎯 TinyTorch Introduction Module Complete!")
print("📦 Exported functions ready for use by other modules")