Files
lightcraft/crates/segment/src/neck.rs
T
Brandon ThomasandClaude Opus 5.5 b52f43d1d5 M12: lightcraft-segment: Apache-2.0, checked weights, pure-Rust model download
- The crate ports Apache-2.0-only Hugging Face transformers code: it is
  now `license = "Apache-2.0"`, and every ported file carries the
  modification notice Apache-2.0 §4(b) asks for.
- Weights::open checks every tensor's byte range (and the header)
  against the file's length: a truncated or damaged download is an
  error at load, never a short read later; regression test with
  hostile offsets.
- fetch/: the model download, only when asked for. An ordered mirror
  list (env LIGHTCRAFT_SAM3_MIRRORS, a mirrors file, DEFAULT_MIRRORS —
  empty until LightCraft's CDN exists, TODO for the maintainer),
  fallback to the next mirror, resume via Range, redirects, connect and
  stall timeouts, cancel, size caps, exact size + SHA-256 (pinned from
  tools/install-sam3.sh) before an atomic rename; damaged files are
  deleted, HTTP error bodies never written.
- HTTP/1.1 client over std sockets + rustls with the RustCrypto provider
  (rustls-rustcrypto) and webpki-roots: no ring / aws-lc-rs, nothing
  compiled from C (no new `cc` users in the app's build graph).
- Tests against a local TcpListener server (no internet): fallback,
  redirects, chunked bodies, resume, 416, hash mismatch, too much data,
  stalls, cancel.
- *.safetensors are gitignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:56:49 -04:00

78 lines
2.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! The feature pyramid (`Sam3VisionNeck`): the backbone's 72 × 72 map at four scales
//! (288, 144, 72 and 36 per side), each projected to 256 channels.
//!
//! Modified work (Apache License 2.0, §4(b)): ported by the LightCraft contributors in 2026 from
//! the Python/PyTorch SAM 3 code of Hugging Face Transformers (`models/sam3/modeling_sam3.py`), Copyright The HuggingFace
//! Team and Meta Platforms, Inc.; translated to Rust on candle and restructured. See NOTICE.
use candle_core::{Module, Tensor};
use candle_nn::{Conv2d, Conv2dConfig, ConvTranspose2d, ConvTranspose2dConfig, VarBuilder};
use crate::Result;
use crate::vit::HIDDEN;
pub const FPN: usize = 256;
enum Scale {
/// Two 2× transposed convolutions with GELU between (4×).
Up4(ConvTranspose2d, ConvTranspose2d),
Up2(ConvTranspose2d),
Same,
/// 2 × 2 max pooling (½×).
Down2,
}
struct Level {
scale: Scale,
proj1: Conv2d,
proj2: Conv2d,
}
pub struct Neck {
levels: Vec<Level>,
}
impl Neck {
/// `vb` at `…neck` (detector) or `tracker_neck`.
pub fn new(vb: VarBuilder) -> Result<Self> {
let up = ConvTranspose2dConfig { stride: 2, ..Default::default() };
let mut levels = Vec::new();
for (i, factor) in [4.0f32, 2.0, 1.0, 0.5].into_iter().enumerate() {
let l = vb.pp(format!("fpn_layers.{i}"));
let s = l.pp("scale_layers");
let (scale, inter) = if factor == 4.0 {
let a = candle_nn::conv_transpose2d(HIDDEN, HIDDEN / 2, 2, up, s.pp("0"))?;
let b = candle_nn::conv_transpose2d(HIDDEN / 2, HIDDEN / 4, 2, up, s.pp("2"))?;
(Scale::Up4(a, b), HIDDEN / 4)
} else if factor == 2.0 {
(Scale::Up2(candle_nn::conv_transpose2d(HIDDEN, HIDDEN / 2, 2, up, s.pp("0"))?), HIDDEN / 2)
} else if factor == 1.0 {
(Scale::Same, HIDDEN)
} else {
(Scale::Down2, HIDDEN)
};
levels.push(Level {
scale,
proj1: candle_nn::conv2d(inter, FPN, 1, Conv2dConfig::default(), l.pp("proj1"))?,
proj2: candle_nn::conv2d(FPN, FPN, 3, Conv2dConfig { padding: 1, ..Default::default() }, l.pp("proj2"))?,
});
}
Ok(Self { levels })
}
/// `x`: the backbone's `[1, 1024, 72, 72]`; `n`: how many levels (from the finest) to compute.
pub fn forward(&self, x: &Tensor, n: usize) -> Result<Vec<Tensor>> {
let mut out = Vec::with_capacity(n);
for l in self.levels.iter().take(n) {
let y = match &l.scale {
Scale::Up4(a, b) => b.forward(&a.forward(x)?.gelu_erf()?)?,
Scale::Up2(a) => a.forward(x)?,
Scale::Same => x.clone(),
Scale::Down2 => x.max_pool2d(2)?,
};
out.push(l.proj2.forward(&l.proj1.forward(&y)?)?);
}
Ok(out)
}
}