feat: Add unified model introspection & diagnostics framework
Author: jonathan-fulton
Pull request: yes

## 🚀 Feature: Unified Model Introspection & Diagnostics Framework

### What does this PR do?

This PR introduces a comprehensive diagnostics framework for profiling and analyzing transformer models during inference. It provides a unified API for collecting per-layer metrics including memory usage, execution timing, and activation statistics.

### Key Features

- **Per-layer memory profiling** - Track GPU/CPU memory allocation for each layer
- **Per-layer timing analysis** - Measure execution time with optional CUDA synchronization
- **Activation statistics** - Collect mean, std, min, max, norm, sparsity of layer outputs
- **Layer filtering** - Use regex patterns to instrument specific layers
- **Model comparison** - Compare metrics between model configurations (e.g., FP32 vs INT8)
- **Multiple output formats** - Summary text, JSON, pandas DataFrame

### Motivation

Currently, profiling transformer models requires cobbling together multiple tools or writing custom hooks. This framework provides:

1. A clean, unified API for common profiling tasks
2. Minimal overhead when disabled (can be left in production code)
3. Consistent interfaces for comparing models/configurations
4. Easy integration with existing transformers workflows

### Usage Examples

**Quick diagnostics:**
```python
from transformers import AutoModel
from transformers.diagnostics import diagnose_forward

model = AutoModel.from_pretrained("bert-base-uncased")
report = diagnose_forward(model, inputs, num_runs=5, warmup_runs=2)
print(report.summary())
```

**Custom configuration:**
```python
from transformers.diagnostics import DiagnosticsConfig, ModelProbe

config = DiagnosticsConfig(
    track_memory=True,
    track_timing=True,
    track_activations=True,
    layer_patterns=[".*attention.*", ".*mlp.*"],  # Only profile these layers
)

with ModelProbe(model, config) as probe:
    outputs = model(**inputs)
    report = probe.get_report()

# Export to different formats
print(report.summary())           # Human-readable summary
df = report.to_dataframe()        # pandas DataFrame
report.save("diagnostics.json")   # JSON file
```

**Model comparison:**
```python
from transformers.diagnostics import compare_reports, diagnose_forward

report_fp32 = diagnose_forward(model_fp32, inputs)
report_int8 = diagnose_forward(model_int8, inputs)

comparison = compare_reports(report_fp32, report_int8, name_a="FP32", name_b="INT8")
print(comparison.summary())  # Shows speedups, slowdowns, memory savings
```

### Components

| Component | Description |
|-----------|-------------|
| `DiagnosticsConfig` | Configuration class controlling what to track |
| `ModelProbe` | Hook-based instrumentation for model layers |
| `DiagnosticsReport` | Structured report with analysis methods |
| `diagnose_forward()` | One-liner convenience function |
| `compare_reports()` | Compare two reports for A/B analysis |

### Testing

Added 69 tests covering:
- Configuration validation and pattern matching
- Memory, timing, and activation analyzers
- Probe lifecycle (attach/detach/reset)
- Report generation and serialization
- Model comparison utilities

### Future Work (not in this PR)

- Integration with `torch.profiler` for detailed GPU kernels
- Visualization utilities (timeline, memory graphs)
- Support for distributed/multi-GPU profiling
- Integration with model optimization pipelines

### Checklist

- [x] This PR fixes no existing issue (new feature)
- [x] Tests added
- [x] Documentation included in docstrings
- [x] Code follows transformers style guidelines
- [x] No debug code or TODOs that shouldn't ship

Comments:
Proactively closing this one too - based on the feedback on #43664, I realize adding a full diagnostics framework is likely too ambitious for an external contribution. Users who need this level of introspection probably have their own tooling already.

Apologies for the scope creep! Will focus on smaller, targeted bug fixes going forward.