Network Architecture Sizing - Research Notes
Experiment Overview
| Item |
Details |
| Date |
2025-12-18 |
| Goal |
Understand relationship between hidden_dims and model file size |
| Environment |
Google Colab A100, PyTorch 2.x, NativePPOTrainer |
| Status |
Documented |
Context
Training runs produced models at ~72 MB instead of expected ~148 MB. Investigation revealed the hidden_dims configuration determines model size, with the first layer dominating total parameter count due to multiplication with observation dimensions.
Architecture Comparison
Model Size vs Architecture
| Architecture |
hidden_dims |
Layers |
Total Params |
File Size |
First Layer Size |
| Large (v2.2) |
(2048, 1024, 512, 256) |
4 |
12.6M |
~148 MB |
2048 × obs_dim |
| Medium (v2.3) |
(1024, 512, 256) |
3 |
6.1M |
~72 MB |
1024 × obs_dim |
| Small |
(512, 256, 128) |
3 |
~1.5M |
~18 MB |
512 × obs_dim |
| Tiny |
(256, 128, 64) |
3 |
~0.4M |
~5 MB |
256 × obs_dim |
Why First Layer Dominates
With 53 features × 100 lookback = 5,300 input dimensions:
- Large:
2048 × 5300 = 10.9M params (86% of network)
- Medium:
1024 × 5300 = 5.4M params (89% of network)
- Small:
512 × 5300 = 2.7M params (90% of network)
Key insight: The first hidden layer dimension has exponentially more impact on model size than deeper layers.
Configuration Locations
Current defaults in ppo_trainer_native.py:
| Function |
GPU Tier |
hidden_dims |
get_auto_config() |
H100 |
(1024, 512, 256) |
get_auto_config() |
A100 |
(1024, 512, 256) |
get_auto_config() |
high (40GB+) |
(512, 256, 128) |
get_auto_config() |
medium (20-40GB) |
(512, 256, 128) |
get_auto_config() |
low (<20GB) |
(256, 128, 64) |
get_a100_config() |
A100-80GB |
(1024, 512, 256) |
get_a100_config() |
A100-40GB |
(512, 256, 128) |
Verified Workflow
To use larger architecture (148 MB models):
from alpaca_trading.gpu.ppo_trainer_native import get_auto_config
config = get_auto_config(total_timesteps=200_000_000, training_mode='production')
config.hidden_dims = (2048, 1024, 512, 256) # Override to 4-layer large
trainer = NativePPOTrainer(env, config)
To verify model architecture before training:
import torch
# Check expected size
obs_dim = 5300 # 53 features × 100 lookback
hidden_dims = (2048, 1024, 512, 256)
params = obs_dim * hidden_dims[0] # First layer
for i in range(len(hidden_dims) - 1):
params += hidden_dims[i] * hidden_dims[i+1]
params += hidden_dims[-1] * 64 * 2 # Actor + critic heads
print(f"Expected params: {params:,}")
print(f"Expected size: ~{params * 4 * 3 / 1024 / 1024:.0f} MB") # float32 × 3 (weights + optimizer state)
To inspect existing model:
import torch
ckpt = torch.load('model.pt', map_location='cpu', weights_only=False)
print(f"hidden_dims: {ckpt['config'].hidden_dims}")
print(f"Total params: {sum(v.numel() for v in ckpt['policy_state_dict'].values()):,}")
Failed Attempts (Critical)
| Attempt |
Why it Failed |
Lesson Learned |
| Assuming all configs use same architecture |
Different GPU tiers have different defaults |
Always check hidden_dims in config before training |
| Only checking layer count |
3-layer (1024,512,256) vs 4-layer (2048,1024,512,256) |
First layer width matters more than depth |
| Not saving config with model |
Couldn't reproduce training |
Always save full config in checkpoint |
| Using large architecture on small GPU |
OOM errors |
Match architecture to available VRAM |
| Assuming bigger = better |
Overfitting on small datasets |
Larger models need more data/regularization |
Performance Considerations
Larger Architecture (2048, 1024, 512, 256)
Pros:
- Higher model capacity for complex patterns
- Better for symbols with rich feature interactions
- May capture longer-term dependencies
Cons:
- 2x file size (~148 MB vs ~72 MB)
- Slower inference (~1.5-2x)
- Higher VRAM usage during training
- More prone to overfitting with limited data
Smaller Architecture (1024, 512, 256)
Pros:
- Faster inference (important for live trading)
- Lower VRAM requirements
- Faster training iterations
- Better generalization on limited data
Cons:
- May underfit complex market dynamics
- Less capacity for feature interactions
Recommended Architecture by Use Case
| Use Case |
Recommended hidden_dims |
Rationale |
| Quick iteration/testing |
(512, 256, 128) |
Fast training, low memory |
| Standard production |
(1024, 512, 256) |
Good balance |
| Complex symbols (crypto) |
(2048, 1024, 512, 256) |
Higher volatility patterns |
| Limited training data (<1 year) |
(512, 256, 128) |
Reduce overfitting |
| Extended training (500M+ steps) |
(2048, 1024, 512, 256) |
Capacity for more learning |
Key Insights
- First layer width dominates model size - doubling first layer ~doubles total params
- File size ≈ params × 12 bytes (float32 weights + Adam optimizer moments)
- Current v2.3 defaults favor smaller models - optimized for speed over capacity
- Architecture mismatch = inference failure - models trained with different hidden_dims are incompatible
- Always log hidden_dims - critical for reproducibility and debugging
Diagnostic Commands
# Compare two model architectures
def compare_models(path1, path2):
m1 = torch.load(path1, map_location='cpu', weights_only=False)
m2 = torch.load(path2, map_location='cpu', weights_only=False)
print(f"Model 1: {m1['config'].hidden_dims}")
print(f"Model 2: {m2['config'].hidden_dims}")
print(f"Params 1: {sum(v.numel() for v in m1['policy_state_dict'].values()):,}")
print(f"Params 2: {sum(v.numel() for v in m2['policy_state_dict'].values()):,}")
References
alpaca_trading/gpu/ppo_trainer_native.py: Lines 1314, 1339, 1726, 1760
alpaca_trading/gpu/ppo_trainer_native.py: NativeActorCritic class (line 305)
- CLAUDE.md: GPU Optimized Settings table
1---2name: network-architecture-sizing3description: PPO network architecture sizing for trading models. Trigger: (1) model files are unexpectedly small/large, (2) choosing hidden_dims for training, (3) balancing model capacity vs inference speed.4---5
6# Network Architecture Sizing - Research Notes
7
8## Experiment Overview
9| Item | Details |
10|------|---------|
11| **Date** | 2025-12-18 |
12| **Goal** | Understand relationship between hidden_dims and model file size |
13| **Environment** | Google Colab A100, PyTorch 2.x, NativePPOTrainer |
14| **Status** | Documented |
15
16## Context
17Training runs produced models at ~72 MB instead of expected ~148 MB. Investigation revealed the `hidden_dims` configuration determines model size, with the first layer dominating total parameter count due to multiplication with observation dimensions.
18
19## Architecture Comparison
20
21### Model Size vs Architecture
22
23| Architecture | hidden_dims | Layers | Total Params | File Size | First Layer Size |
24|--------------|-------------|--------|--------------|-----------|------------------|
25| Large (v2.2) | (2048, 1024, 512, 256) | 4 | 12.6M | ~148 MB | 2048 × obs_dim |
26| Medium (v2.3) | (1024, 512, 256) | 3 | 6.1M | ~72 MB | 1024 × obs_dim |
27| Small | (512, 256, 128) | 3 | ~1.5M | ~18 MB | 512 × obs_dim |
28| Tiny | (256, 128, 64) | 3 | ~0.4M | ~5 MB | 256 × obs_dim |
29
30### Why First Layer Dominates
31
32With 53 features × 100 lookback = 5,300 input dimensions:
33- Large: `2048 × 5300 = 10.9M params` (86% of network)
34- Medium: `1024 × 5300 = 5.4M params` (89% of network)
35- Small: `512 × 5300 = 2.7M params` (90% of network)
36
37**Key insight**: The first hidden layer dimension has exponentially more impact on model size than deeper layers.
38
39## Configuration Locations
40
41Current defaults in `ppo_trainer_native.py`:
42
43| Function | GPU Tier | hidden_dims |
44|----------|----------|-------------|
45| `get_auto_config()` | H100 | (1024, 512, 256) |
46| `get_auto_config()` | A100 | (1024, 512, 256) |
47| `get_auto_config()` | high (40GB+) | (512, 256, 128) |
48| `get_auto_config()` | medium (20-40GB) | (512, 256, 128) |
49| `get_auto_config()` | low (<20GB) | (256, 128, 64) |
50| `get_a100_config()` | A100-80GB | (1024, 512, 256) |
51| `get_a100_config()` | A100-40GB | (512, 256, 128) |
52
53## Verified Workflow
54
55### To use larger architecture (148 MB models):
56```python
57from alpaca_trading.gpu.ppo_trainer_native import get_auto_config
58
59config = get_auto_config(total_timesteps=200_000_000, training_mode='production')
60config.hidden_dims = (2048, 1024, 512, 256) # Override to 4-layer large
61
62trainer = NativePPOTrainer(env, config)
63```
64
65### To verify model architecture before training:
66```python
67import torch
68
69# Check expected size
70obs_dim = 5300 # 53 features × 100 lookback
71hidden_dims = (2048, 1024, 512, 256)
72
73params = obs_dim * hidden_dims[0] # First layer
74for i in range(len(hidden_dims) - 1):
75 params += hidden_dims[i] * hidden_dims[i+1]
76params += hidden_dims[-1] * 64 * 2 # Actor + critic heads
77
78print(f"Expected params: {params:,}")
79print(f"Expected size: ~{params * 4 * 3 / 1024 / 1024:.0f} MB") # float32 × 3 (weights + optimizer state)
80```
81
82### To inspect existing model:
83```python
84import torch
85
86ckpt = torch.load('model.pt', map_location='cpu', weights_only=False)
87print(f"hidden_dims: {ckpt['config'].hidden_dims}")
88print(f"Total params: {sum(v.numel() for v in ckpt['policy_state_dict'].values()):,}")
89```
90
91## Failed Attempts (Critical)
92
93| Attempt | Why it Failed | Lesson Learned |
94|---------|---------------|----------------|
95| Assuming all configs use same architecture | Different GPU tiers have different defaults | Always check `hidden_dims` in config before training |
96| Only checking layer count | 3-layer (1024,512,256) vs 4-layer (2048,1024,512,256) | First layer width matters more than depth |
97| Not saving config with model | Couldn't reproduce training | Always save full config in checkpoint |
98| Using large architecture on small GPU | OOM errors | Match architecture to available VRAM |
99| Assuming bigger = better | Overfitting on small datasets | Larger models need more data/regularization |
100
101## Performance Considerations
102
103### Larger Architecture (2048, 1024, 512, 256)
104**Pros:**
105- Higher model capacity for complex patterns
106- Better for symbols with rich feature interactions
107- May capture longer-term dependencies
108
109**Cons:**
110- 2x file size (~148 MB vs ~72 MB)
111- Slower inference (~1.5-2x)
112- Higher VRAM usage during training
113- More prone to overfitting with limited data
114
115### Smaller Architecture (1024, 512, 256)
116**Pros:**
117- Faster inference (important for live trading)
118- Lower VRAM requirements
119- Faster training iterations
120- Better generalization on limited data
121
122**Cons:**
123- May underfit complex market dynamics
124- Less capacity for feature interactions
125
126## Recommended Architecture by Use Case
127
128| Use Case | Recommended hidden_dims | Rationale |
129|----------|------------------------|-----------|
130| Quick iteration/testing | (512, 256, 128) | Fast training, low memory |
131| Standard production | (1024, 512, 256) | Good balance |
132| Complex symbols (crypto) | (2048, 1024, 512, 256) | Higher volatility patterns |
133| Limited training data (<1 year) | (512, 256, 128) | Reduce overfitting |
134| Extended training (500M+ steps) | (2048, 1024, 512, 256) | Capacity for more learning |
135
136## Key Insights
137
138- **First layer width dominates model size** - doubling first layer ~doubles total params
139- **File size ≈ params × 12 bytes** (float32 weights + Adam optimizer moments)
140- **Current v2.3 defaults favor smaller models** - optimized for speed over capacity
141- **Architecture mismatch = inference failure** - models trained with different hidden_dims are incompatible
142- **Always log hidden_dims** - critical for reproducibility and debugging
143
144## Diagnostic Commands
145
146```python
147# Compare two model architectures
148def compare_models(path1, path2):
149 m1 = torch.load(path1, map_location='cpu', weights_only=False)
150 m2 = torch.load(path2, map_location='cpu', weights_only=False)
151
152 print(f"Model 1: {m1['config'].hidden_dims}")
153 print(f"Model 2: {m2['config'].hidden_dims}")
154 print(f"Params 1: {sum(v.numel() for v in m1['policy_state_dict'].values()):,}")
155 print(f"Params 2: {sum(v.numel() for v in m2['policy_state_dict'].values()):,}")
156```
157
158## References
159- `alpaca_trading/gpu/ppo_trainer_native.py`: Lines 1314, 1339, 1726, 1760
160- `alpaca_trading/gpu/ppo_trainer_native.py`: NativeActorCritic class (line 305)
161- CLAUDE.md: GPU Optimized Settings table