mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-27 03:27:14 -06:00
- 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>
82 lines
2.9 KiB
Rust
82 lines
2.9 KiB
Rust
//! The CLIP text encoder (24 layers, 1024 wide, causal) that turns a prompt into the token
|
|
//! features SAM 3's detector attends to.
|
|
//!
|
|
//! 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`, CLIP text model), Copyright The HuggingFace
|
|
//! Team and Meta Platforms, Inc.; translated to Rust on candle and restructured. See NOTICE.
|
|
|
|
use candle_core::{Device, Module, Tensor};
|
|
use candle_nn::{Embedding, LayerNorm, VarBuilder};
|
|
|
|
use crate::Result;
|
|
use crate::nn::{Attention, Mlp, ln};
|
|
use crate::tokenizer::CONTEXT;
|
|
|
|
const HIDDEN: usize = 1024;
|
|
const LAYERS: usize = 24;
|
|
const HEADS: usize = 16;
|
|
const EPS: f64 = 1e-5;
|
|
|
|
struct Layer {
|
|
ln1: LayerNorm,
|
|
attn: Attention,
|
|
ln2: LayerNorm,
|
|
mlp: Mlp,
|
|
}
|
|
|
|
pub struct TextEncoder {
|
|
tokens: Embedding,
|
|
positions: Tensor,
|
|
layers: Vec<Layer>,
|
|
final_ln: LayerNorm,
|
|
device: Device,
|
|
}
|
|
|
|
impl TextEncoder {
|
|
/// `vb` at `detector_model.text_encoder.text_model`.
|
|
pub fn new(vb: VarBuilder) -> Result<Self> {
|
|
let e = vb.pp("embeddings");
|
|
let mut layers = Vec::with_capacity(LAYERS);
|
|
for i in 0..LAYERS {
|
|
let l = vb.pp(format!("encoder.layers.{i}"));
|
|
layers.push(Layer {
|
|
ln1: ln(HIDDEN, EPS, l.pp("layer_norm1"))?,
|
|
attn: Attention::named(HIDDEN, HIDDEN, HEADS, "out_proj", l.pp("self_attn"))?,
|
|
ln2: ln(HIDDEN, EPS, l.pp("layer_norm2"))?,
|
|
mlp: Mlp::new(HIDDEN, 4 * HIDDEN, true, l.pp("mlp"))?,
|
|
});
|
|
}
|
|
Ok(Self {
|
|
tokens: candle_nn::embedding(49408, HIDDEN, e.pp("token_embedding"))?,
|
|
positions: e.get((CONTEXT, HIDDEN), "position_embedding.weight")?.unsqueeze(0)?,
|
|
layers,
|
|
final_ln: ln(HIDDEN, EPS, vb.pp("final_layer_norm"))?,
|
|
device: vb.device().clone(),
|
|
})
|
|
}
|
|
|
|
/// `[1, 32, 1024]` features for token `ids` (`valid` marks real tokens).
|
|
pub fn forward(&self, ids: &[u32], valid: &[bool]) -> Result<Tensor> {
|
|
let ids = Tensor::from_slice(ids, (1, ids.len()), &self.device)?;
|
|
let mut x = self.tokens.forward(&ids)?.broadcast_add(&self.positions)?;
|
|
// causal, and padding keys masked
|
|
let n = valid.len();
|
|
let mut bias = vec![0f32; n * n];
|
|
for (q, row) in bias.chunks_mut(n).enumerate() {
|
|
for (k, v) in row.iter_mut().enumerate() {
|
|
if k > q || !valid.get(k).copied().unwrap_or(false) {
|
|
*v = f32::MIN;
|
|
}
|
|
}
|
|
}
|
|
let bias = Tensor::from_vec(bias, (1, 1, n, n), &self.device)?;
|
|
for l in &self.layers {
|
|
let h = l.ln1.forward(&x)?;
|
|
x = (&x + l.attn.forward(&h, &h, &h, Some(&bias))?)?;
|
|
let h = l.ln2.forward(&x)?;
|
|
x = (&x + l.mlp.forward(&h)?)?;
|
|
}
|
|
Ok(self.final_ln.forward(&x)?)
|
|
}
|
|
}
|