fuse_proj: Initial operational package for 3 researchers (Pavlenko/Blizno/Moroz)

Multimodal fusion research on StripNet+GTA-UAV proxy:
- 3 independent fusion tracks: condition-aware (A), token/bottleneck (B), role-aware (C)
- Shared interfaces, protocol, dataset audit, baseline benchmarks
- Canonical version-chain references to vault (SPEC, ANALYSIS, TRIAGE)
- Personalized task plans and decision tables for each researcher
- 3 generated DOCX task assignment files with milestones and DoD checklist
- Full modality dropout diagnostics and missing-modality robustness requirements
- Data contract, benchmark registry, experiment tracking infrastructure

Operational documents:
- docs/00_project/: MERIDIAN context, protocol, repository reuse guide, experiment specification
- docs/01_tasks/: Master assignment + 3 individual researcher tracks + joint integration
- docs/02_references/: Core literature, version-chain bases, code maps
- docs/03_codebase_guides/: Existing code snapshots from vault
- scripts/: gen_task_plans.js (DOCX generation), placeholder infrastructure
- vendor_reference/: Snapshots of caption_test, depth_edges_annotate, existing SOFIA/SegModel code
- reports/, results/, experiments/: Shared output structure for all 3 researchers

3 DOCX files generated from gen_task_plans.js (Times New Roman 14pt, GOST format):
- План_заданий_Павленко_БВ.docx (Condition-Aware track, fusion API owner)
- План_заданий_Близно_МВ.docx (Token/Bottleneck track, benchmark owner)
- План_заданий_Мороз_ЕС.docx (Role-Aware track, data contract owner)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Pikaliov
2026-06-11 17:16:57 +03:00
commit 2c6a00a4ca
155 changed files with 39765 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
from __future__ import annotations
"""PyTorch Profiler wrapper for training performance analysis.
Profiles the first N batches of training to identify bottlenecks
in CUDA/CPU execution, memory allocation, and data loading.
Exports:
- Chrome trace (viewable in chrome://tracing)
- TensorBoard plugin data (if TB available)
- Summary table to console
Usage:
profiler = TrainingProfiler(output_dir, n_warmup=3, n_active=5)
for batch_idx, batch in enumerate(loader):
with profiler.step_context(batch_idx):
# ... training step ...
if profiler.is_done(batch_idx):
break
profiler.export()
"""
import logging
from pathlib import Path
import torch
from torch.profiler import ProfilerActivity, profile, schedule, tensorboard_trace_handler
LOGGER = logging.getLogger("caption_test.profiler")
class TrainingProfiler:
"""PyTorch profiler for first N training batches.
Args:
output_dir: Directory for profiler output.
n_warmup: Number of warmup steps (not profiled).
n_active: Number of steps to actively profile.
n_repeat: Number of profiling cycles.
record_shapes: Record tensor shapes.
profile_memory: Track memory allocation.
with_stack: Record Python call stacks.
"""
def __init__(
self,
output_dir: str | Path,
n_warmup: int = 3,
n_active: int = 5,
n_repeat: int = 1,
record_shapes: bool = True,
profile_memory: bool = True,
with_stack: bool = False,
) -> None:
self.output_dir = Path(output_dir) / "profiler"
self.output_dir.mkdir(parents=True, exist_ok=True)
self.n_warmup = n_warmup
self.n_active = n_active
self.n_repeat = n_repeat
self.total_steps = (n_warmup + n_active) * n_repeat
self._profiler = profile(
activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
schedule=schedule(
wait=0,
warmup=n_warmup,
active=n_active,
repeat=n_repeat,
),
on_trace_ready=tensorboard_trace_handler(str(self.output_dir)),
record_shapes=record_shapes,
profile_memory=profile_memory,
with_stack=with_stack,
)
self._started = False
def start(self) -> None:
"""Start the profiler."""
self._profiler.__enter__()
self._started = True
LOGGER.info(
"Profiler started: %d warmup + %d active steps, output: %s",
self.n_warmup, self.n_active, self.output_dir,
)
def step(self) -> None:
"""Signal end of a profiling step."""
if self._started:
self._profiler.step()
def is_done(self, batch_idx: int) -> bool:
"""Check if profiling is complete."""
return batch_idx >= self.total_steps
def export(self) -> None:
"""Export profiling results and print summary."""
if not self._started:
return
self._profiler.__exit__(None, None, None)
self._started = False
# Print key averages summary.
summary = self._profiler.key_averages().table(
sort_by="cuda_time_total", row_limit=20,
)
LOGGER.info("Profiler summary (top 20 by CUDA time):\n%s", summary)
# Export Chrome trace.
trace_path = self.output_dir / "chrome_trace.json"
self._profiler.export_chrome_trace(str(trace_path))
LOGGER.info("Chrome trace exported: %s", trace_path)
# Memory summary if available.
if torch.cuda.is_available():
mem_summary = torch.cuda.memory_summary(abbreviated=True)
summary_path = self.output_dir / "memory_summary.txt"
summary_path.write_text(mem_summary)
LOGGER.info("CUDA memory summary: %s", summary_path)
def print_model_summary(model: torch.nn.Module, device: str = "cuda") -> str:
"""Print model summary using torchinfo (if available).
Falls back to a simple parameter count if torchinfo is not installed.
Returns:
Summary string.
"""
try:
from torchinfo import summary as torchinfo_summary
info = torchinfo_summary(
model,
input_data={
"drone_img": torch.randn(1, 3, 256, 256, device=device),
"sat_img": torch.randn(1, 3, 256, 256, device=device),
},
col_names=["input_size", "output_size", "num_params", "trainable"],
verbose=0,
depth=3,
)
summary_str = str(info)
LOGGER.info("Model summary (torchinfo):\n%s", summary_str)
return summary_str
except ImportError:
LOGGER.info("torchinfo not installed, using basic parameter count")
except Exception as e:
LOGGER.warning("torchinfo failed (%s), using basic parameter count", e)
# Fallback: simple param count.
lines = []
total = 0
trainable = 0
for name, param in model.named_parameters():
total += param.numel()
if param.requires_grad:
trainable += param.numel()
lines.append(f" [trainable] {name}: {list(param.shape)} ({param.numel():,})")
summary_str = (
f"Total parameters: {total:,}\n"
f"Trainable parameters: {trainable:,} ({100*trainable/max(total,1):.2f}%)\n"
+ "\n".join(lines[:30])
)
LOGGER.info("Model summary:\n%s", summary_str)
return summary_str

View File

@@ -0,0 +1,206 @@
from __future__ import annotations
"""Unified experiment tracking: W&B + TensorBoard + CSV.
Auto-detects available backends. Falls back gracefully if wandb/tensorboard
are not installed.
Usage:
tracker = ExperimentTracker(output_dir, config_dict, use_wandb=True, use_tb=True)
tracker.log_train(epoch, {"loss": 0.5, "lr": 1e-4})
tracker.log_val(epoch, {"r@1_q2g": 0.3})
tracker.log_gradients(epoch, grad_norms_dict)
tracker.log_image(epoch, "gradcam/drone", image_tensor)
tracker.close()
"""
import logging
from pathlib import Path
from typing import Any
import torch
LOGGER = logging.getLogger("caption_test.trackers")
def _try_import_wandb():
try:
import wandb
return wandb
except ImportError:
return None
def _try_import_tb():
try:
from torch.utils.tensorboard import SummaryWriter
return SummaryWriter
except ImportError:
return None
class ExperimentTracker:
"""Unified tracker dispatching to W&B, TensorBoard, and CSV.
Args:
output_dir: Base output directory.
config: Dict of hyperparameters to log.
use_wandb: Enable Weights & Biases tracking.
use_tb: Enable TensorBoard tracking.
wandb_project: W&B project name.
wandb_run_name: W&B run name (auto-generated if None).
wandb_entity: W&B entity (team/user).
"""
def __init__(
self,
output_dir: str | Path,
config: dict[str, Any] | None = None,
use_wandb: bool = False,
use_tb: bool = True,
wandb_project: str = "caption-test-gtauav",
wandb_run_name: str | None = None,
wandb_entity: str | None = None,
) -> None:
self.output_dir = Path(output_dir)
self._wandb_run = None
self._tb_writer = None
# W&B init.
if use_wandb:
wandb = _try_import_wandb()
if wandb is not None:
self._wandb_run = wandb.init(
project=wandb_project,
name=wandb_run_name,
entity=wandb_entity,
config=config or {},
dir=str(self.output_dir),
reinit=True,
)
LOGGER.info("W&B initialized: %s", self._wandb_run.url)
else:
LOGGER.warning("wandb not installed, skipping W&B tracking")
# TensorBoard init.
if use_tb:
SummaryWriter = _try_import_tb()
if SummaryWriter is not None:
tb_dir = self.output_dir / "tb_logs"
tb_dir.mkdir(parents=True, exist_ok=True)
self._tb_writer = SummaryWriter(log_dir=str(tb_dir))
LOGGER.info("TensorBoard initialized: %s", tb_dir)
else:
LOGGER.warning("tensorboard not installed, skipping TB tracking")
@property
def has_wandb(self) -> bool:
return self._wandb_run is not None
@property
def has_tb(self) -> bool:
return self._tb_writer is not None
def log_train(self, epoch: int, metrics: dict[str, float], step: int | None = None) -> None:
"""Log training metrics for an epoch."""
if self._wandb_run is not None:
self._wandb_run.log(
{f"train/{k}": v for k, v in metrics.items()},
step=step or epoch,
)
if self._tb_writer is not None:
for k, v in metrics.items():
self._tb_writer.add_scalar(f"train/{k}", v, global_step=step or epoch)
def log_val(self, epoch: int, metrics: dict[str, float], step: int | None = None) -> None:
"""Log validation metrics."""
if self._wandb_run is not None:
self._wandb_run.log(
{f"val/{k}": v for k, v in metrics.items()},
step=step or epoch,
)
if self._tb_writer is not None:
for k, v in metrics.items():
self._tb_writer.add_scalar(f"val/{k}", v, global_step=step or epoch)
def log_gradients(self, epoch: int, grad_norms: dict[str, float], step: int | None = None) -> None:
"""Log gradient norms per parameter group."""
if self._wandb_run is not None:
self._wandb_run.log(
{f"gradients/{k}": v for k, v in grad_norms.items()},
step=step or epoch,
)
if self._tb_writer is not None:
for k, v in grad_norms.items():
self._tb_writer.add_scalar(f"gradients/{k}", v, global_step=step or epoch)
def log_scalar(self, tag: str, value: float, step: int) -> None:
"""Log a single scalar."""
if self._wandb_run is not None:
self._wandb_run.log({tag: value}, step=step)
if self._tb_writer is not None:
self._tb_writer.add_scalar(tag, value, global_step=step)
def log_image(self, tag: str, image: Any, step: int, caption: str | None = None) -> None:
"""Log an image (numpy HWC or torch CHW).
Args:
tag: Image tag/name.
image: numpy array [H,W,C] or torch tensor [C,H,W].
step: Global step.
caption: Optional caption for W&B.
"""
if self._wandb_run is not None:
wandb = _try_import_wandb()
if isinstance(image, torch.Tensor):
image_np = image.detach().cpu().permute(1, 2, 0).numpy()
else:
image_np = image
self._wandb_run.log(
{tag: wandb.Image(image_np, caption=caption)},
step=step,
)
if self._tb_writer is not None:
if isinstance(image, torch.Tensor):
self._tb_writer.add_image(tag, image.detach().cpu(), global_step=step)
else:
self._tb_writer.add_image(tag, image, global_step=step, dataformats="HWC")
def log_histogram(self, tag: str, values: torch.Tensor, step: int) -> None:
"""Log a histogram of values (weights, activations, etc.)."""
if self._wandb_run is not None:
wandb = _try_import_wandb()
self._wandb_run.log(
{tag: wandb.Histogram(values.detach().cpu().numpy())},
step=step,
)
if self._tb_writer is not None:
self._tb_writer.add_histogram(tag, values.detach().cpu(), global_step=step)
def log_model_graph(self, model: torch.nn.Module, input_example: Any = None) -> None:
"""Log model graph to TensorBoard (if available)."""
if self._tb_writer is not None and input_example is not None:
try:
self._tb_writer.add_graph(model, input_example)
except Exception as e:
LOGGER.warning("Failed to log model graph: %s", e)
def watch_model(self, model: torch.nn.Module, log_freq: int = 100) -> None:
"""Enable W&B gradient/weight watching."""
if self._wandb_run is not None:
wandb = _try_import_wandb()
wandb.watch(model, log="all", log_freq=log_freq)
def log_summary(self, summary: dict[str, Any]) -> None:
"""Log final summary metrics (best R@1, etc.)."""
if self._wandb_run is not None:
for k, v in summary.items():
self._wandb_run.summary[k] = v
def close(self) -> None:
"""Flush and close all backends."""
if self._tb_writer is not None:
self._tb_writer.flush()
self._tb_writer.close()
if self._wandb_run is not None:
self._wandb_run.finish()

File diff suppressed because it is too large Load Diff