3421 lines
153 KiB
C++
3421 lines
153 KiB
C++
#include "trellis2.h"
|
|
|
|
#include "ggml.h"
|
|
#include "ggml-alloc.h"
|
|
#include "ggml-backend.h"
|
|
#include "ggml-cpu.h"
|
|
#include "gguf.h"
|
|
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <array>
|
|
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <limits>
|
|
#include <cstdlib>
|
|
#include <random>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
|
|
/*****************************************************************************
|
|
** Helpers
|
|
*****************************************************************************/
|
|
|
|
namespace {
|
|
|
|
inline void set_error(std::string * error, const std::string & msg) {
|
|
if (error) *error = msg;
|
|
}
|
|
|
|
// Read a little-endian uint32 from a byte cursor, advancing it.
|
|
inline bool read_u32_le(const uint8_t *& p, const uint8_t * end, uint32_t & out) {
|
|
if (p + 4 > end) return false;
|
|
out = (uint32_t) p[0]
|
|
| ((uint32_t) p[1] << 8)
|
|
| ((uint32_t) p[2] << 16)
|
|
| ((uint32_t) p[3] << 24);
|
|
p += 4;
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/*****************************************************************************
|
|
** Version
|
|
*****************************************************************************/
|
|
|
|
const char * trellis2_version(void) {
|
|
return TRELLIS2_VERSION;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** .dinodata loader
|
|
**
|
|
** Binary layout (little-endian), produced by dump_dinodata.py:
|
|
** magic : 8 bytes "DINOCOND"
|
|
** version : uint32
|
|
** dtype : uint32 (0 = float32) -- only float32 is supported here
|
|
** ndim : uint32
|
|
** shape : ndim * uint32 (C-contiguous in this order)
|
|
** payload : prod(shape) * float32 (little-endian)
|
|
*****************************************************************************/
|
|
|
|
bool trellis2_load_dinodata(const std::string & path,
|
|
trellis2_dino_cond & out,
|
|
std::string * error) {
|
|
out = trellis2_dino_cond{};
|
|
|
|
std::ifstream f(path, std::ios::binary);
|
|
if (!f) {
|
|
set_error(error, "cannot open file: " + path);
|
|
return false;
|
|
}
|
|
|
|
// Slurp the whole file — these are a few MB, well within memory.
|
|
std::vector<uint8_t> buf((std::istreambuf_iterator<char>(f)),
|
|
std::istreambuf_iterator<char>());
|
|
if (buf.size() < 8 + 12) {
|
|
set_error(error, "file too small to contain a .dinodata header");
|
|
return false;
|
|
}
|
|
|
|
const uint8_t * p = buf.data();
|
|
const uint8_t * end = buf.data() + buf.size();
|
|
|
|
static const char MAGIC[8] = {'D','I','N','O','C','O','N','D'};
|
|
if (std::memcmp(p, MAGIC, 8) != 0) {
|
|
set_error(error, "bad magic (expected 'DINOCOND')");
|
|
return false;
|
|
}
|
|
p += 8;
|
|
|
|
uint32_t version = 0, dtype = 0, ndim = 0;
|
|
if (!read_u32_le(p, end, version) ||
|
|
!read_u32_le(p, end, dtype) ||
|
|
!read_u32_le(p, end, ndim)) {
|
|
set_error(error, "truncated header");
|
|
return false;
|
|
}
|
|
|
|
if (dtype != 0) {
|
|
set_error(error, "unsupported dtype " + std::to_string(dtype) +
|
|
" (only 0=float32 is supported)");
|
|
return false;
|
|
}
|
|
if (ndim == 0 || ndim > 8) {
|
|
set_error(error, "implausible ndim " + std::to_string(ndim));
|
|
return false;
|
|
}
|
|
|
|
// The shape product is attacker-controlled and must not wrap: cap the
|
|
// element count well above any real conditioning tensor but far below
|
|
// anything that could overflow want_bytes or exhaust memory.
|
|
const int64_t MAX_ELEMS = (int64_t) 1 << 31;
|
|
|
|
std::vector<int64_t> shape(ndim);
|
|
int64_t total = 1;
|
|
for (uint32_t i = 0; i < ndim; ++i) {
|
|
uint32_t dim = 0;
|
|
if (!read_u32_le(p, end, dim)) {
|
|
set_error(error, "truncated shape");
|
|
return false;
|
|
}
|
|
if (dim == 0 || (int64_t) dim > MAX_ELEMS || total > MAX_ELEMS / (int64_t) dim) {
|
|
set_error(error, "implausible shape (zero or overflowing element count)");
|
|
return false;
|
|
}
|
|
shape[i] = (int64_t) dim;
|
|
total *= (int64_t) dim;
|
|
}
|
|
|
|
const size_t want_bytes = (size_t) total * sizeof(float);
|
|
const size_t have_bytes = (size_t) (end - p);
|
|
if (have_bytes < want_bytes) {
|
|
set_error(error, "payload truncated: have " + std::to_string(have_bytes) +
|
|
" bytes, need " + std::to_string(want_bytes));
|
|
return false;
|
|
}
|
|
|
|
out.shape = std::move(shape);
|
|
out.format_version = version;
|
|
out.data.resize((size_t) total);
|
|
// Little-endian float32 on the host (all targets we build for are LE).
|
|
std::memcpy(out.data.data(), p, want_bytes);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** Fingerprints
|
|
*****************************************************************************/
|
|
|
|
trellis2_dino_fingerprint
|
|
trellis2_dino_fingerprints(const trellis2_dino_cond & cond) {
|
|
trellis2_dino_fingerprint fp;
|
|
fp.count = cond.data.size();
|
|
if (cond.data.empty()) {
|
|
return fp;
|
|
}
|
|
|
|
float vmin = std::numeric_limits<float>::infinity();
|
|
float vmax = -std::numeric_limits<float>::infinity();
|
|
double sum = 0.0;
|
|
double sumsq = 0.0;
|
|
for (float v : cond.data) {
|
|
if (v < vmin) vmin = v;
|
|
if (v > vmax) vmax = v;
|
|
sum += (double) v;
|
|
sumsq += (double) v * (double) v;
|
|
}
|
|
|
|
fp.vmin = vmin;
|
|
fp.vmax = vmax;
|
|
fp.sum = sum;
|
|
fp.mean = sum / (double) fp.count;
|
|
fp.l2 = std::sqrt(sumsq);
|
|
return fp;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** Sparse-structure flow DiT (stage 1) — GGUF loader
|
|
*****************************************************************************/
|
|
|
|
struct trellis2_ss_flow_model {
|
|
gguf_context * gguf = nullptr;
|
|
ggml_context * ctx = nullptr;
|
|
trellis2_ss_flow_hparams hp;
|
|
bool has_data = false; // true if weight payloads were read (load_tensors)
|
|
|
|
// Compute backend (auto-selected: GPU if available, else CPU) and the
|
|
// buffer holding the weights on that backend. Only set when has_data.
|
|
ggml_backend_t backend = nullptr;
|
|
ggml_backend_buffer_t weights_buf = nullptr;
|
|
std::string backend_name;
|
|
|
|
// name -> tensor (into ctx); built once at load for O(1) graph wiring later.
|
|
std::unordered_map<std::string, ggml_tensor *> tensors;
|
|
};
|
|
|
|
namespace {
|
|
|
|
// Pick the best available compute backend: the first GPU device exposed by the
|
|
// ggml backend registry (CUDA / Metal / Vulkan / ...), falling back to CPU.
|
|
// Mirrors sam3.cpp's "use a GPU backend automatically if one is available".
|
|
// device: nullptr/"auto" = GPU if available else CPU; "cpu" = force CPU.
|
|
// The TRELLIS2_DEVICE env var overrides "auto".
|
|
ggml_backend_t init_best_backend(std::string & name_out, const char * device = nullptr) {
|
|
std::string want = device ? device : "";
|
|
if (want.empty() || want == "auto") {
|
|
if (const char * env = std::getenv("TRELLIS2_DEVICE")) want = env;
|
|
}
|
|
if (want != "cpu")
|
|
for (size_t i = 0; i < ggml_backend_dev_count(); ++i) {
|
|
ggml_backend_dev_t dev = ggml_backend_dev_get(i);
|
|
if (ggml_backend_dev_type(dev) == GGML_BACKEND_DEVICE_TYPE_GPU) {
|
|
ggml_backend_t b = ggml_backend_dev_init(dev, nullptr);
|
|
if (b) {
|
|
const char * d = ggml_backend_dev_description(dev);
|
|
name_out = d ? d : ggml_backend_dev_name(dev);
|
|
return b;
|
|
}
|
|
}
|
|
}
|
|
name_out = "CPU";
|
|
ggml_backend_t cpu = ggml_backend_cpu_init();
|
|
// ggml defaults to 4 threads; use every core (TRELLIS2_N_THREADS overrides).
|
|
int n_threads = (int) std::thread::hardware_concurrency();
|
|
if (const char * env = std::getenv("TRELLIS2_N_THREADS")) {
|
|
const int v = std::atoi(env);
|
|
if (v > 0) n_threads = v;
|
|
}
|
|
if (n_threads > 0) ggml_backend_cpu_set_n_threads(cpu, n_threads);
|
|
return cpu;
|
|
}
|
|
|
|
// KV readers with defaults (return the default if the key is absent).
|
|
uint32_t kv_u32(const gguf_context * g, const char * key, uint32_t def) {
|
|
const int64_t id = gguf_find_key(g, key);
|
|
return id < 0 ? def : gguf_get_val_u32(g, id);
|
|
}
|
|
float kv_f32(const gguf_context * g, const char * key, float def) {
|
|
const int64_t id = gguf_find_key(g, key);
|
|
return id < 0 ? def : gguf_get_val_f32(g, id);
|
|
}
|
|
bool kv_bool(const gguf_context * g, const char * key, bool def) {
|
|
const int64_t id = gguf_find_key(g, key);
|
|
return id < 0 ? def : gguf_get_val_bool(g, id);
|
|
}
|
|
const char * kv_str(const gguf_context * g, const char * key, const char * def) {
|
|
const int64_t id = gguf_find_key(g, key);
|
|
return id < 0 ? def : gguf_get_val_str(g, id);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
size_t trellis2_gpu_free_vram(void) {
|
|
ggml_backend_dev_t dev = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_GPU);
|
|
if (!dev) return 0; // CPU-only build/host
|
|
size_t free = 0, total = 0;
|
|
ggml_backend_dev_memory(dev, &free, &total);
|
|
return free;
|
|
}
|
|
|
|
trellis2_ss_flow_model *
|
|
trellis2_ss_flow_load(const std::string & path, bool load_tensors, std::string * error,
|
|
const char * device) {
|
|
auto * m = new trellis2_ss_flow_model();
|
|
|
|
// Always parse metadata only; the weights are then allocated on the chosen
|
|
// backend and the payloads streamed in from the file (so the GPU can use
|
|
// them directly). This is the standard llama.cpp / stable-diffusion.cpp path.
|
|
gguf_init_params params;
|
|
params.no_alloc = true;
|
|
params.ctx = &m->ctx;
|
|
|
|
m->gguf = gguf_init_from_file(path.c_str(), params);
|
|
if (!m->gguf) {
|
|
set_error(error, "gguf_init_from_file failed (not a GGUF file?): " + path);
|
|
delete m;
|
|
return nullptr;
|
|
}
|
|
|
|
// Sanity-check the architecture tag.
|
|
const char * arch = kv_str(m->gguf, "general.architecture", "");
|
|
if (std::strcmp(arch, "trellis2-ss-flow") != 0) {
|
|
set_error(error, std::string("unexpected architecture '") + arch +
|
|
"' (expected 'trellis2-ss-flow')");
|
|
trellis2_ss_flow_free(m);
|
|
return nullptr;
|
|
}
|
|
|
|
trellis2_ss_flow_hparams & hp = m->hp;
|
|
const char * P = "trellis2.ss_flow.";
|
|
auto K = [&](const char * suffix) { return std::string(P) + suffix; };
|
|
|
|
hp.resolution = (int32_t) kv_u32 (m->gguf, K("resolution").c_str(), 0);
|
|
hp.in_channels = (int32_t) kv_u32 (m->gguf, K("in_channels").c_str(), 0);
|
|
hp.out_channels = (int32_t) kv_u32 (m->gguf, K("out_channels").c_str(), 0);
|
|
hp.model_channels = (int32_t) kv_u32 (m->gguf, K("model_channels").c_str(), 0);
|
|
hp.cond_channels = (int32_t) kv_u32 (m->gguf, K("cond_channels").c_str(), 0);
|
|
hp.num_blocks = (int32_t) kv_u32 (m->gguf, K("num_blocks").c_str(), 0);
|
|
hp.num_heads = (int32_t) kv_u32 (m->gguf, K("num_heads").c_str(), 0);
|
|
hp.mlp_ratio = kv_f32 (m->gguf, K("mlp_ratio").c_str(), 0.0f);
|
|
hp.share_mod = kv_bool(m->gguf, K("share_mod").c_str(), false) ? 1 : 0;
|
|
hp.qk_rms_norm = kv_bool(m->gguf, K("qk_rms_norm").c_str(), false) ? 1 : 0;
|
|
hp.qk_rms_norm_cross = kv_bool(m->gguf, K("qk_rms_norm_cross").c_str(), false) ? 1 : 0;
|
|
hp.rope_freq_min = kv_f32 (m->gguf, K("rope_freq_min").c_str(), 1.0f);
|
|
hp.rope_freq_base = kv_f32 (m->gguf, K("rope_freq_base").c_str(), 10000.0f);
|
|
hp.file_type = (int32_t) kv_u32 (m->gguf, "general.file_type", 0);
|
|
std::snprintf(hp.pe_mode, sizeof(hp.pe_mode), "%s",
|
|
kv_str(m->gguf, K("pe_mode").c_str(), "rope"));
|
|
|
|
// Build name -> tensor map.
|
|
for (ggml_tensor * t = ggml_get_first_tensor(m->ctx); t != nullptr;
|
|
t = ggml_get_next_tensor(m->ctx, t)) {
|
|
m->tensors[t->name] = t;
|
|
}
|
|
|
|
if (load_tensors) {
|
|
// Allocate all weights on the auto-selected backend, then stream the
|
|
// payloads from the file into that buffer.
|
|
m->backend = init_best_backend(m->backend_name, device);
|
|
m->weights_buf = ggml_backend_alloc_ctx_tensors(m->ctx, m->backend);
|
|
if (!m->weights_buf) {
|
|
set_error(error, "failed to allocate weights on backend " + m->backend_name);
|
|
trellis2_ss_flow_free(m);
|
|
return nullptr;
|
|
}
|
|
|
|
std::ifstream fin(path, std::ios::binary);
|
|
if (!fin) {
|
|
set_error(error, "cannot reopen file for weight data: " + path);
|
|
trellis2_ss_flow_free(m);
|
|
return nullptr;
|
|
}
|
|
const size_t data_off = gguf_get_data_offset(m->gguf);
|
|
const int64_t nt = gguf_get_n_tensors(m->gguf);
|
|
std::vector<uint8_t> buf;
|
|
for (int64_t i = 0; i < nt; ++i) {
|
|
const char * name = gguf_get_tensor_name(m->gguf, i);
|
|
ggml_tensor * t = m->tensors[name];
|
|
const size_t nb = ggml_nbytes(t);
|
|
const size_t off = data_off + gguf_get_tensor_offset(m->gguf, i);
|
|
buf.resize(nb);
|
|
fin.seekg((std::streamoff) off, std::ios::beg);
|
|
if (!fin.read(reinterpret_cast<char *>(buf.data()), (std::streamsize) nb)) {
|
|
set_error(error, std::string("failed reading weight '") + name + "' from file");
|
|
trellis2_ss_flow_free(m);
|
|
return nullptr;
|
|
}
|
|
ggml_backend_tensor_set(t, buf.data(), 0, nb);
|
|
}
|
|
m->has_data = true;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
void trellis2_ss_flow_free(trellis2_ss_flow_model * m) {
|
|
if (!m) return;
|
|
if (m->weights_buf) ggml_backend_buffer_free(m->weights_buf);
|
|
if (m->backend) ggml_backend_free(m->backend);
|
|
if (m->gguf) gguf_free(m->gguf);
|
|
if (m->ctx) ggml_free(m->ctx);
|
|
delete m;
|
|
}
|
|
|
|
const char * trellis2_ss_flow_backend_name(const trellis2_ss_flow_model * m) {
|
|
return (m && !m->backend_name.empty()) ? m->backend_name.c_str() : "none";
|
|
}
|
|
|
|
const trellis2_ss_flow_hparams &
|
|
trellis2_ss_flow_hparams_of(const trellis2_ss_flow_model * m) {
|
|
return m->hp;
|
|
}
|
|
|
|
int trellis2_ss_flow_n_tensors(const trellis2_ss_flow_model * m) {
|
|
return m ? (int) gguf_get_n_tensors(m->gguf) : 0;
|
|
}
|
|
|
|
bool trellis2_ss_flow_get_tensor_info(const trellis2_ss_flow_model * m,
|
|
int i, trellis2_tensor_info & out) {
|
|
if (!m || i < 0 || i >= (int) gguf_get_n_tensors(m->gguf)) return false;
|
|
const char * name = gguf_get_tensor_name(m->gguf, i);
|
|
out.name = name;
|
|
|
|
ggml_tensor * t = ggml_get_tensor(m->ctx, name);
|
|
if (!t) return false;
|
|
out.n_dims = ggml_n_dims(t);
|
|
for (int d = 0; d < 4; ++d) out.ne[d] = t->ne[d];
|
|
out.ggml_type = (int) t->type;
|
|
out.type_name = ggml_type_name(t->type);
|
|
out.n_bytes = ggml_nbytes(t);
|
|
return true;
|
|
}
|
|
|
|
bool trellis2_ss_flow_has_tensor(const trellis2_ss_flow_model * m,
|
|
const std::string & name) {
|
|
return m && m->tensors.find(name) != m->tensors.end();
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** Sparse-structure flow DiT — forward pass (CPU backend)
|
|
**
|
|
** Mirrors SparseStructureFlowModel.forward + ModulatedTransformerCrossBlock:
|
|
** h = input_layer(x) # [C, N]
|
|
** t_emb = adaLN(SiLU stack)(timestep_embedding(t)) # [6C] shared modulation
|
|
** for each of num_blocks cross-blocks:
|
|
** (shift/scale/gate)_{msa,mlp} = modulation_b + t_emb
|
|
** h += gate_msa * self_attn( modulate(LN0(h)) ) # RoPE + QK-RMSNorm
|
|
** h += cross_attn( LN1_affine(h), cond ) # QK-RMSNorm, no RoPE
|
|
** h += gate_mlp * mlp( modulate(LN2(h)) ) # GELU-tanh FFN
|
|
** out = out_layer(LayerNorm(h)) # [out_channels, N]
|
|
*****************************************************************************/
|
|
|
|
namespace {
|
|
|
|
// Scaled dot-product attention via ggml_flash_attn_ext (tiled online softmax,
|
|
// O(L) memory). q3/k3/v3 are [head_dim, n_head, L]; returns [n_head*head_dim, L_q].
|
|
//
|
|
// Flash is the default for both flow DiTs: it is bit-faithful to full softmax on
|
|
// CPU with F32 accumulation (validated to ~1e-4 rel-L2, identical to the exact
|
|
// materialized path) but avoids the [L_k, L_q, heads] score matrix — which is
|
|
// both the memory wall (the HR cascade's ~49k voxels would need >100 GB) and,
|
|
// on GPU, ~30% of the forward's wall time (the softmax + the permute/cont copies
|
|
// around it). On the CUDA F16-MMA kernel flash costs ~3e-3 rel-L2 per forward,
|
|
// immaterial to the final mesh. Set TRELLIS2_SDPA_EXACT to force the old
|
|
// materialized path (e.g. to reproduce the tightest GPU numbers).
|
|
ggml_tensor * sdpa_auto(ggml_context * ctx, ggml_tensor * q3, ggml_tensor * k3,
|
|
ggml_tensor * v3, int C, float scale) {
|
|
ggml_tensor * qp = ggml_cont(ctx, ggml_permute(ctx, q3, 0, 2, 1, 3)); // [hd, Lq, H]
|
|
ggml_tensor * kp = ggml_cont(ctx, ggml_permute(ctx, k3, 0, 2, 1, 3)); // [hd, Lk, H]
|
|
ggml_tensor * vp = ggml_cont(ctx, ggml_permute(ctx, v3, 0, 2, 1, 3)); // [hd, Lk, H]
|
|
|
|
static const bool exact = std::getenv("TRELLIS2_SDPA_EXACT") != nullptr;
|
|
if (!exact) {
|
|
ggml_tensor * o = ggml_flash_attn_ext(ctx, qp, kp, vp, nullptr, scale, 0.0f, 0.0f);
|
|
ggml_flash_attn_ext_set_prec(o, GGML_PREC_F32);
|
|
return ggml_reshape_2d(ctx, o, C, o->ne[2]); // [C, Lq]
|
|
}
|
|
ggml_tensor * sc = ggml_mul_mat(ctx, kp, qp); // [Lk, Lq, H]
|
|
sc = ggml_soft_max_ext(ctx, sc, nullptr, scale, 0.0f);
|
|
ggml_tensor * vt = ggml_cont(ctx, ggml_permute(ctx, vp, 1, 0, 2, 3)); // [Lk, hd, H]
|
|
ggml_tensor * o = ggml_mul_mat(ctx, vt, sc); // [hd, Lq, H]
|
|
o = ggml_cont(ctx, ggml_permute(ctx, o, 0, 2, 1, 3)); // [hd, H, Lq]
|
|
return ggml_reshape_2d(ctx, o, C, o->ne[2]); // [C, Lq]
|
|
}
|
|
|
|
// Sinusoidal timestep embedding (cos|sin), matching TimestepEmbedder.
|
|
std::vector<float> timestep_embedding(float t, int dim) {
|
|
std::vector<float> e((size_t) dim, 0.0f);
|
|
const int half = dim / 2;
|
|
for (int i = 0; i < half; ++i) {
|
|
const float freq = std::exp(-std::log(10000.0f) * (float) i / (float) half);
|
|
const float arg = t * freq;
|
|
e[i] = std::cos(arg);
|
|
e[half + i] = std::sin(arg);
|
|
}
|
|
return e; // dim is even here (256) so no padding needed
|
|
}
|
|
|
|
// Precompute the interleaved 3D-RoPE cos/sin tables for an R^3 grid.
|
|
// Layout matches q reshaped to [head_dim, n_heads, N]: cos/sin are [head_dim, 1, N]
|
|
// with cos[n*head_dim + 2p] == cos[n*head_dim + 2p+1] == cos(theta_p(n)).
|
|
void rope_tables(int res, int head_dim, float freq_min, float freq_base,
|
|
std::vector<float> & cos_t, std::vector<float> & sin_t) {
|
|
const int dim = 3; // 3 spatial axes
|
|
const int freq_dim = head_dim / 2 / dim; // 21 for head_dim 128
|
|
const int N = res * res * res;
|
|
|
|
std::vector<float> freqs((size_t) freq_dim);
|
|
for (int mi = 0; mi < freq_dim; ++mi) {
|
|
freqs[mi] = freq_min / std::pow(freq_base, (float) mi / (float) freq_dim);
|
|
}
|
|
|
|
cos_t.assign((size_t) head_dim * N, 1.0f);
|
|
sin_t.assign((size_t) head_dim * N, 0.0f);
|
|
const int pairs = head_dim / 2; // 64
|
|
for (int n = 0; n < N; ++n) {
|
|
const int coord[3] = { n / (res * res), (n / res) % res, n % res };
|
|
for (int p = 0; p < pairs; ++p) {
|
|
float theta = 0.0f; // p == 63 -> pad (theta 0)
|
|
if (p < dim * freq_dim) { // p in 0..62
|
|
theta = (float) coord[p / freq_dim] * freqs[p % freq_dim];
|
|
}
|
|
const size_t base = (size_t) n * head_dim + (size_t) 2 * p;
|
|
cos_t[base] = cos_t[base + 1] = std::cos(theta);
|
|
sin_t[base] = sin_t[base + 1] = std::sin(theta);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool trellis2_ss_flow_forward(trellis2_ss_flow_model * m,
|
|
const float * x, float t,
|
|
const float * cond, int cond_tokens, int cond_channels,
|
|
float * out, std::string * error) {
|
|
if (!m) { set_error(error, "null model"); return false; }
|
|
if (!m->has_data) { set_error(error, "model loaded metadata-only; reload with load_tensors=true"); return false; }
|
|
|
|
const trellis2_ss_flow_hparams & hp = m->hp;
|
|
if (std::strcmp(hp.pe_mode, "rope") != 0) { set_error(error, "only pe_mode=rope is implemented"); return false; }
|
|
if (!hp.share_mod) { set_error(error, "only share_mod=true is implemented"); return false; }
|
|
if (cond_channels != hp.cond_channels) { set_error(error, "cond_channels mismatch"); return false; }
|
|
|
|
const bool t2_timing = std::getenv("TRELLIS2_TIMING") != nullptr;
|
|
auto t_now = [] { return std::chrono::steady_clock::now(); };
|
|
auto t_ms = [](auto a, auto b) { return std::chrono::duration<double, std::milli>(b - a).count(); };
|
|
auto t_start = t_now();
|
|
|
|
const int C = hp.model_channels; // 1536
|
|
const int R = hp.resolution; // 16
|
|
const int N = R * R * R; // 4096 tokens
|
|
const int H = hp.num_heads; // 12
|
|
const int hd = hp.head_dim(); // 128
|
|
const int Lkv = cond_tokens; // 1029
|
|
const float attn_scale = 1.0f / std::sqrt((float) hd);
|
|
|
|
std::string missing;
|
|
auto W = [&](const std::string & n) -> ggml_tensor * {
|
|
auto it = m->tensors.find(n);
|
|
if (it == m->tensors.end()) { if (missing.empty()) missing = n; return nullptr; }
|
|
return it->second;
|
|
};
|
|
|
|
// ── compute graph context (metadata only; gallocr allocates data) ────────
|
|
const size_t mem = ggml_tensor_overhead() * 32768 + ggml_graph_overhead_custom(32768, false);
|
|
ggml_init_params ip{ mem, nullptr, /*no_alloc*/ true };
|
|
ggml_context * ctx = ggml_init(ip);
|
|
ggml_cgraph * gf = ggml_new_graph_custom(ctx, 32768, false);
|
|
|
|
// ── input leaves ─────────────────────────────────────────────────────────
|
|
ggml_tensor * x_t = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, N, hp.in_channels); // channel-major [N, Cin]
|
|
ggml_tensor * temb = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 256);
|
|
ggml_tensor * cos_t = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, hd, 1, N);
|
|
ggml_tensor * sin_t = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, hd, 1, N);
|
|
ggml_tensor * cnd = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, cond_channels, Lkv); // [ctx_ch, Lkv]
|
|
ggml_set_input(x_t); ggml_set_name(x_t, "x");
|
|
ggml_set_input(temb); ggml_set_name(temb, "temb");
|
|
ggml_set_input(cos_t);
|
|
ggml_set_input(sin_t);
|
|
ggml_set_input(cnd);
|
|
|
|
auto lin = [&](ggml_tensor * in, const std::string & pfx) -> ggml_tensor * {
|
|
ggml_tensor * y = ggml_mul_mat(ctx, W(pfx + ".weight"), in);
|
|
ggml_tensor * b = W(pfx + ".bias");
|
|
if (b) y = ggml_add(ctx, y, b);
|
|
return y;
|
|
};
|
|
// h * (1 + scale) + shift, broadcasting the [C] vectors over tokens.
|
|
auto modulate = [&](ggml_tensor * h, ggml_tensor * scale, ggml_tensor * shift) {
|
|
return ggml_add(ctx, ggml_add(ctx, ggml_mul(ctx, h, scale), h), shift);
|
|
};
|
|
// interleaved RoPE on a [hd, H, N] tensor using the cos/sin tables.
|
|
auto rope = [&](ggml_tensor * q3) -> ggml_tensor * {
|
|
ggml_tensor * q4 = ggml_reshape_4d(ctx, q3, 2, hd / 2, H, N);
|
|
ggml_tensor * q0 = ggml_cont(ctx, ggml_view_4d(ctx, q4, 1, hd / 2, H, N,
|
|
q4->nb[1], q4->nb[2], q4->nb[3], 0));
|
|
ggml_tensor * q1 = ggml_cont(ctx, ggml_view_4d(ctx, q4, 1, hd / 2, H, N,
|
|
q4->nb[1], q4->nb[2], q4->nb[3], q4->nb[0]));
|
|
ggml_tensor * swap = ggml_concat(ctx, ggml_neg(ctx, q1), q0, 0); // [2,hd/2,H,N]
|
|
swap = ggml_reshape_3d(ctx, swap, hd, H, N);
|
|
return ggml_add(ctx, ggml_mul(ctx, q3, cos_t), ggml_mul(ctx, swap, sin_t));
|
|
};
|
|
// QK-RMSNorm: F.normalize(x)*gamma*sqrt(hd) == rms_norm(x)*gamma (sqrt cancels).
|
|
auto qk_norm = [&](ggml_tensor * v3, const std::string & gname) {
|
|
return ggml_mul(ctx, ggml_rms_norm(ctx, v3, 1e-12f), W(gname));
|
|
};
|
|
// scaled-dot-product attention (flash: O(L) memory); q3/k3/v3 are [hd,H,L].
|
|
auto sdpa = [&](ggml_tensor * q3, ggml_tensor * k3, ggml_tensor * v3) {
|
|
return sdpa_auto(ctx, q3, k3, v3, C, attn_scale);
|
|
};
|
|
|
|
const size_t es = sizeof(float);
|
|
|
|
// ── stem: input projection (+ no additive PE in rope mode) ───────────────
|
|
ggml_tensor * h = ggml_cont(ctx, ggml_transpose(ctx, x_t)); // [Cin, N]
|
|
h = lin(h, "input_layer"); // [C, N]
|
|
|
|
// ── shared modulation from the timestep ──────────────────────────────────
|
|
ggml_tensor * te = lin(temb, "t_embedder.mlp.0");
|
|
te = ggml_silu(ctx, te);
|
|
te = lin(te, "t_embedder.mlp.2"); // [C]
|
|
ggml_tensor * tmod = lin(ggml_silu(ctx, te), "adaLN_modulation.1"); // [6C]
|
|
|
|
ggml_tensor * cond_h = cnd; // [ctx_ch, Lkv]
|
|
|
|
for (int b = 0; b < hp.num_blocks; ++b) {
|
|
const std::string blk = "blocks." + std::to_string(b);
|
|
ggml_tensor * mods = ggml_add(ctx, W(blk + ".modulation"), tmod); // [6C]
|
|
auto chunk = [&](int idx) {
|
|
return ggml_view_1d(ctx, mods, C, (size_t) idx * C * es);
|
|
};
|
|
ggml_tensor * shift_msa = chunk(0), * scale_msa = chunk(1), * gate_msa = chunk(2);
|
|
ggml_tensor * shift_mlp = chunk(3), * scale_mlp = chunk(4), * gate_mlp = chunk(5);
|
|
|
|
// self-attention (norm1 affine-free, modulated; RoPE + QK-RMSNorm)
|
|
ggml_tensor * hn = modulate(ggml_norm(ctx, h, 1e-6f), scale_msa, shift_msa);
|
|
ggml_tensor * qkv = lin(hn, blk + ".self_attn.to_qkv"); // [3C, N]
|
|
ggml_tensor * q = ggml_reshape_3d(ctx, ggml_cont(ctx, ggml_view_2d(ctx, qkv, C, N, qkv->nb[1], 0)), hd, H, N);
|
|
ggml_tensor * k = ggml_reshape_3d(ctx, ggml_cont(ctx, ggml_view_2d(ctx, qkv, C, N, qkv->nb[1], (size_t)C*es)), hd, H, N);
|
|
ggml_tensor * v = ggml_reshape_3d(ctx, ggml_cont(ctx, ggml_view_2d(ctx, qkv, C, N, qkv->nb[1], (size_t)2*C*es)), hd, H, N);
|
|
q = rope(qk_norm(q, blk + ".self_attn.q_rms_norm.gamma"));
|
|
k = rope(qk_norm(k, blk + ".self_attn.k_rms_norm.gamma"));
|
|
ggml_tensor * sa = lin(sdpa(q, k, v), blk + ".self_attn.to_out");
|
|
h = ggml_add(ctx, h, ggml_mul(ctx, sa, gate_msa));
|
|
|
|
// cross-attention (norm2 affine; QK-RMSNorm, no RoPE, no gate)
|
|
ggml_tensor * h2 = ggml_norm(ctx, h, 1e-6f);
|
|
h2 = ggml_add(ctx, ggml_mul(ctx, h2, W(blk + ".norm2.weight")), W(blk + ".norm2.bias"));
|
|
ggml_tensor * cq = ggml_reshape_3d(ctx, lin(h2, blk + ".cross_attn.to_q"), hd, H, N);
|
|
cq = qk_norm(cq, blk + ".cross_attn.q_rms_norm.gamma");
|
|
ggml_tensor * kv = lin(cond_h, blk + ".cross_attn.to_kv"); // [2C, Lkv]
|
|
ggml_tensor * ck = ggml_reshape_3d(ctx, ggml_cont(ctx, ggml_view_2d(ctx, kv, C, Lkv, kv->nb[1], 0)), hd, H, Lkv);
|
|
ggml_tensor * cv = ggml_reshape_3d(ctx, ggml_cont(ctx, ggml_view_2d(ctx, kv, C, Lkv, kv->nb[1], (size_t)C*es)), hd, H, Lkv);
|
|
ck = qk_norm(ck, blk + ".cross_attn.k_rms_norm.gamma");
|
|
ggml_tensor * ca = lin(sdpa(cq, ck, cv), blk + ".cross_attn.to_out");
|
|
h = ggml_add(ctx, h, ca);
|
|
|
|
// feed-forward (norm3 affine-free, modulated; GELU-tanh)
|
|
ggml_tensor * hm = modulate(ggml_norm(ctx, h, 1e-6f), scale_mlp, shift_mlp);
|
|
hm = lin(hm, blk + ".mlp.mlp.0");
|
|
hm = ggml_gelu(ctx, hm);
|
|
hm = lin(hm, blk + ".mlp.mlp.2");
|
|
h = ggml_add(ctx, h, ggml_mul(ctx, hm, gate_mlp));
|
|
}
|
|
|
|
// ── head: affine-free LayerNorm (eps 1e-5) + output projection ────────────
|
|
h = ggml_norm(ctx, h, 1e-5f);
|
|
h = lin(h, "out_layer"); // [out_channels, N]
|
|
ggml_tensor * y = ggml_cont(ctx, ggml_transpose(ctx, h)); // [N, out_channels], channel-major
|
|
ggml_set_output(y);
|
|
|
|
if (!missing.empty()) {
|
|
set_error(error, "missing tensor: " + missing);
|
|
ggml_free(ctx);
|
|
return false;
|
|
}
|
|
|
|
ggml_build_forward_expand(gf, y);
|
|
auto t_build = t_now();
|
|
|
|
// ── allocate + run on the model's backend (GPU if available, else CPU) ────
|
|
ggml_backend_t backend = m->backend;
|
|
ggml_gallocr_t alloc = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend));
|
|
if (!ggml_gallocr_alloc_graph(alloc, gf)) {
|
|
set_error(error, "ggml_gallocr_alloc_graph failed");
|
|
ggml_gallocr_free(alloc); ggml_free(ctx);
|
|
return false;
|
|
}
|
|
auto t_alloc = t_now();
|
|
|
|
std::vector<float> emb = timestep_embedding(t, 256);
|
|
std::vector<float> cosv, sinv;
|
|
rope_tables(R, hd, hp.rope_freq_min, hp.rope_freq_base, cosv, sinv);
|
|
|
|
ggml_backend_tensor_set(x_t, x, 0, (size_t) hp.in_channels * N * es);
|
|
ggml_backend_tensor_set(temb, emb.data(), 0, emb.size() * es);
|
|
ggml_backend_tensor_set(cos_t, cosv.data(), 0, cosv.size() * es);
|
|
ggml_backend_tensor_set(sin_t, sinv.data(), 0, sinv.size() * es);
|
|
ggml_backend_tensor_set(cnd, cond, 0, (size_t) cond_channels * Lkv * es);
|
|
auto t_upload = t_now();
|
|
|
|
const ggml_status st = ggml_backend_graph_compute(backend, gf);
|
|
auto t_compute = t_now();
|
|
bool ok = (st == GGML_STATUS_SUCCESS);
|
|
if (ok) {
|
|
ggml_backend_tensor_get(y, out, 0, (size_t) hp.out_channels * N * es);
|
|
if (t2_timing) {
|
|
std::fprintf(stderr, "[ss_flow] build=%.1f alloc=%.1f upload=%.1f compute=%.1f read=%.1f total=%.1f ms\n",
|
|
t_ms(t_start, t_build), t_ms(t_build, t_alloc), t_ms(t_alloc, t_upload),
|
|
t_ms(t_upload, t_compute), t_ms(t_compute, t_now()), t_ms(t_start, t_now()));
|
|
}
|
|
} else {
|
|
set_error(error, "graph compute failed");
|
|
}
|
|
|
|
ggml_gallocr_free(alloc);
|
|
ggml_free(ctx);
|
|
return ok;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** Flow-Euler sampler (classifier-free guidance + interval + rescale)
|
|
**
|
|
** Mirrors FlowEulerGuidanceIntervalSampler.sample. All the per-step flow
|
|
** arithmetic is elementwise on the latent and runs on the host; only the
|
|
** velocity prediction (1 or 2 forwards per step) uses the GPU graph above.
|
|
*****************************************************************************/
|
|
|
|
namespace {
|
|
|
|
// x_0 estimate from a velocity prediction: (1-s)x_t - (s + (1-s)t) pred.
|
|
inline void pred_to_xstart(const std::vector<float> & x_t, double t, double sm,
|
|
const std::vector<float> & pred, std::vector<float> & x0) {
|
|
const double a = 1.0 - sm;
|
|
const double b = sm + a * t;
|
|
for (size_t i = 0; i < x_t.size(); ++i) x0[i] = (float) (a * x_t[i] - b * pred[i]);
|
|
}
|
|
|
|
// inverse of pred_to_xstart.
|
|
inline void xstart_to_pred(const std::vector<float> & x_t, double t, double sm,
|
|
const std::vector<float> & x0, std::vector<float> & pred) {
|
|
const double a = 1.0 - sm;
|
|
const double b = sm + a * t;
|
|
for (size_t i = 0; i < x_t.size(); ++i) pred[i] = (float) ((a * x_t[i] - x0[i]) / b);
|
|
}
|
|
|
|
// unbiased std over a whole buffer (matches torch .std(), correction=1).
|
|
double unbiased_std(const std::vector<float> & v) {
|
|
const size_t n = v.size();
|
|
if (n < 2) return 0.0;
|
|
double sum = 0.0;
|
|
for (float x : v) sum += x;
|
|
const double mean = sum / (double) n;
|
|
double ss = 0.0;
|
|
for (float x : v) { const double d = (double) x - mean; ss += d * d; }
|
|
return std::sqrt(ss / (double) (n - 1));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool trellis2_ss_flow_sample(trellis2_ss_flow_model * m,
|
|
const float * cond, int cond_tokens, int cond_channels,
|
|
const trellis2_ss_sampler_params * params_in,
|
|
const float * noise,
|
|
float * out_latent, std::string * error) {
|
|
if (!m) { set_error(error, "null model"); return false; }
|
|
if (!m->has_data) { set_error(error, "model loaded metadata-only; reload with load_tensors=true"); return false; }
|
|
|
|
trellis2_ss_sampler_params P;
|
|
if (params_in) P = *params_in;
|
|
|
|
const trellis2_ss_flow_hparams & hp = m->hp;
|
|
const int R = hp.resolution;
|
|
const size_t N = (size_t) R * R * R;
|
|
const size_t n = (size_t) hp.in_channels * N;
|
|
const double sm = P.sigma_min;
|
|
|
|
// ── initial noise ─────────────────────────────────────────────────────────
|
|
std::vector<float> x_t(n);
|
|
if (noise) {
|
|
std::memcpy(x_t.data(), noise, n * sizeof(float));
|
|
} else {
|
|
std::mt19937_64 rng(P.seed);
|
|
std::normal_distribution<float> nd(0.0f, 1.0f);
|
|
for (size_t i = 0; i < n; ++i) x_t[i] = nd(rng);
|
|
}
|
|
|
|
// ── timestep schedule: linspace(1,0,steps+1) warped by rescale_t ─────────
|
|
std::vector<double> ts((size_t) P.steps + 1);
|
|
for (int i = 0; i <= P.steps; ++i) {
|
|
const double lin = 1.0 - (double) i / (double) P.steps; // 1 -> 0
|
|
ts[i] = P.rescale_t * lin / (1.0 + (P.rescale_t - 1.0) * lin);
|
|
}
|
|
|
|
const std::vector<float> zero_cond((size_t) cond_tokens * cond_channels, 0.0f);
|
|
std::vector<float> pred_pos(n), pred_neg(n), pred_v(n), x0_pos(n), x0_cfg(n);
|
|
std::vector<float> x0_view; // scratch for the live-preview x_0 estimate
|
|
if (P.preview) x0_view.resize(n);
|
|
|
|
auto fwd = [&](double t, const float * c, std::vector<float> & dst) -> bool {
|
|
return trellis2_ss_flow_forward(m, x_t.data(), (float) (1000.0 * t),
|
|
c, cond_tokens, cond_channels, dst.data(), error);
|
|
};
|
|
|
|
for (int i = 0; i < P.steps; ++i) {
|
|
const double t = ts[i], t_prev = ts[i + 1];
|
|
const bool in_interval = (t >= P.guidance_interval_min && t <= P.guidance_interval_max);
|
|
const float gs = in_interval ? P.guidance_strength : 1.0f;
|
|
|
|
if (gs == 1.0f) {
|
|
if (!fwd(t, cond, pred_v)) return false;
|
|
} else if (gs == 0.0f) {
|
|
if (!fwd(t, zero_cond.data(), pred_v)) return false;
|
|
} else {
|
|
if (!fwd(t, cond, pred_pos)) return false;
|
|
if (!fwd(t, zero_cond.data(), pred_neg)) return false;
|
|
for (size_t k = 0; k < n; ++k) pred_v[k] = gs * pred_pos[k] + (1.0f - gs) * pred_neg[k];
|
|
|
|
if (P.guidance_rescale > 0.0f) {
|
|
pred_to_xstart(x_t, t, sm, pred_pos, x0_pos);
|
|
pred_to_xstart(x_t, t, sm, pred_v, x0_cfg);
|
|
const double std_pos = unbiased_std(x0_pos);
|
|
const double std_cfg = unbiased_std(x0_cfg);
|
|
const double ratio = (std_cfg != 0.0) ? std_pos / std_cfg : 1.0;
|
|
const float gr = P.guidance_rescale;
|
|
for (size_t k = 0; k < n; ++k) {
|
|
const double rescaled = x0_cfg[k] * ratio;
|
|
x0_cfg[k] = (float) (gr * rescaled + (1.0 - gr) * x0_cfg[k]);
|
|
}
|
|
xstart_to_pred(x_t, t, sm, x0_cfg, pred_v);
|
|
}
|
|
}
|
|
|
|
// Live preview: the denoised x_0 estimate at this step (best guess of the
|
|
// clean latent), computed from x_t@t before the Euler step overwrites it.
|
|
if (P.preview) {
|
|
pred_to_xstart(x_t, t, sm, pred_v, x0_view);
|
|
P.preview(P.preview_user, i + 1, P.steps, x0_view.data(), (int) n);
|
|
}
|
|
|
|
// Euler step: x_{t-1} = x_t - (t - t_prev) * v
|
|
const double dt = t - t_prev;
|
|
for (size_t k = 0; k < n; ++k) x_t[k] = (float) (x_t[k] - dt * pred_v[k]);
|
|
|
|
if (P.verbose) {
|
|
std::fprintf(stderr, "\r[ss sample] step %2d/%d t=%.4f->%.4f %s ",
|
|
i + 1, P.steps, t, t_prev, in_interval ? "cfg" : "uncond");
|
|
std::fflush(stderr);
|
|
}
|
|
if (P.progress) P.progress(P.progress_user, i + 1, P.steps);
|
|
}
|
|
if (P.verbose) std::fprintf(stderr, "\n");
|
|
|
|
std::memcpy(out_latent, x_t.data(), n * sizeof(float));
|
|
return true;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** Sparse-structure decoder (stage 1): SparseStructureDecoder
|
|
**
|
|
** h = input_layer(z_s) # Conv3d latent->channels[0]
|
|
** h = middle_block(h) # num_res_blocks_middle ResBlocks
|
|
** for level i in 0..n_levels-1:
|
|
** h = ResBlock x num_res_blocks # at channels[i]
|
|
** if i < n_levels-1: h = Upsample(h) # Conv3d (C->C'*8) + pixel_shuffle_3d
|
|
** logits = out_layer(h) # ChannelLayerNorm + SiLU + Conv3d->out
|
|
**
|
|
** ResBlock3d: x + conv2(silu(norm2(conv1(silu(norm1(x)))))), all skips Identity
|
|
** here (in==out at every block). norm is a per-voxel LayerNorm over channels.
|
|
** Two pixel-shuffle upsamples take 16^3 -> 32^3 -> 64^3.
|
|
*****************************************************************************/
|
|
|
|
struct trellis2_ss_dec_model {
|
|
gguf_context * gguf = nullptr;
|
|
ggml_context * ctx = nullptr;
|
|
trellis2_ss_dec_hparams hp;
|
|
bool has_data = false;
|
|
|
|
ggml_backend_t backend = nullptr;
|
|
ggml_backend_buffer_t weights_buf = nullptr;
|
|
std::string backend_name;
|
|
|
|
std::unordered_map<std::string, ggml_tensor *> tensors;
|
|
};
|
|
|
|
trellis2_ss_dec_model *
|
|
trellis2_ss_dec_load(const std::string & path, bool load_tensors, std::string * error,
|
|
const char * device) {
|
|
auto * m = new trellis2_ss_dec_model();
|
|
|
|
gguf_init_params params;
|
|
params.no_alloc = true;
|
|
params.ctx = &m->ctx;
|
|
|
|
m->gguf = gguf_init_from_file(path.c_str(), params);
|
|
if (!m->gguf) {
|
|
set_error(error, "gguf_init_from_file failed (not a GGUF file?): " + path);
|
|
delete m;
|
|
return nullptr;
|
|
}
|
|
|
|
const char * arch = kv_str(m->gguf, "general.architecture", "");
|
|
if (std::strcmp(arch, "trellis2-ss-dec") != 0) {
|
|
set_error(error, std::string("unexpected architecture '") + arch +
|
|
"' (expected 'trellis2-ss-dec')");
|
|
trellis2_ss_dec_free(m);
|
|
return nullptr;
|
|
}
|
|
|
|
trellis2_ss_dec_hparams & hp = m->hp;
|
|
const char * P = "trellis2.ss_dec.";
|
|
auto K = [&](const char * suffix) { return std::string(P) + suffix; };
|
|
|
|
hp.out_channels = (int32_t) kv_u32(m->gguf, K("out_channels").c_str(), 1);
|
|
hp.latent_channels = (int32_t) kv_u32(m->gguf, K("latent_channels").c_str(), 8);
|
|
hp.num_res_blocks = (int32_t) kv_u32(m->gguf, K("num_res_blocks").c_str(), 2);
|
|
hp.num_res_blocks_middle = (int32_t) kv_u32(m->gguf, K("num_res_blocks_middle").c_str(), 2);
|
|
hp.n_levels = (int32_t) kv_u32(m->gguf, K("n_levels").c_str(), 3);
|
|
hp.norm_eps = kv_f32(m->gguf, K("norm_eps").c_str(), 1e-5f);
|
|
hp.file_type = (int32_t) kv_u32(m->gguf, "general.file_type", 0);
|
|
std::snprintf(hp.norm_type, sizeof(hp.norm_type), "%s",
|
|
kv_str(m->gguf, K("norm_type").c_str(), "layer"));
|
|
if (hp.n_levels > 8) hp.n_levels = 8;
|
|
for (int i = 0; i < hp.n_levels; ++i) {
|
|
hp.channels[i] = (int32_t) kv_u32(m->gguf, K(("channels." + std::to_string(i)).c_str()).c_str(), 0);
|
|
}
|
|
|
|
for (ggml_tensor * t = ggml_get_first_tensor(m->ctx); t != nullptr;
|
|
t = ggml_get_next_tensor(m->ctx, t)) {
|
|
m->tensors[t->name] = t;
|
|
}
|
|
|
|
if (load_tensors) {
|
|
m->backend = init_best_backend(m->backend_name, device);
|
|
m->weights_buf = ggml_backend_alloc_ctx_tensors(m->ctx, m->backend);
|
|
if (!m->weights_buf) {
|
|
set_error(error, "failed to allocate weights on backend " + m->backend_name);
|
|
trellis2_ss_dec_free(m);
|
|
return nullptr;
|
|
}
|
|
std::ifstream fin(path, std::ios::binary);
|
|
if (!fin) {
|
|
set_error(error, "cannot reopen file for weight data: " + path);
|
|
trellis2_ss_dec_free(m);
|
|
return nullptr;
|
|
}
|
|
const size_t data_off = gguf_get_data_offset(m->gguf);
|
|
const int64_t nt = gguf_get_n_tensors(m->gguf);
|
|
std::vector<uint8_t> buf;
|
|
for (int64_t i = 0; i < nt; ++i) {
|
|
const char * name = gguf_get_tensor_name(m->gguf, i);
|
|
ggml_tensor * t = m->tensors[name];
|
|
const size_t nb = ggml_nbytes(t);
|
|
const size_t off = data_off + gguf_get_tensor_offset(m->gguf, i);
|
|
buf.resize(nb);
|
|
fin.seekg((std::streamoff) off, std::ios::beg);
|
|
if (!fin.read(reinterpret_cast<char *>(buf.data()), (std::streamsize) nb)) {
|
|
set_error(error, std::string("failed reading weight '") + name + "' from file");
|
|
trellis2_ss_dec_free(m);
|
|
return nullptr;
|
|
}
|
|
ggml_backend_tensor_set(t, buf.data(), 0, nb);
|
|
}
|
|
m->has_data = true;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
void trellis2_ss_dec_free(trellis2_ss_dec_model * m) {
|
|
if (!m) return;
|
|
if (m->weights_buf) ggml_backend_buffer_free(m->weights_buf);
|
|
if (m->backend) ggml_backend_free(m->backend);
|
|
if (m->gguf) gguf_free(m->gguf);
|
|
if (m->ctx) ggml_free(m->ctx);
|
|
delete m;
|
|
}
|
|
|
|
const char * trellis2_ss_dec_backend_name(const trellis2_ss_dec_model * m) {
|
|
return (m && !m->backend_name.empty()) ? m->backend_name.c_str() : "none";
|
|
}
|
|
|
|
const trellis2_ss_dec_hparams &
|
|
trellis2_ss_dec_hparams_of(const trellis2_ss_dec_model * m) { return m->hp; }
|
|
|
|
int trellis2_ss_dec_n_tensors(const trellis2_ss_dec_model * m) {
|
|
return m ? (int) gguf_get_n_tensors(m->gguf) : 0;
|
|
}
|
|
|
|
bool trellis2_ss_dec_get_tensor_info(const trellis2_ss_dec_model * m,
|
|
int i, trellis2_tensor_info & out) {
|
|
if (!m || i < 0 || i >= (int) gguf_get_n_tensors(m->gguf)) return false;
|
|
const char * name = gguf_get_tensor_name(m->gguf, i);
|
|
out.name = name;
|
|
ggml_tensor * t = ggml_get_tensor(m->ctx, name);
|
|
if (!t) return false;
|
|
out.n_dims = ggml_n_dims(t);
|
|
for (int d = 0; d < 4; ++d) out.ne[d] = t->ne[d];
|
|
out.ggml_type = (int) t->type;
|
|
out.type_name = ggml_type_name(t->type);
|
|
out.n_bytes = ggml_nbytes(t);
|
|
return true;
|
|
}
|
|
|
|
bool trellis2_ss_dec_decode(trellis2_ss_dec_model * m,
|
|
const float * latent, float * out, std::string * error) {
|
|
if (!m) { set_error(error, "null model"); return false; }
|
|
if (!m->has_data) { set_error(error, "model loaded metadata-only; reload with load_tensors=true"); return false; }
|
|
|
|
const trellis2_ss_dec_hparams & hp = m->hp;
|
|
const int R = hp.res_in(); // 16
|
|
const int Cin = hp.latent_channels; // 8
|
|
const float eps = hp.norm_eps; // 1e-5
|
|
const size_t es = sizeof(float);
|
|
|
|
std::string missing;
|
|
auto W = [&](const std::string & n) -> ggml_tensor * {
|
|
auto it = m->tensors.find(n);
|
|
if (it == m->tensors.end()) { if (missing.empty()) missing = n; return nullptr; }
|
|
return it->second;
|
|
};
|
|
|
|
const size_t mem = ggml_tensor_overhead() * 8192 + ggml_graph_overhead_custom(8192, false);
|
|
ggml_init_params ip{ mem, nullptr, /*no_alloc*/ true };
|
|
ggml_context * ctx = ggml_init(ip);
|
|
ggml_cgraph * gf = ggml_new_graph_custom(ctx, 8192, false);
|
|
|
|
// input leaf: z_s as ggml [R,R,R,Cin] (ne0=k, ne1=j, ne2=i, ne3=channel) —
|
|
// identical layout to channel-major latent[c*R^3 + i*R^2 + j*R + k].
|
|
ggml_tensor * x = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, R, R, R, Cin);
|
|
ggml_set_input(x);
|
|
ggml_set_name(x, "z_s");
|
|
|
|
// Conv3d (stride 1, pad 1) + per-output-channel bias. ic/oc passed explicitly.
|
|
auto conv = [&](ggml_tensor * in, const std::string & pfx, int ic, int oc) -> ggml_tensor * {
|
|
ggml_tensor * w = W(pfx + ".weight");
|
|
ggml_tensor * b = W(pfx + ".bias");
|
|
if (!w) return in;
|
|
ggml_tensor * y = ggml_conv_3d_direct(ctx, w, in, 1,1,1, 1,1,1, 1,1,1, ic, 1, oc);
|
|
if (b) y = ggml_add(ctx, y, ggml_reshape_4d(ctx, b, 1, 1, 1, oc));
|
|
return y;
|
|
};
|
|
// ChannelLayerNorm32: per-voxel LayerNorm over the channel axis (with affine).
|
|
auto clnorm = [&](ggml_tensor * in, const std::string & pfx) -> ggml_tensor * {
|
|
ggml_tensor * p = ggml_cont(ctx, ggml_permute(ctx, in, 1, 2, 3, 0)); // [C,W,H,D]
|
|
p = ggml_norm(ctx, p, eps);
|
|
p = ggml_mul(ctx, p, W(pfx + ".weight"));
|
|
p = ggml_add(ctx, p, W(pfx + ".bias"));
|
|
return ggml_cont(ctx, ggml_permute(ctx, p, 3, 0, 1, 2)); // [W,H,D,C]
|
|
};
|
|
auto resblock = [&](ggml_tensor * in, const std::string & pfx, int C) -> ggml_tensor * {
|
|
ggml_tensor * h = clnorm(in, pfx + ".norm1");
|
|
h = ggml_silu(ctx, h);
|
|
h = conv(h, pfx + ".conv1", C, C);
|
|
h = clnorm(h, pfx + ".norm2");
|
|
h = ggml_silu(ctx, h);
|
|
h = conv(h, pfx + ".conv2", C, C);
|
|
return ggml_add(ctx, h, in); // skip = Identity
|
|
};
|
|
// pixel_shuffle_3d(scale 2): [A,A,A, Cout*8] -> [2A,2A,2A, Cout]. Each scale
|
|
// bit (LSB->axis0, mid->axis1, MSB->axis2, matching torch's H/W/D pairing)
|
|
// is peeled out of the channel and interleaved into its spatial axis.
|
|
auto pshuf = [&](ggml_tensor * t, int A0, int A1, int A2, int Co) -> ggml_tensor * {
|
|
// peel s2 (channel LSB) into axis0
|
|
t = ggml_reshape_4d(ctx, t, A0, A1 * A2, 2, Co * 4);
|
|
t = ggml_cont(ctx, ggml_permute(ctx, t, 1, 2, 0, 3)); // [2, A0, A1*A2, Co*4]
|
|
t = ggml_reshape_4d(ctx, t, 2 * A0, A1, A2, Co * 4);
|
|
// peel s1 into axis1
|
|
t = ggml_cont(ctx, ggml_permute(ctx, t, 1, 0, 2, 3)); // [A1, 2A0, A2, Co*4]
|
|
t = ggml_reshape_4d(ctx, t, A1, 2 * A0 * A2, 2, Co * 2);
|
|
t = ggml_cont(ctx, ggml_permute(ctx, t, 1, 2, 0, 3)); // [2, A1, 2A0*A2, Co*2]
|
|
t = ggml_reshape_4d(ctx, t, 2 * A1, 2 * A0, A2, Co * 2);
|
|
t = ggml_cont(ctx, ggml_permute(ctx, t, 1, 0, 2, 3)); // [2A0, 2A1, A2, Co*2]
|
|
// peel s0 (channel MSB) into axis2
|
|
t = ggml_cont(ctx, ggml_permute(ctx, t, 1, 2, 0, 3)); // [A2, 2A0, 2A1, Co*2]
|
|
t = ggml_reshape_4d(ctx, t, A2, 2 * A0 * 2 * A1, 2, Co);
|
|
t = ggml_cont(ctx, ggml_permute(ctx, t, 1, 2, 0, 3)); // [2, A2, 2A0*2A1, Co]
|
|
t = ggml_reshape_4d(ctx, t, 2 * A2, 2 * A0, 2 * A1, Co);
|
|
t = ggml_cont(ctx, ggml_permute(ctx, t, 2, 0, 1, 3)); // [2A0, 2A1, 2A2, Co]
|
|
return t;
|
|
};
|
|
|
|
// ── forward ───────────────────────────────────────────────────────────────
|
|
ggml_tensor * h = conv(x, "input_layer", Cin, hp.channels[0]);
|
|
|
|
for (int i = 0; i < hp.num_res_blocks_middle; ++i) {
|
|
h = resblock(h, "middle_block." + std::to_string(i), hp.channels[0]);
|
|
}
|
|
|
|
int blk = 0;
|
|
int cur_res = R;
|
|
for (int lvl = 0; lvl < hp.n_levels; ++lvl) {
|
|
const int C = hp.channels[lvl];
|
|
for (int r = 0; r < hp.num_res_blocks; ++r) {
|
|
h = resblock(h, "blocks." + std::to_string(blk++), C);
|
|
}
|
|
if (lvl < hp.n_levels - 1) {
|
|
const int Co = hp.channels[lvl + 1];
|
|
h = conv(h, "blocks." + std::to_string(blk++) + ".conv", C, Co * 8);
|
|
h = pshuf(h, cur_res, cur_res, cur_res, Co);
|
|
cur_res *= 2;
|
|
}
|
|
}
|
|
|
|
h = clnorm(h, "out_layer.0");
|
|
h = ggml_silu(ctx, h);
|
|
h = conv(h, "out_layer.2", hp.channels[hp.n_levels - 1], hp.out_channels); // [Rout,Rout,Rout,Oc]
|
|
ggml_set_output(h);
|
|
|
|
if (!missing.empty()) {
|
|
set_error(error, "missing tensor: " + missing);
|
|
ggml_free(ctx);
|
|
return false;
|
|
}
|
|
|
|
ggml_build_forward_expand(gf, h);
|
|
|
|
ggml_backend_t backend = m->backend;
|
|
ggml_gallocr_t alloc = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend));
|
|
if (!ggml_gallocr_alloc_graph(alloc, gf)) {
|
|
set_error(error, "ggml_gallocr_alloc_graph failed");
|
|
ggml_gallocr_free(alloc); ggml_free(ctx);
|
|
return false;
|
|
}
|
|
|
|
ggml_backend_tensor_set(x, latent, 0, (size_t) Cin * R * R * R * es);
|
|
|
|
const ggml_status st = ggml_backend_graph_compute(backend, gf);
|
|
bool ok = (st == GGML_STATUS_SUCCESS);
|
|
if (ok) {
|
|
const size_t Rout = (size_t) hp.res_out();
|
|
ggml_backend_tensor_get(h, out, 0, (size_t) hp.out_channels * Rout * Rout * Rout * es);
|
|
} else {
|
|
set_error(error, "graph compute failed");
|
|
}
|
|
|
|
ggml_gallocr_free(alloc);
|
|
ggml_free(ctx);
|
|
return ok;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** DINOv3 ViT-L/16 image-conditioning encoder — GGUF loader
|
|
*****************************************************************************/
|
|
|
|
struct trellis2_dino_model {
|
|
gguf_context * gguf = nullptr;
|
|
ggml_context * ctx = nullptr;
|
|
trellis2_dino_hparams hp;
|
|
bool has_data = false;
|
|
|
|
ggml_backend_t backend = nullptr;
|
|
ggml_backend_buffer_t weights_buf = nullptr;
|
|
std::string backend_name;
|
|
|
|
std::unordered_map<std::string, ggml_tensor *> tensors;
|
|
};
|
|
|
|
trellis2_dino_model *
|
|
trellis2_dino_load(const std::string & path, bool load_tensors, std::string * error,
|
|
const char * device) {
|
|
auto * m = new trellis2_dino_model();
|
|
|
|
gguf_init_params params;
|
|
params.no_alloc = true;
|
|
params.ctx = &m->ctx;
|
|
|
|
m->gguf = gguf_init_from_file(path.c_str(), params);
|
|
if (!m->gguf) {
|
|
set_error(error, "gguf_init_from_file failed (not a GGUF file?): " + path);
|
|
delete m;
|
|
return nullptr;
|
|
}
|
|
|
|
const char * arch = kv_str(m->gguf, "general.architecture", "");
|
|
if (std::strcmp(arch, "trellis2-dino") != 0) {
|
|
set_error(error, std::string("unexpected architecture '") + arch +
|
|
"' (expected 'trellis2-dino')");
|
|
trellis2_dino_free(m);
|
|
return nullptr;
|
|
}
|
|
|
|
trellis2_dino_hparams & hp = m->hp;
|
|
const char * P = "trellis2.dino.";
|
|
auto K = [&](const char * suffix) { return std::string(P) + suffix; };
|
|
|
|
hp.hidden_size = (int32_t) kv_u32(m->gguf, K("hidden_size").c_str(), 0);
|
|
hp.n_layers = (int32_t) kv_u32(m->gguf, K("n_layers").c_str(), 0);
|
|
hp.n_heads = (int32_t) kv_u32(m->gguf, K("n_heads").c_str(), 0);
|
|
hp.intermediate_size = (int32_t) kv_u32(m->gguf, K("intermediate_size").c_str(), 0);
|
|
hp.patch_size = (int32_t) kv_u32(m->gguf, K("patch_size").c_str(), 16);
|
|
hp.num_register_tokens = (int32_t) kv_u32(m->gguf, K("num_register_tokens").c_str(), 0);
|
|
hp.layer_norm_eps = kv_f32(m->gguf, K("layer_norm_eps").c_str(), 1e-5f);
|
|
hp.rope_theta = kv_f32(m->gguf, K("rope_theta").c_str(), 100.0f);
|
|
hp.file_type = (int32_t) kv_u32(m->gguf, "general.file_type", 0);
|
|
for (int c = 0; c < 3; ++c) {
|
|
hp.image_mean[c] = kv_f32(m->gguf, (K("image_mean.") + std::to_string(c)).c_str(), hp.image_mean[c]);
|
|
hp.image_std[c] = kv_f32(m->gguf, (K("image_std.") + std::to_string(c)).c_str(), hp.image_std[c]);
|
|
}
|
|
|
|
for (ggml_tensor * t = ggml_get_first_tensor(m->ctx); t != nullptr;
|
|
t = ggml_get_next_tensor(m->ctx, t)) {
|
|
m->tensors[t->name] = t;
|
|
}
|
|
|
|
if (load_tensors) {
|
|
m->backend = init_best_backend(m->backend_name, device);
|
|
m->weights_buf = ggml_backend_alloc_ctx_tensors(m->ctx, m->backend);
|
|
if (!m->weights_buf) {
|
|
set_error(error, "failed to allocate weights on backend " + m->backend_name);
|
|
trellis2_dino_free(m);
|
|
return nullptr;
|
|
}
|
|
|
|
std::ifstream fin(path, std::ios::binary);
|
|
if (!fin) {
|
|
set_error(error, "cannot reopen file for weight data: " + path);
|
|
trellis2_dino_free(m);
|
|
return nullptr;
|
|
}
|
|
const size_t data_off = gguf_get_data_offset(m->gguf);
|
|
const int64_t nt = gguf_get_n_tensors(m->gguf);
|
|
std::vector<uint8_t> buf;
|
|
for (int64_t i = 0; i < nt; ++i) {
|
|
const char * name = gguf_get_tensor_name(m->gguf, i);
|
|
ggml_tensor * t = m->tensors[name];
|
|
const size_t nb = ggml_nbytes(t);
|
|
const size_t off = data_off + gguf_get_tensor_offset(m->gguf, i);
|
|
buf.resize(nb);
|
|
fin.seekg((std::streamoff) off, std::ios::beg);
|
|
if (!fin.read(reinterpret_cast<char *>(buf.data()), (std::streamsize) nb)) {
|
|
set_error(error, std::string("failed reading weight '") + name + "' from file");
|
|
trellis2_dino_free(m);
|
|
return nullptr;
|
|
}
|
|
ggml_backend_tensor_set(t, buf.data(), 0, nb);
|
|
}
|
|
m->has_data = true;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
void trellis2_dino_free(trellis2_dino_model * m) {
|
|
if (!m) return;
|
|
if (m->weights_buf) ggml_backend_buffer_free(m->weights_buf);
|
|
if (m->backend) ggml_backend_free(m->backend);
|
|
if (m->gguf) gguf_free(m->gguf);
|
|
if (m->ctx) ggml_free(m->ctx);
|
|
delete m;
|
|
}
|
|
|
|
const char * trellis2_dino_backend_name(const trellis2_dino_model * m) {
|
|
return (m && !m->backend_name.empty()) ? m->backend_name.c_str() : "none";
|
|
}
|
|
|
|
const trellis2_dino_hparams &
|
|
trellis2_dino_hparams_of(const trellis2_dino_model * m) {
|
|
return m->hp;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** DINOv3 — forward pass
|
|
**
|
|
** Mirrors DinoV3FeatureExtractor.extract_features:
|
|
** h = embeddings(pixels) # patch conv + [CLS | 4 reg | patches]
|
|
** cos,sin = rope_embeddings(pixels) # axial 2D RoPE over patch centers
|
|
** for each of 24 layers: # pre-norm ViT block with LayerScale
|
|
** h += ls1 * attn(LN1(h)) # (RoPE on patch tokens only)
|
|
** h += ls2 * mlp(LN2(h)) # (exact-GELU MLP)
|
|
** cond = layer_norm(h) # affine-free; model.norm NOT applied
|
|
*****************************************************************************/
|
|
|
|
namespace {
|
|
|
|
// Axial 2D RoPE tables for a Wp x Hp patch grid, matching HF's
|
|
// DINOv3ViTRopePositionEmbedding in eval mode (no shift/jitter/rescale):
|
|
// inv_freq[j] = 1 / theta^(4j/hd), j < hd/4
|
|
// angles(p) = 2*pi * [cy, cx] (x) inv_freq -> [hd/2], tiled to [hd]
|
|
// with (cy, cx) the patch-center coords normalized to [-1, 1].
|
|
// Output buffers are [P][hd] row-major (== ggml ne [hd, 1, P]).
|
|
void dino_rope_tables(int hp_grid, int wp_grid, int head_dim, float theta,
|
|
std::vector<float> & cos_t, std::vector<float> & sin_t) {
|
|
const int quarter = head_dim / 4;
|
|
std::vector<double> inv_freq((size_t) quarter);
|
|
for (int j = 0; j < quarter; ++j) {
|
|
inv_freq[j] = 1.0 / std::pow((double) theta, (double) j * 4.0 / (double) head_dim);
|
|
}
|
|
|
|
const int P = hp_grid * wp_grid;
|
|
cos_t.resize((size_t) P * head_dim);
|
|
sin_t.resize((size_t) P * head_dim);
|
|
for (int py = 0; py < hp_grid; ++py) {
|
|
const double cy = 2.0 * (((double) py + 0.5) / (double) hp_grid) - 1.0;
|
|
for (int px = 0; px < wp_grid; ++px) {
|
|
const double cx = 2.0 * (((double) px + 0.5) / (double) wp_grid) - 1.0;
|
|
const size_t base = (size_t) (py * wp_grid + px) * head_dim;
|
|
for (int j = 0; j < quarter; ++j) {
|
|
const double ay = 2.0 * M_PI * cy * inv_freq[j];
|
|
const double ax = 2.0 * M_PI * cx * inv_freq[j];
|
|
// angles layout: [cy*f..., cx*f...] then tiled x2
|
|
const float cy_c = (float) std::cos(ay), cy_s = (float) std::sin(ay);
|
|
const float cx_c = (float) std::cos(ax), cx_s = (float) std::sin(ax);
|
|
cos_t[base + j] = cy_c;
|
|
cos_t[base + quarter + j] = cx_c;
|
|
cos_t[base + 2 * quarter + j] = cy_c;
|
|
cos_t[base + 3 * quarter + j] = cx_c;
|
|
sin_t[base + j] = cy_s;
|
|
sin_t[base + quarter + j] = cx_s;
|
|
sin_t[base + 2 * quarter + j] = cy_s;
|
|
sin_t[base + 3 * quarter + j] = cx_s;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool trellis2_dino_encode(trellis2_dino_model * m,
|
|
const float * pixel_values, int image_size,
|
|
trellis2_dino_cond & out,
|
|
trellis2_dino_taps * taps,
|
|
std::string * error) {
|
|
if (!m) { set_error(error, "null model"); return false; }
|
|
if (!m->has_data) { set_error(error, "model loaded metadata-only; reload with load_tensors=true"); return false; }
|
|
|
|
const trellis2_dino_hparams & hp = m->hp;
|
|
const int S = image_size;
|
|
if (S <= 0 || S % hp.patch_size != 0) {
|
|
set_error(error, "image_size must be a positive multiple of patch_size");
|
|
return false;
|
|
}
|
|
const int Wp = S / hp.patch_size; // 32 @ 512
|
|
const int P = Wp * Wp; // 1024 patch tokens
|
|
const int Npre= 1 + hp.num_register_tokens; // CLS + registers
|
|
const int N = Npre + P; // 1029 tokens
|
|
const int C = hp.hidden_size; // 1024
|
|
const int H = hp.n_heads; // 16
|
|
const int hd = hp.head_dim(); // 64
|
|
const float attn_scale = 1.0f / std::sqrt((float) hd);
|
|
const float eps = hp.layer_norm_eps;
|
|
|
|
std::string missing;
|
|
auto W = [&](const std::string & n) -> ggml_tensor * {
|
|
auto it = m->tensors.find(n);
|
|
if (it == m->tensors.end()) { if (missing.empty()) missing = n; return nullptr; }
|
|
return it->second;
|
|
};
|
|
auto Wopt = [&](const std::string & n) -> ggml_tensor * {
|
|
auto it = m->tensors.find(n);
|
|
return it == m->tensors.end() ? nullptr : it->second;
|
|
};
|
|
|
|
const size_t mem = ggml_tensor_overhead() * 8192 + ggml_graph_overhead_custom(8192, false);
|
|
ggml_init_params ip{ mem, nullptr, /*no_alloc*/ true };
|
|
ggml_context * ctx = ggml_init(ip);
|
|
ggml_cgraph * gf = ggml_new_graph_custom(ctx, 8192, false);
|
|
|
|
// ── input leaves ─────────────────────────────────────────────────────────
|
|
ggml_tensor * pix = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, S, S, 3, 1); // CHW flat
|
|
ggml_tensor * cos_t = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, hd, 1, P);
|
|
ggml_tensor * sin_t = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, hd, 1, P);
|
|
ggml_set_input(pix);
|
|
ggml_set_input(cos_t);
|
|
ggml_set_input(sin_t);
|
|
|
|
// Tap bookkeeping: tensors registered here are marked as graph outputs and
|
|
// copied to the host after compute. All taps are [C, N]-contiguous, whose
|
|
// memory order equals the reference's row-major [N, C].
|
|
std::vector<std::pair<std::string, ggml_tensor *>> tap_list;
|
|
auto tap = [&](const std::string & name, ggml_tensor * t) {
|
|
if (!taps) return;
|
|
ggml_tensor * c = ggml_cont(ctx, t);
|
|
ggml_set_output(c);
|
|
ggml_build_forward_expand(gf, c);
|
|
tap_list.emplace_back(name, c);
|
|
};
|
|
tap("rope_0", cos_t); // [hd,1,P] contiguous == reference's [P, hd]
|
|
tap("rope_1", sin_t);
|
|
|
|
auto lin = [&](ggml_tensor * in, const std::string & pfx) -> ggml_tensor * {
|
|
ggml_tensor * y = ggml_mul_mat(ctx, W(pfx + ".weight"), in);
|
|
ggml_tensor * b = Wopt(pfx + ".bias");
|
|
if (b) y = ggml_add(ctx, y, b);
|
|
return y;
|
|
};
|
|
// affine LayerNorm over channels of a [C, N] tensor
|
|
auto lnorm = [&](ggml_tensor * h, const std::string & pfx) -> ggml_tensor * {
|
|
ggml_tensor * y = ggml_norm(ctx, h, eps);
|
|
y = ggml_mul(ctx, y, W(pfx + ".weight"));
|
|
y = ggml_add(ctx, y, W(pfx + ".bias"));
|
|
return y;
|
|
};
|
|
// half-split RoPE on a [hd, H, P] tensor: x*cos + rotate_half(x)*sin
|
|
auto rope = [&](ggml_tensor * q3) -> ggml_tensor * {
|
|
const size_t half_off = (size_t) (hd / 2) * sizeof(float);
|
|
ggml_tensor * x1 = ggml_cont(ctx, ggml_view_3d(ctx, q3, hd / 2, H, P,
|
|
q3->nb[1], q3->nb[2], 0));
|
|
ggml_tensor * x2 = ggml_cont(ctx, ggml_view_3d(ctx, q3, hd / 2, H, P,
|
|
q3->nb[1], q3->nb[2], half_off));
|
|
ggml_tensor * rh = ggml_concat(ctx, ggml_neg(ctx, x2), x1, 0); // [hd,H,P]
|
|
return ggml_add(ctx, ggml_mul(ctx, q3, cos_t), ggml_mul(ctx, rh, sin_t));
|
|
};
|
|
// RoPE on patch tokens only of a [hd, H, N] tensor (prefix passes through).
|
|
auto rope_patches = [&](ggml_tensor * q3) -> ggml_tensor * {
|
|
ggml_tensor * pre = ggml_cont(ctx, ggml_view_3d(ctx, q3, hd, H, Npre,
|
|
q3->nb[1], q3->nb[2], 0));
|
|
ggml_tensor * pat = ggml_cont(ctx, ggml_view_3d(ctx, q3, hd, H, P,
|
|
q3->nb[1], q3->nb[2], (size_t) Npre * q3->nb[2]));
|
|
return ggml_concat(ctx, pre, rope(pat), 2);
|
|
};
|
|
// scaled-dot-product attention; q3/k3/v3 are [hd, H, N].
|
|
auto sdpa = [&](ggml_tensor * q3, ggml_tensor * k3, ggml_tensor * v3) -> ggml_tensor * {
|
|
ggml_tensor * qp = ggml_cont(ctx, ggml_permute(ctx, q3, 0, 2, 1, 3)); // [hd, N, H]
|
|
ggml_tensor * kp = ggml_cont(ctx, ggml_permute(ctx, k3, 0, 2, 1, 3));
|
|
ggml_tensor * vp = ggml_cont(ctx, ggml_permute(ctx, v3, 0, 2, 1, 3));
|
|
ggml_tensor * sc = ggml_mul_mat(ctx, kp, qp); // [Nk, Nq, H]
|
|
sc = ggml_soft_max_ext(ctx, sc, nullptr, attn_scale, 0.0f);
|
|
ggml_tensor * vt = ggml_cont(ctx, ggml_permute(ctx, vp, 1, 0, 2, 3)); // [Nk, hd, H]
|
|
ggml_tensor * o = ggml_mul_mat(ctx, vt, sc); // [hd, Nq, H]
|
|
o = ggml_cont(ctx, ggml_permute(ctx, o, 0, 2, 1, 3)); // [hd, H, Nq]
|
|
return ggml_reshape_2d(ctx, o, C, o->ne[2]); // [C, Nq]
|
|
};
|
|
|
|
// ── embeddings: patch conv + CLS + register tokens ───────────────────────
|
|
ggml_tensor * pconv = ggml_conv_2d(ctx, W("embeddings.patch_embeddings.weight"), pix,
|
|
hp.patch_size, hp.patch_size, 0, 0, 1, 1); // [Wp, Wp, C, 1]
|
|
pconv = ggml_reshape_2d(ctx, pconv, P, C); // [P, C] (token-fastest)
|
|
pconv = ggml_cont(ctx, ggml_transpose(ctx, pconv)); // [C, P]
|
|
pconv = ggml_add(ctx, pconv, W("embeddings.patch_embeddings.bias"));
|
|
|
|
ggml_tensor * h = ggml_concat(ctx, W("embeddings.cls_token"),
|
|
ggml_concat(ctx, W("embeddings.register_tokens"), pconv, 1), 1); // [C, N]
|
|
h = ggml_cont(ctx, h);
|
|
tap("embd", h);
|
|
|
|
// ── transformer layers ───────────────────────────────────────────────────
|
|
const bool detail_first_last = true;
|
|
for (int i = 0; i < hp.n_layers; ++i) {
|
|
const std::string blk = "layer." + std::to_string(i);
|
|
const bool detail = taps && detail_first_last && (i == 0 || i == hp.n_layers - 1);
|
|
auto tn = [&](const char * s) { return "l" + std::to_string(i) + "." + s; };
|
|
(void) tn;
|
|
|
|
ggml_tensor * hn = lnorm(h, blk + ".norm1");
|
|
if (detail) tap(tn("norm1"), hn);
|
|
|
|
ggml_tensor * q = ggml_reshape_3d(ctx, lin(hn, blk + ".attention.q_proj"), hd, H, N);
|
|
ggml_tensor * k = ggml_reshape_3d(ctx, lin(hn, blk + ".attention.k_proj"), hd, H, N);
|
|
ggml_tensor * v = ggml_reshape_3d(ctx, lin(hn, blk + ".attention.v_proj"), hd, H, N);
|
|
q = rope_patches(q);
|
|
k = rope_patches(k);
|
|
ggml_tensor * sa = lin(sdpa(q, k, v), blk + ".attention.o_proj"); // [C, N]
|
|
if (detail) tap(tn("attention"), sa);
|
|
|
|
ggml_tensor * ls1 = ggml_mul(ctx, sa, W(blk + ".layer_scale1.lambda1"));
|
|
if (detail) tap(tn("layer_scale1"), ls1);
|
|
h = ggml_add(ctx, h, ls1);
|
|
|
|
ggml_tensor * h2 = lnorm(h, blk + ".norm2");
|
|
if (detail) tap(tn("norm2"), h2);
|
|
ggml_tensor * mlp = lin(h2, blk + ".mlp.up_proj");
|
|
mlp = ggml_gelu_erf(ctx, mlp);
|
|
mlp = lin(mlp, blk + ".mlp.down_proj");
|
|
if (detail) tap(tn("mlp"), mlp);
|
|
ggml_tensor * ls2 = ggml_mul(ctx, mlp, W(blk + ".layer_scale2.lambda1"));
|
|
if (detail) tap(tn("layer_scale2"), ls2);
|
|
h = ggml_add(ctx, h, ls2);
|
|
|
|
tap("l" + std::to_string(i) + ".out", h);
|
|
}
|
|
|
|
// ── affine-free final LayerNorm (F.layer_norm, eps 1e-5) ─────────────────
|
|
ggml_tensor * cond = ggml_norm(ctx, h, 1e-5f);
|
|
cond = ggml_cont(ctx, cond); // [C, N] contiguous == row-major [N, C]
|
|
ggml_set_output(cond);
|
|
tap("cond", cond);
|
|
|
|
if (!missing.empty()) {
|
|
set_error(error, "missing tensor: " + missing);
|
|
ggml_free(ctx);
|
|
return false;
|
|
}
|
|
|
|
ggml_build_forward_expand(gf, cond);
|
|
|
|
ggml_backend_t backend = m->backend;
|
|
ggml_gallocr_t alloc = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend));
|
|
if (!ggml_gallocr_alloc_graph(alloc, gf)) {
|
|
set_error(error, "ggml_gallocr_alloc_graph failed");
|
|
ggml_gallocr_free(alloc); ggml_free(ctx);
|
|
return false;
|
|
}
|
|
|
|
std::vector<float> cosv, sinv;
|
|
dino_rope_tables(Wp, Wp, hd, hp.rope_theta, cosv, sinv);
|
|
|
|
const size_t es = sizeof(float);
|
|
ggml_backend_tensor_set(pix, pixel_values, 0, (size_t) 3 * S * S * es);
|
|
ggml_backend_tensor_set(cos_t, cosv.data(), 0, cosv.size() * es);
|
|
ggml_backend_tensor_set(sin_t, sinv.data(), 0, sinv.size() * es);
|
|
|
|
const ggml_status st = ggml_backend_graph_compute(backend, gf);
|
|
bool ok = (st == GGML_STATUS_SUCCESS);
|
|
if (ok) {
|
|
out.shape = {1, (int64_t) N, (int64_t) C};
|
|
out.data.resize((size_t) N * C);
|
|
out.format_version = 1;
|
|
ggml_backend_tensor_get(cond, out.data.data(), 0, out.data.size() * es);
|
|
|
|
if (taps) {
|
|
for (auto & nt : tap_list) {
|
|
taps->names.push_back(nt.first);
|
|
std::vector<float> buf(ggml_nelements(nt.second));
|
|
ggml_backend_tensor_get(nt.second, buf.data(), 0, buf.size() * es);
|
|
taps->data.push_back(std::move(buf));
|
|
}
|
|
}
|
|
} else {
|
|
set_error(error, "graph compute failed");
|
|
}
|
|
|
|
ggml_gallocr_free(alloc);
|
|
ggml_free(ctx);
|
|
return ok;
|
|
}
|
|
|
|
bool trellis2_dino_encode_rgb(trellis2_dino_model * m,
|
|
const uint8_t * rgb, int image_size,
|
|
trellis2_dino_cond & out,
|
|
std::string * error) {
|
|
if (!m) { set_error(error, "null model"); return false; }
|
|
const trellis2_dino_hparams & hp = m->hp;
|
|
const size_t S = (size_t) image_size;
|
|
std::vector<float> pix(3 * S * S);
|
|
for (int c = 0; c < 3; ++c) {
|
|
const float mean = hp.image_mean[c], sd = hp.image_std[c];
|
|
for (size_t i = 0; i < S * S; ++i) {
|
|
pix[(size_t) c * S * S + i] = ((float) rgb[i * 3 + c] / 255.0f - mean) / sd;
|
|
}
|
|
}
|
|
return trellis2_dino_encode(m, pix.data(), image_size, out, nullptr, error);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** Image preprocessing (pipeline.preprocess_image, has_alpha path)
|
|
**
|
|
** The resampler reproduces PIL's 8-bit fixed-point separable Lanczos-3
|
|
** (Pillow Resample.c): double-precision coefficient windows normalized per
|
|
** output pixel, quantized to integers at PRECISION_BITS, horizontal pass then
|
|
** vertical pass with uint8 rounding between passes. This makes the C++
|
|
** preprocessing byte-identical to the Python reference on the same input.
|
|
*****************************************************************************/
|
|
|
|
namespace {
|
|
|
|
constexpr int PIL_PRECISION_BITS = 32 - 8 - 2;
|
|
|
|
inline double pil_sinc(double x) {
|
|
if (x == 0.0) return 1.0;
|
|
const double px = M_PI * x;
|
|
return std::sin(px) / px;
|
|
}
|
|
inline double pil_lanczos3(double x) {
|
|
if (x >= -3.0 && x < 3.0) return pil_sinc(x) * pil_sinc(x / 3.0);
|
|
return 0.0;
|
|
}
|
|
|
|
inline uint8_t pil_clip8(int64_t in) {
|
|
if (in >= ((int64_t) 1 << PIL_PRECISION_BITS << 8)) return 255;
|
|
if (in <= 0) return 0;
|
|
return (uint8_t) (in >> PIL_PRECISION_BITS);
|
|
}
|
|
|
|
// Coefficient windows for one axis (PIL precompute_coeffs + normalize_8bpc).
|
|
void pil_coeffs(int in_size, int out_size,
|
|
std::vector<int> & bounds, std::vector<int32_t> & kk, int & ksize) {
|
|
const double support0 = 3.0; // Lanczos
|
|
const double scale = (double) in_size / (double) out_size;
|
|
const double filterscale = scale < 1.0 ? 1.0 : scale;
|
|
const double support = support0 * filterscale;
|
|
ksize = (int) std::ceil(support) * 2 + 1;
|
|
|
|
std::vector<double> k((size_t) ksize);
|
|
bounds.resize((size_t) out_size * 2);
|
|
kk.resize((size_t) out_size * ksize);
|
|
|
|
for (int xx = 0; xx < out_size; ++xx) {
|
|
const double center = ((double) xx + 0.5) * scale;
|
|
const double ss = 1.0 / filterscale;
|
|
int xmin = (int) (center - support + 0.5);
|
|
if (xmin < 0) xmin = 0;
|
|
int xmax = (int) (center + support + 0.5);
|
|
if (xmax > in_size) xmax = in_size;
|
|
xmax -= xmin;
|
|
|
|
double ww = 0.0;
|
|
for (int x = 0; x < xmax; ++x) {
|
|
const double w = pil_lanczos3(((double) (x + xmin) - center + 0.5) * ss);
|
|
k[(size_t) x] = w;
|
|
ww += w;
|
|
}
|
|
for (int x = 0; x < xmax; ++x) {
|
|
if (ww != 0.0) k[(size_t) x] /= ww;
|
|
}
|
|
for (int x = 0; x < xmax; ++x) {
|
|
const double w = k[(size_t) x] * (double) (1 << PIL_PRECISION_BITS);
|
|
kk[(size_t) xx * ksize + x] = (int32_t) (w < 0 ? w - 0.5 : w + 0.5);
|
|
}
|
|
for (int x = xmax; x < ksize; ++x) kk[(size_t) xx * ksize + x] = 0;
|
|
bounds[(size_t) xx * 2 + 0] = xmin;
|
|
bounds[(size_t) xx * 2 + 1] = xmax;
|
|
}
|
|
}
|
|
|
|
// Separable resample of an interleaved uint8 image (any channel count),
|
|
// horizontal pass then vertical pass, PIL-compatible.
|
|
void pil_resize(const uint8_t * in, int w, int h, int ch,
|
|
int out_w, int out_h, std::vector<uint8_t> & out) {
|
|
std::vector<int> bounds;
|
|
std::vector<int32_t> kk;
|
|
int ksize = 0;
|
|
|
|
// horizontal: [h, w] -> [h, out_w]
|
|
std::vector<uint8_t> tmp((size_t) h * out_w * ch);
|
|
pil_coeffs(w, out_w, bounds, kk, ksize);
|
|
for (int y = 0; y < h; ++y) {
|
|
const uint8_t * row = in + (size_t) y * w * ch;
|
|
uint8_t * orow = tmp.data() + (size_t) y * out_w * ch;
|
|
for (int xx = 0; xx < out_w; ++xx) {
|
|
const int xmin = bounds[(size_t) xx * 2 + 0];
|
|
const int xmax = bounds[(size_t) xx * 2 + 1];
|
|
const int32_t * k = kk.data() + (size_t) xx * ksize;
|
|
for (int c = 0; c < ch; ++c) {
|
|
int64_t ss = (int64_t) 1 << (PIL_PRECISION_BITS - 1);
|
|
for (int x = 0; x < xmax; ++x) {
|
|
ss += (int64_t) row[(size_t) (x + xmin) * ch + c] * k[x];
|
|
}
|
|
orow[(size_t) xx * ch + c] = pil_clip8(ss);
|
|
}
|
|
}
|
|
}
|
|
|
|
// vertical: [h, out_w] -> [out_h, out_w]
|
|
out.resize((size_t) out_h * out_w * ch);
|
|
pil_coeffs(h, out_h, bounds, kk, ksize);
|
|
for (int yy = 0; yy < out_h; ++yy) {
|
|
const int ymin = bounds[(size_t) yy * 2 + 0];
|
|
const int ymax = bounds[(size_t) yy * 2 + 1];
|
|
const int32_t * k = kk.data() + (size_t) yy * ksize;
|
|
uint8_t * orow = out.data() + (size_t) yy * out_w * ch;
|
|
for (int xx = 0; xx < out_w; ++xx) {
|
|
for (int c = 0; c < ch; ++c) {
|
|
int64_t ss = (int64_t) 1 << (PIL_PRECISION_BITS - 1);
|
|
for (int y = 0; y < ymax; ++y) {
|
|
ss += (int64_t) tmp[(size_t) (y + ymin) * out_w * ch + (size_t) xx * ch + c] * k[y];
|
|
}
|
|
orow[(size_t) xx * ch + c] = pil_clip8(ss);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Python round() (banker's rounding) for the .0/.5 values PIL's crop sees.
|
|
inline int py_round_half_even(double v) {
|
|
const double fl = std::floor(v);
|
|
const double frac = v - fl;
|
|
if (frac < 0.5) return (int) fl;
|
|
if (frac > 0.5) return (int) fl + 1;
|
|
const int lo = (int) fl;
|
|
return (lo % 2 == 0) ? lo : lo + 1;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int trellis2_remove_solid_background_rgba(uint8_t * rgba, int w, int h, int mode) {
|
|
if (!rgba || w <= 0 || h <= 0 ||
|
|
mode < TRELLIS2_BACKGROUND_AUTO || mode > TRELLIS2_BACKGROUND_WHITE) {
|
|
return -1;
|
|
}
|
|
if (mode == TRELLIS2_BACKGROUND_KEEP) return 0;
|
|
|
|
const size_t count = (size_t) w * h;
|
|
if (mode == TRELLIS2_BACKGROUND_AUTO) {
|
|
// An already-masked PNG should be trusted. Requiring more than both 1%
|
|
// and four pixels avoids treating a stray transparent metadata pixel as
|
|
// a meaningful subject mask.
|
|
size_t translucent = 0;
|
|
for (size_t i = 0; i < count; ++i) translucent += rgba[i * 4 + 3] < 250;
|
|
if (translucent > std::max<size_t>(4, count / 100)) return 0;
|
|
|
|
int border = 0, dark = 0, light = 0;
|
|
auto sample = [&](int x, int y) {
|
|
const uint8_t * p = rgba + ((size_t) y * w + x) * 4;
|
|
const int lo = std::min((int) p[0], std::min((int) p[1], (int) p[2]));
|
|
const int hi = std::max((int) p[0], std::max((int) p[1], (int) p[2]));
|
|
++border;
|
|
dark += hi <= 80;
|
|
light += lo >= 175;
|
|
};
|
|
for (int x = 0; x < w; ++x) {
|
|
sample(x, 0);
|
|
if (h > 1) sample(x, h - 1);
|
|
}
|
|
for (int y = 1; y + 1 < h; ++y) {
|
|
sample(0, y);
|
|
if (w > 1) sample(w - 1, y);
|
|
}
|
|
int dark_corners = 0, light_corners = 0;
|
|
const int corners[4][2] = {{0, 0}, {w - 1, 0}, {0, h - 1}, {w - 1, h - 1}};
|
|
for (const auto & c : corners) {
|
|
const uint8_t * p = rgba + ((size_t) c[1] * w + c[0]) * 4;
|
|
const int lo = std::min((int) p[0], std::min((int) p[1], (int) p[2]));
|
|
const int hi = std::max((int) p[0], std::max((int) p[1], (int) p[2]));
|
|
dark_corners += hi <= 80;
|
|
light_corners += lo >= 175;
|
|
}
|
|
const bool is_dark = dark * 100 >= border * 55 || dark_corners >= 3;
|
|
const bool is_light = light * 100 >= border * 55 || light_corners >= 3;
|
|
if (!is_dark && !is_light) return 0;
|
|
mode = is_dark && (!is_light || dark >= light)
|
|
? TRELLIS2_BACKGROUND_BLACK : TRELLIS2_BACKGROUND_WHITE;
|
|
}
|
|
|
|
auto distance = [&](size_t i) {
|
|
const uint8_t * p = rgba + i * 4;
|
|
if (mode == TRELLIS2_BACKGROUND_BLACK) {
|
|
return std::max((int) p[0], std::max((int) p[1], (int) p[2]));
|
|
}
|
|
const int lo = std::min((int) p[0], std::min((int) p[1], (int) p[2]));
|
|
return 255 - lo;
|
|
};
|
|
auto eligible = [&](size_t i) { return distance(i) <= 80; };
|
|
|
|
std::vector<uint8_t> seen(count, 0);
|
|
std::vector<size_t> queue;
|
|
queue.reserve(std::min<size_t>(count, (size_t) 1 << 20));
|
|
auto seed = [&](int x, int y) {
|
|
const size_t i = (size_t) y * w + x;
|
|
if (!seen[i] && eligible(i)) {
|
|
seen[i] = 1;
|
|
queue.push_back(i);
|
|
}
|
|
};
|
|
for (int x = 0; x < w; ++x) {
|
|
seed(x, 0);
|
|
if (h > 1) seed(x, h - 1);
|
|
}
|
|
for (int y = 1; y + 1 < h; ++y) {
|
|
seed(0, y);
|
|
if (w > 1) seed(w - 1, y);
|
|
}
|
|
|
|
int changed = 0;
|
|
for (size_t head = 0; head < queue.size(); ++head) {
|
|
const size_t i = queue[head];
|
|
const int d = distance(i);
|
|
float t = (float) (d - 12) / (72.0f - 12.0f);
|
|
t = std::max(0.0f, std::min(1.0f, t));
|
|
t = t * t * (3.0f - 2.0f * t); // smooth feather, background -> subject
|
|
uint8_t * p = rgba + i * 4;
|
|
const uint8_t a = (uint8_t) std::lround((float) p[3] * t);
|
|
if (a != p[3]) {
|
|
p[3] = a;
|
|
++changed;
|
|
}
|
|
|
|
const int x = (int) (i % (size_t) w), y = (int) (i / (size_t) w);
|
|
auto visit = [&](size_t n) {
|
|
if (!seen[n] && eligible(n)) {
|
|
seen[n] = 1;
|
|
queue.push_back(n);
|
|
}
|
|
};
|
|
if (x > 0) visit(i - 1);
|
|
if (x + 1 < w) visit(i + 1);
|
|
if (y > 0) visit(i - (size_t) w);
|
|
if (y + 1 < h) visit(i + (size_t) w);
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
bool trellis2_preprocess_rgba(const uint8_t * rgba, int w, int h,
|
|
int out_size, std::vector<uint8_t> & out_rgb,
|
|
std::string * error) {
|
|
if (!rgba || w <= 0 || h <= 0 || out_size <= 0) {
|
|
set_error(error, "invalid arguments");
|
|
return false;
|
|
}
|
|
|
|
// 1. downscale so max(W, H) <= 1024 (PIL: int(dim * scale) floor)
|
|
std::vector<uint8_t> img(rgba, rgba + (size_t) w * h * 4);
|
|
const int max_size = w > h ? w : h;
|
|
if (max_size > 1024) {
|
|
const double scale = 1024.0 / (double) max_size;
|
|
const int nw = (int) ((double) w * scale);
|
|
const int nh = (int) ((double) h * scale);
|
|
std::vector<uint8_t> resized;
|
|
pil_resize(img.data(), w, h, 4, nw, nh, resized);
|
|
img = std::move(resized);
|
|
w = nw;
|
|
h = nh;
|
|
}
|
|
|
|
// 2. bounding box of alpha > 0.8*255, square crop centered on it
|
|
int x0 = w, y0 = h, x1 = -1, y1 = -1;
|
|
for (int y = 0; y < h; ++y) {
|
|
for (int x = 0; x < w; ++x) {
|
|
if (img[((size_t) y * w + x) * 4 + 3] > 204) { // 0.8*255 = 204.0
|
|
if (x < x0) x0 = x;
|
|
if (x > x1) x1 = x;
|
|
if (y < y0) y0 = y;
|
|
if (y > y1) y1 = y;
|
|
}
|
|
}
|
|
}
|
|
if (x1 < 0) {
|
|
set_error(error, "image has no pixels with alpha > 0.8 (fully transparent?)");
|
|
return false;
|
|
}
|
|
|
|
const double cx = (double) (x0 + x1) / 2.0;
|
|
const double cy = (double) (y0 + y1) / 2.0;
|
|
const int size = (x1 - x0) > (y1 - y0) ? (x1 - x0) : (y1 - y0);
|
|
const int half = size / 2;
|
|
|
|
const int cx0 = py_round_half_even(cx - half);
|
|
const int cy0 = py_round_half_even(cy - half);
|
|
const int cx1 = py_round_half_even(cx + half);
|
|
const int cy1 = py_round_half_even(cy + half);
|
|
const int cw = cx1 - cx0;
|
|
const int chh = cy1 - cy0;
|
|
if (cw <= 0 || chh <= 0) {
|
|
set_error(error, "degenerate alpha bounding box");
|
|
return false;
|
|
}
|
|
|
|
// 3. crop (zero-padded outside the source) + premultiply onto black -> RGB
|
|
std::vector<uint8_t> rgb((size_t) cw * chh * 3, 0);
|
|
for (int y = 0; y < chh; ++y) {
|
|
const int sy = y + cy0;
|
|
if (sy < 0 || sy >= h) continue;
|
|
for (int x = 0; x < cw; ++x) {
|
|
const int sx = x + cx0;
|
|
if (sx < 0 || sx >= w) continue;
|
|
const uint8_t * p = &img[((size_t) sy * w + sx) * 4];
|
|
const float a = (float) p[3] / 255.0f;
|
|
for (int c = 0; c < 3; ++c) {
|
|
// matches numpy: ((rgb/255 * alpha/255) * 255).astype(uint8)
|
|
const float v = ((float) p[c] / 255.0f) * a * 255.0f;
|
|
rgb[((size_t) y * cw + x) * 3 + c] = (uint8_t) v;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. LANCZOS resize to out_size x out_size
|
|
pil_resize(rgb.data(), cw, chh, 3, out_size, out_size, out_rgb);
|
|
return true;
|
|
}
|
|
|
|
bool trellis2_save_dinodata(const std::string & path,
|
|
const trellis2_dino_cond & cond,
|
|
std::string * error) {
|
|
if (cond.empty() || cond.shape.empty()) {
|
|
set_error(error, "empty cond");
|
|
return false;
|
|
}
|
|
int64_t total = 1;
|
|
for (int64_t d : cond.shape) total *= d;
|
|
if ((size_t) total != cond.data.size()) {
|
|
set_error(error, "shape/data size mismatch");
|
|
return false;
|
|
}
|
|
|
|
std::ofstream f(path, std::ios::binary);
|
|
if (!f) {
|
|
set_error(error, "cannot open for writing: " + path);
|
|
return false;
|
|
}
|
|
f.write("DINOCOND", 8);
|
|
const uint32_t version = cond.format_version ? cond.format_version : 1;
|
|
const uint32_t dtype = 0; // f32
|
|
const uint32_t ndim = (uint32_t) cond.shape.size();
|
|
auto w32 = [&](uint32_t v) { f.write(reinterpret_cast<const char *>(&v), 4); };
|
|
w32(version);
|
|
w32(dtype);
|
|
w32(ndim);
|
|
for (int64_t d : cond.shape) w32((uint32_t) d);
|
|
f.write(reinterpret_cast<const char *>(cond.data.data()),
|
|
(std::streamsize) (cond.data.size() * sizeof(float)));
|
|
if (!f) {
|
|
set_error(error, "short write: " + path);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** Shape-SLAT flow DiT (stage 2) — GGUF loader
|
|
*****************************************************************************/
|
|
|
|
struct trellis2_slat_flow_model {
|
|
gguf_context * gguf = nullptr;
|
|
ggml_context * ctx = nullptr;
|
|
trellis2_slat_flow_hparams hp;
|
|
bool has_data = false;
|
|
|
|
ggml_backend_t backend = nullptr;
|
|
ggml_backend_buffer_t weights_buf = nullptr;
|
|
std::string backend_name;
|
|
|
|
std::unordered_map<std::string, ggml_tensor *> tensors;
|
|
};
|
|
|
|
trellis2_slat_flow_model *
|
|
trellis2_slat_flow_load(const std::string & path, bool load_tensors, std::string * error,
|
|
const char * device) {
|
|
auto * m = new trellis2_slat_flow_model();
|
|
|
|
gguf_init_params params;
|
|
params.no_alloc = true;
|
|
params.ctx = &m->ctx;
|
|
|
|
m->gguf = gguf_init_from_file(path.c_str(), params);
|
|
if (!m->gguf) {
|
|
set_error(error, "gguf_init_from_file failed (not a GGUF file?): " + path);
|
|
delete m;
|
|
return nullptr;
|
|
}
|
|
|
|
const char * arch = kv_str(m->gguf, "general.architecture", "");
|
|
if (std::strcmp(arch, "trellis2-slat-flow") != 0) {
|
|
set_error(error, std::string("unexpected architecture '") + arch +
|
|
"' (expected 'trellis2-slat-flow')");
|
|
trellis2_slat_flow_free(m);
|
|
return nullptr;
|
|
}
|
|
|
|
trellis2_slat_flow_hparams & hp = m->hp;
|
|
const char * P = "trellis2.slat_flow.";
|
|
auto K = [&](const std::string & suffix) { return std::string(P) + suffix; };
|
|
|
|
hp.resolution = (int32_t) kv_u32 (m->gguf, K("resolution").c_str(), 0);
|
|
hp.in_channels = (int32_t) kv_u32 (m->gguf, K("in_channels").c_str(), 0);
|
|
hp.out_channels = (int32_t) kv_u32 (m->gguf, K("out_channels").c_str(), 0);
|
|
hp.model_channels = (int32_t) kv_u32 (m->gguf, K("model_channels").c_str(), 0);
|
|
hp.cond_channels = (int32_t) kv_u32 (m->gguf, K("cond_channels").c_str(), 0);
|
|
hp.num_blocks = (int32_t) kv_u32 (m->gguf, K("num_blocks").c_str(), 0);
|
|
hp.num_heads = (int32_t) kv_u32 (m->gguf, K("num_heads").c_str(), 0);
|
|
hp.mlp_ratio = kv_f32 (m->gguf, K("mlp_ratio").c_str(), 0.0f);
|
|
hp.share_mod = kv_bool(m->gguf, K("share_mod").c_str(), false) ? 1 : 0;
|
|
hp.qk_rms_norm = kv_bool(m->gguf, K("qk_rms_norm").c_str(), false) ? 1 : 0;
|
|
hp.qk_rms_norm_cross = kv_bool(m->gguf, K("qk_rms_norm_cross").c_str(), false) ? 1 : 0;
|
|
hp.rope_freq_min = kv_f32 (m->gguf, K("rope_freq_min").c_str(), 1.0f);
|
|
hp.rope_freq_base = kv_f32 (m->gguf, K("rope_freq_base").c_str(), 10000.0f);
|
|
hp.file_type = (int32_t) kv_u32 (m->gguf, "general.file_type", 0);
|
|
std::snprintf(hp.pe_mode, sizeof(hp.pe_mode), "%s",
|
|
kv_str(m->gguf, K("pe_mode").c_str(), "rope"));
|
|
for (int c = 0; c < hp.out_channels && c < 64; ++c) {
|
|
hp.norm_mean[c] = kv_f32(m->gguf, K("norm_mean." + std::to_string(c)).c_str(), 0.0f);
|
|
hp.norm_std[c] = kv_f32(m->gguf, K("norm_std." + std::to_string(c)).c_str(), 1.0f);
|
|
}
|
|
// Texture SLAT flow: concat_cond. 0/absent on the shape flow. When > 0, the
|
|
// shape SLAT (concat_norm-normalized) is concatenated onto the noise so the
|
|
// DiT sees in_channels = out_channels + concat_cond_channels.
|
|
hp.concat_cond_channels = (int32_t) kv_u32(m->gguf, K("concat_cond_channels").c_str(), 0);
|
|
for (int c = 0; c < hp.concat_cond_channels && c < 64; ++c) {
|
|
hp.concat_norm_mean[c] = kv_f32(m->gguf, K("concat_norm_mean." + std::to_string(c)).c_str(), 0.0f);
|
|
hp.concat_norm_std[c] = kv_f32(m->gguf, K("concat_norm_std." + std::to_string(c)).c_str(), 1.0f);
|
|
}
|
|
|
|
for (ggml_tensor * t = ggml_get_first_tensor(m->ctx); t != nullptr;
|
|
t = ggml_get_next_tensor(m->ctx, t)) {
|
|
m->tensors[t->name] = t;
|
|
}
|
|
|
|
if (load_tensors) {
|
|
m->backend = init_best_backend(m->backend_name, device);
|
|
m->weights_buf = ggml_backend_alloc_ctx_tensors(m->ctx, m->backend);
|
|
if (!m->weights_buf) {
|
|
set_error(error, "failed to allocate weights on backend " + m->backend_name);
|
|
trellis2_slat_flow_free(m);
|
|
return nullptr;
|
|
}
|
|
std::ifstream fin(path, std::ios::binary);
|
|
if (!fin) {
|
|
set_error(error, "cannot reopen file for weight data: " + path);
|
|
trellis2_slat_flow_free(m);
|
|
return nullptr;
|
|
}
|
|
const size_t data_off = gguf_get_data_offset(m->gguf);
|
|
const int64_t nt = gguf_get_n_tensors(m->gguf);
|
|
std::vector<uint8_t> buf;
|
|
for (int64_t i = 0; i < nt; ++i) {
|
|
const char * name = gguf_get_tensor_name(m->gguf, i);
|
|
ggml_tensor * t = m->tensors[name];
|
|
const size_t nb = ggml_nbytes(t);
|
|
const size_t off = data_off + gguf_get_tensor_offset(m->gguf, i);
|
|
buf.resize(nb);
|
|
fin.seekg((std::streamoff) off, std::ios::beg);
|
|
if (!fin.read(reinterpret_cast<char *>(buf.data()), (std::streamsize) nb)) {
|
|
set_error(error, std::string("failed reading weight '") + name + "' from file");
|
|
trellis2_slat_flow_free(m);
|
|
return nullptr;
|
|
}
|
|
ggml_backend_tensor_set(t, buf.data(), 0, nb);
|
|
}
|
|
m->has_data = true;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
void trellis2_slat_flow_free(trellis2_slat_flow_model * m) {
|
|
if (!m) return;
|
|
if (m->weights_buf) ggml_backend_buffer_free(m->weights_buf);
|
|
if (m->backend) ggml_backend_free(m->backend);
|
|
if (m->gguf) gguf_free(m->gguf);
|
|
if (m->ctx) ggml_free(m->ctx);
|
|
delete m;
|
|
}
|
|
|
|
const char * trellis2_slat_flow_backend_name(const trellis2_slat_flow_model * m) {
|
|
return (m && !m->backend_name.empty()) ? m->backend_name.c_str() : "none";
|
|
}
|
|
|
|
const trellis2_slat_flow_hparams &
|
|
trellis2_slat_flow_hparams_of(const trellis2_slat_flow_model * m) {
|
|
return m->hp;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** Shape-SLAT flow DiT — forward pass
|
|
**
|
|
** Identical block structure to trellis2_ss_flow_forward, with the dense R^3
|
|
** token grid replaced by the L active voxels: 3D RoPE phases come from each
|
|
** voxel's integer coords, everything else (shared adaLN modulation, QK-RMS
|
|
** norm, cross-attention to the DINO tokens, GELU-tanh FFN) is unchanged.
|
|
*****************************************************************************/
|
|
|
|
namespace {
|
|
|
|
// Interleaved 3D-RoPE tables for an explicit voxel-coordinate list. Same
|
|
// layout as rope_tables(): [head_dim, 1, L] with cos[2p] == cos[2p+1].
|
|
void rope_tables_coords(const int32_t * coords, int L, int head_dim,
|
|
float freq_min, float freq_base,
|
|
std::vector<float> & cos_t, std::vector<float> & sin_t) {
|
|
const int dim = 3;
|
|
const int freq_dim = head_dim / 2 / dim;
|
|
|
|
std::vector<float> freqs((size_t) freq_dim);
|
|
for (int mi = 0; mi < freq_dim; ++mi) {
|
|
freqs[mi] = freq_min / std::pow(freq_base, (float) mi / (float) freq_dim);
|
|
}
|
|
|
|
cos_t.assign((size_t) head_dim * L, 1.0f);
|
|
sin_t.assign((size_t) head_dim * L, 0.0f);
|
|
const int pairs = head_dim / 2;
|
|
for (int v = 0; v < L; ++v) {
|
|
for (int p = 0; p < pairs; ++p) {
|
|
float theta = 0.0f;
|
|
if (p < dim * freq_dim) {
|
|
theta = (float) coords[(size_t) v * 3 + p / freq_dim] * freqs[p % freq_dim];
|
|
}
|
|
const size_t base = (size_t) v * head_dim + (size_t) 2 * p;
|
|
cos_t[base] = cos_t[base + 1] = std::cos(theta);
|
|
sin_t[base] = sin_t[base + 1] = std::sin(theta);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool trellis2_slat_flow_forward(trellis2_slat_flow_model * m,
|
|
const float * x, int n_voxels, const int32_t * coords,
|
|
float t,
|
|
const float * cond, int cond_tokens, int cond_channels,
|
|
float * out, std::string * error) {
|
|
if (!m) { set_error(error, "null model"); return false; }
|
|
if (!m->has_data) { set_error(error, "model loaded metadata-only; reload with load_tensors=true"); return false; }
|
|
|
|
const trellis2_slat_flow_hparams & hp = m->hp;
|
|
if (std::strcmp(hp.pe_mode, "rope") != 0) { set_error(error, "only pe_mode=rope is implemented"); return false; }
|
|
if (!hp.share_mod) { set_error(error, "only share_mod=true is implemented"); return false; }
|
|
if (cond_channels != hp.cond_channels) { set_error(error, "cond_channels mismatch"); return false; }
|
|
|
|
const int C = hp.model_channels;
|
|
const int N = n_voxels;
|
|
const int H = hp.num_heads;
|
|
const int hd = hp.head_dim();
|
|
const int Lkv = cond_tokens;
|
|
const float attn_scale = 1.0f / std::sqrt((float) hd);
|
|
|
|
std::string missing;
|
|
auto W = [&](const std::string & n) -> ggml_tensor * {
|
|
auto it = m->tensors.find(n);
|
|
if (it == m->tensors.end()) { if (missing.empty()) missing = n; return nullptr; }
|
|
return it->second;
|
|
};
|
|
|
|
const size_t mem = ggml_tensor_overhead() * 32768 + ggml_graph_overhead_custom(32768, false);
|
|
ggml_init_params ip{ mem, nullptr, true };
|
|
ggml_context * ctx = ggml_init(ip);
|
|
ggml_cgraph * gf = ggml_new_graph_custom(ctx, 32768, false);
|
|
|
|
ggml_tensor * x_t = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, hp.in_channels, N); // voxel-major [L][Cin]
|
|
ggml_tensor * temb = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 256);
|
|
ggml_tensor * cos_t = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, hd, 1, N);
|
|
ggml_tensor * sin_t = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, hd, 1, N);
|
|
ggml_tensor * cnd = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, cond_channels, Lkv);
|
|
ggml_set_input(x_t);
|
|
ggml_set_input(temb);
|
|
ggml_set_input(cos_t);
|
|
ggml_set_input(sin_t);
|
|
ggml_set_input(cnd);
|
|
|
|
auto lin = [&](ggml_tensor * in, const std::string & pfx) -> ggml_tensor * {
|
|
ggml_tensor * y = ggml_mul_mat(ctx, W(pfx + ".weight"), in);
|
|
ggml_tensor * b = W(pfx + ".bias");
|
|
if (b) y = ggml_add(ctx, y, b);
|
|
return y;
|
|
};
|
|
auto modulate = [&](ggml_tensor * h, ggml_tensor * scale, ggml_tensor * shift) {
|
|
return ggml_add(ctx, ggml_add(ctx, ggml_mul(ctx, h, scale), h), shift);
|
|
};
|
|
auto rope = [&](ggml_tensor * q3) -> ggml_tensor * {
|
|
ggml_tensor * q4 = ggml_reshape_4d(ctx, q3, 2, hd / 2, H, N);
|
|
ggml_tensor * q0 = ggml_cont(ctx, ggml_view_4d(ctx, q4, 1, hd / 2, H, N,
|
|
q4->nb[1], q4->nb[2], q4->nb[3], 0));
|
|
ggml_tensor * q1 = ggml_cont(ctx, ggml_view_4d(ctx, q4, 1, hd / 2, H, N,
|
|
q4->nb[1], q4->nb[2], q4->nb[3], q4->nb[0]));
|
|
ggml_tensor * swap = ggml_concat(ctx, ggml_neg(ctx, q1), q0, 0);
|
|
swap = ggml_reshape_3d(ctx, swap, hd, H, N);
|
|
return ggml_add(ctx, ggml_mul(ctx, q3, cos_t), ggml_mul(ctx, swap, sin_t));
|
|
};
|
|
auto qk_norm = [&](ggml_tensor * v3, const std::string & gname) {
|
|
return ggml_mul(ctx, ggml_rms_norm(ctx, v3, 1e-12f), W(gname));
|
|
};
|
|
auto sdpa = [&](ggml_tensor * q3, ggml_tensor * k3, ggml_tensor * v3) {
|
|
return sdpa_auto(ctx, q3, k3, v3, C, attn_scale);
|
|
};
|
|
|
|
const size_t es = sizeof(float);
|
|
|
|
ggml_tensor * h = lin(x_t, "input_layer"); // [C, N]
|
|
|
|
ggml_tensor * te = lin(temb, "t_embedder.mlp.0");
|
|
te = ggml_silu(ctx, te);
|
|
te = lin(te, "t_embedder.mlp.2");
|
|
ggml_tensor * tmod = lin(ggml_silu(ctx, te), "adaLN_modulation.1");
|
|
|
|
ggml_tensor * cond_h = cnd;
|
|
|
|
for (int b = 0; b < hp.num_blocks; ++b) {
|
|
const std::string blk = "blocks." + std::to_string(b);
|
|
ggml_tensor * mods = ggml_add(ctx, W(blk + ".modulation"), tmod);
|
|
auto chunk = [&](int idx) {
|
|
return ggml_view_1d(ctx, mods, C, (size_t) idx * C * es);
|
|
};
|
|
ggml_tensor * shift_msa = chunk(0), * scale_msa = chunk(1), * gate_msa = chunk(2);
|
|
ggml_tensor * shift_mlp = chunk(3), * scale_mlp = chunk(4), * gate_mlp = chunk(5);
|
|
|
|
ggml_tensor * hn = modulate(ggml_norm(ctx, h, 1e-6f), scale_msa, shift_msa);
|
|
ggml_tensor * qkv = lin(hn, blk + ".self_attn.to_qkv");
|
|
ggml_tensor * q = ggml_reshape_3d(ctx, ggml_cont(ctx, ggml_view_2d(ctx, qkv, C, N, qkv->nb[1], 0)), hd, H, N);
|
|
ggml_tensor * k = ggml_reshape_3d(ctx, ggml_cont(ctx, ggml_view_2d(ctx, qkv, C, N, qkv->nb[1], (size_t) C * es)), hd, H, N);
|
|
ggml_tensor * v = ggml_reshape_3d(ctx, ggml_cont(ctx, ggml_view_2d(ctx, qkv, C, N, qkv->nb[1], (size_t) 2 * C * es)), hd, H, N);
|
|
q = rope(qk_norm(q, blk + ".self_attn.q_rms_norm.gamma"));
|
|
k = rope(qk_norm(k, blk + ".self_attn.k_rms_norm.gamma"));
|
|
ggml_tensor * sa = lin(sdpa(q, k, v), blk + ".self_attn.to_out");
|
|
h = ggml_add(ctx, h, ggml_mul(ctx, sa, gate_msa));
|
|
|
|
ggml_tensor * h2 = ggml_norm(ctx, h, 1e-6f);
|
|
h2 = ggml_add(ctx, ggml_mul(ctx, h2, W(blk + ".norm2.weight")), W(blk + ".norm2.bias"));
|
|
ggml_tensor * cq = ggml_reshape_3d(ctx, lin(h2, blk + ".cross_attn.to_q"), hd, H, N);
|
|
cq = qk_norm(cq, blk + ".cross_attn.q_rms_norm.gamma");
|
|
ggml_tensor * kv = lin(cond_h, blk + ".cross_attn.to_kv");
|
|
ggml_tensor * ck = ggml_reshape_3d(ctx, ggml_cont(ctx, ggml_view_2d(ctx, kv, C, Lkv, kv->nb[1], 0)), hd, H, Lkv);
|
|
ggml_tensor * cv = ggml_reshape_3d(ctx, ggml_cont(ctx, ggml_view_2d(ctx, kv, C, Lkv, kv->nb[1], (size_t) C * es)), hd, H, Lkv);
|
|
ck = qk_norm(ck, blk + ".cross_attn.k_rms_norm.gamma");
|
|
ggml_tensor * ca = lin(sdpa(cq, ck, cv), blk + ".cross_attn.to_out");
|
|
h = ggml_add(ctx, h, ca);
|
|
|
|
ggml_tensor * hm = modulate(ggml_norm(ctx, h, 1e-6f), scale_mlp, shift_mlp);
|
|
hm = lin(hm, blk + ".mlp.mlp.0");
|
|
hm = ggml_gelu(ctx, hm);
|
|
hm = lin(hm, blk + ".mlp.mlp.2");
|
|
h = ggml_add(ctx, h, ggml_mul(ctx, hm, gate_mlp));
|
|
}
|
|
|
|
h = ggml_norm(ctx, h, 1e-5f);
|
|
h = lin(h, "out_layer"); // [Cout, N]
|
|
ggml_tensor * y = ggml_cont(ctx, h); // voxel-major [L][Cout]
|
|
ggml_set_output(y);
|
|
|
|
if (!missing.empty()) {
|
|
set_error(error, "missing tensor: " + missing);
|
|
ggml_free(ctx);
|
|
return false;
|
|
}
|
|
|
|
ggml_build_forward_expand(gf, y);
|
|
|
|
ggml_backend_t backend = m->backend;
|
|
ggml_gallocr_t alloc = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend));
|
|
if (!ggml_gallocr_alloc_graph(alloc, gf)) {
|
|
set_error(error, "ggml_gallocr_alloc_graph failed");
|
|
ggml_gallocr_free(alloc); ggml_free(ctx);
|
|
return false;
|
|
}
|
|
|
|
std::vector<float> emb = timestep_embedding(t, 256);
|
|
std::vector<float> cosv, sinv;
|
|
rope_tables_coords(coords, N, hd, hp.rope_freq_min, hp.rope_freq_base, cosv, sinv);
|
|
|
|
ggml_backend_tensor_set(x_t, x, 0, (size_t) hp.in_channels * N * es);
|
|
ggml_backend_tensor_set(temb, emb.data(), 0, emb.size() * es);
|
|
ggml_backend_tensor_set(cos_t, cosv.data(), 0, cosv.size() * es);
|
|
ggml_backend_tensor_set(sin_t, sinv.data(), 0, sinv.size() * es);
|
|
ggml_backend_tensor_set(cnd, cond, 0, (size_t) cond_channels * Lkv * es);
|
|
|
|
const ggml_status st = ggml_backend_graph_compute(backend, gf);
|
|
bool ok = (st == GGML_STATUS_SUCCESS);
|
|
if (ok) {
|
|
ggml_backend_tensor_get(y, out, 0, (size_t) hp.out_channels * N * es);
|
|
} else {
|
|
set_error(error, "graph compute failed");
|
|
}
|
|
|
|
ggml_gallocr_free(alloc);
|
|
ggml_free(ctx);
|
|
return ok;
|
|
}
|
|
|
|
bool trellis2_slat_flow_sample(trellis2_slat_flow_model * m,
|
|
int n_voxels, const int32_t * coords,
|
|
const float * cond, int cond_tokens, int cond_channels,
|
|
const trellis2_ss_sampler_params * params_in,
|
|
const float * noise, bool denormalize,
|
|
float * out_latent, std::string * error) {
|
|
if (!m) { set_error(error, "null model"); return false; }
|
|
if (!m->has_data) { set_error(error, "model loaded metadata-only; reload with load_tensors=true"); return false; }
|
|
|
|
trellis2_ss_sampler_params P;
|
|
if (params_in) P = *params_in;
|
|
|
|
const trellis2_slat_flow_hparams & hp = m->hp;
|
|
const size_t n = (size_t) hp.in_channels * n_voxels;
|
|
const double sm = P.sigma_min;
|
|
|
|
std::vector<float> x_t(n);
|
|
if (noise) {
|
|
std::memcpy(x_t.data(), noise, n * sizeof(float));
|
|
} else {
|
|
std::mt19937_64 rng(P.seed);
|
|
std::normal_distribution<float> nd(0.0f, 1.0f);
|
|
for (size_t i = 0; i < n; ++i) x_t[i] = nd(rng);
|
|
}
|
|
|
|
std::vector<double> ts((size_t) P.steps + 1);
|
|
for (int i = 0; i <= P.steps; ++i) {
|
|
const double lin = 1.0 - (double) i / (double) P.steps;
|
|
ts[i] = P.rescale_t * lin / (1.0 + (P.rescale_t - 1.0) * lin);
|
|
}
|
|
|
|
const std::vector<float> zero_cond((size_t) cond_tokens * cond_channels, 0.0f);
|
|
std::vector<float> pred_pos(n), pred_neg(n), pred_v(n), x0_pos(n), x0_cfg(n);
|
|
|
|
auto fwd = [&](double t, const float * c, std::vector<float> & dst) -> bool {
|
|
return trellis2_slat_flow_forward(m, x_t.data(), n_voxels, coords,
|
|
(float) (1000.0 * t),
|
|
c, cond_tokens, cond_channels, dst.data(), error);
|
|
};
|
|
|
|
for (int i = 0; i < P.steps; ++i) {
|
|
const double t = ts[i], t_prev = ts[i + 1];
|
|
const bool in_interval = (t >= P.guidance_interval_min && t <= P.guidance_interval_max);
|
|
const float gs = in_interval ? P.guidance_strength : 1.0f;
|
|
|
|
if (gs == 1.0f) {
|
|
if (!fwd(t, cond, pred_v)) return false;
|
|
} else if (gs == 0.0f) {
|
|
if (!fwd(t, zero_cond.data(), pred_v)) return false;
|
|
} else {
|
|
if (!fwd(t, cond, pred_pos)) return false;
|
|
if (!fwd(t, zero_cond.data(), pred_neg)) return false;
|
|
for (size_t k = 0; k < n; ++k) pred_v[k] = gs * pred_pos[k] + (1.0f - gs) * pred_neg[k];
|
|
|
|
if (P.guidance_rescale > 0.0f) {
|
|
pred_to_xstart(x_t, t, sm, pred_pos, x0_pos);
|
|
pred_to_xstart(x_t, t, sm, pred_v, x0_cfg);
|
|
const double std_pos = unbiased_std(x0_pos);
|
|
const double std_cfg = unbiased_std(x0_cfg);
|
|
const double ratio = (std_cfg != 0.0) ? std_pos / std_cfg : 1.0;
|
|
const float gr = P.guidance_rescale;
|
|
for (size_t k = 0; k < n; ++k) {
|
|
const double rescaled = x0_cfg[k] * ratio;
|
|
x0_cfg[k] = (float) (gr * rescaled + (1.0 - gr) * x0_cfg[k]);
|
|
}
|
|
xstart_to_pred(x_t, t, sm, x0_cfg, pred_v);
|
|
}
|
|
}
|
|
|
|
const double dt = t - t_prev;
|
|
for (size_t k = 0; k < n; ++k) x_t[k] = (float) (x_t[k] - dt * pred_v[k]);
|
|
|
|
if (P.verbose) {
|
|
std::fprintf(stderr, "\r[slat sample] step %2d/%d t=%.4f->%.4f %s ",
|
|
i + 1, P.steps, t, t_prev, in_interval ? "cfg" : "uncond");
|
|
std::fflush(stderr);
|
|
}
|
|
if (P.progress) P.progress(P.progress_user, i + 1, P.steps);
|
|
}
|
|
if (P.verbose) std::fprintf(stderr, "\n");
|
|
|
|
if (denormalize) {
|
|
const int C = hp.in_channels;
|
|
for (int v = 0; v < n_voxels; ++v) {
|
|
for (int c = 0; c < C; ++c) {
|
|
x_t[(size_t) v * C + c] = x_t[(size_t) v * C + c] * hp.norm_std[c] + hp.norm_mean[c];
|
|
}
|
|
}
|
|
}
|
|
|
|
std::memcpy(out_latent, x_t.data(), n * sizeof(float));
|
|
return true;
|
|
}
|
|
|
|
// Texture-SLAT flow sampling with concat_cond. Same flow-Euler loop, but the
|
|
// diffused variable is out_channels (32) and each forward is fed a fresh
|
|
// [noise(32) | normalized shape-SLAT(32)] = in_channels (64) input.
|
|
bool trellis2_slat_flow_sample_tex(trellis2_slat_flow_model * m,
|
|
int n_voxels, const int32_t * coords,
|
|
const float * cond, int cond_tokens, int cond_channels,
|
|
const float * shape_slat,
|
|
const trellis2_ss_sampler_params * params_in,
|
|
const float * noise, bool denormalize,
|
|
float * out_latent, std::string * error) {
|
|
if (!m) { set_error(error, "null model"); return false; }
|
|
if (!m->has_data) { set_error(error, "model loaded metadata-only; reload with load_tensors=true"); return false; }
|
|
|
|
const trellis2_slat_flow_hparams & hp = m->hp;
|
|
const int Cin = hp.in_channels, Cout = hp.out_channels, Ccat = hp.concat_cond_channels;
|
|
if (Ccat <= 0 || Cin != Cout + Ccat) {
|
|
set_error(error, "model is not a concat_cond (texture) flow"); return false;
|
|
}
|
|
|
|
trellis2_ss_sampler_params P;
|
|
if (params_in) P = *params_in;
|
|
const size_t n = (size_t) Cout * n_voxels; // diffused variable
|
|
const double sm = P.sigma_min;
|
|
|
|
std::vector<float> x_t(n);
|
|
if (noise) {
|
|
std::memcpy(x_t.data(), noise, n * sizeof(float));
|
|
} else {
|
|
std::mt19937_64 rng(P.seed);
|
|
std::normal_distribution<float> nd(0.0f, 1.0f);
|
|
for (size_t i = 0; i < n; ++i) x_t[i] = nd(rng);
|
|
}
|
|
|
|
// shape SLAT normalized by concat_norm (once); concatenated onto the noise.
|
|
std::vector<float> shape_n((size_t) Ccat * n_voxels);
|
|
for (int v = 0; v < n_voxels; ++v)
|
|
for (int c = 0; c < Ccat; ++c)
|
|
shape_n[(size_t) v * Ccat + c] =
|
|
(shape_slat[(size_t) v * Ccat + c] - hp.concat_norm_mean[c]) / hp.concat_norm_std[c];
|
|
|
|
std::vector<double> ts((size_t) P.steps + 1);
|
|
for (int i = 0; i <= P.steps; ++i) {
|
|
const double lin = 1.0 - (double) i / (double) P.steps;
|
|
ts[i] = P.rescale_t * lin / (1.0 + (P.rescale_t - 1.0) * lin);
|
|
}
|
|
|
|
const std::vector<float> zero_cond((size_t) cond_tokens * cond_channels, 0.0f);
|
|
std::vector<float> pred_pos(n), pred_neg(n), pred_v(n), x0_pos(n), x0_cfg(n);
|
|
std::vector<float> xin((size_t) Cin * n_voxels);
|
|
|
|
auto fwd = [&](double t, const float * c, std::vector<float> & dst) -> bool {
|
|
for (int v = 0; v < n_voxels; ++v) {
|
|
float * d = xin.data() + (size_t) v * Cin;
|
|
std::memcpy(d, x_t.data() + (size_t) v * Cout, (size_t) Cout * sizeof(float));
|
|
std::memcpy(d + Cout, shape_n.data() + (size_t) v * Ccat, (size_t) Ccat * sizeof(float));
|
|
}
|
|
return trellis2_slat_flow_forward(m, xin.data(), n_voxels, coords,
|
|
(float) (1000.0 * t),
|
|
c, cond_tokens, cond_channels, dst.data(), error);
|
|
};
|
|
|
|
for (int i = 0; i < P.steps; ++i) {
|
|
const double t = ts[i], t_prev = ts[i + 1];
|
|
const bool in_interval = (t >= P.guidance_interval_min && t <= P.guidance_interval_max);
|
|
const float gs = in_interval ? P.guidance_strength : 1.0f;
|
|
|
|
if (gs == 1.0f) {
|
|
if (!fwd(t, cond, pred_v)) return false;
|
|
} else if (gs == 0.0f) {
|
|
if (!fwd(t, zero_cond.data(), pred_v)) return false;
|
|
} else {
|
|
if (!fwd(t, cond, pred_pos)) return false;
|
|
if (!fwd(t, zero_cond.data(), pred_neg)) return false;
|
|
for (size_t k = 0; k < n; ++k) pred_v[k] = gs * pred_pos[k] + (1.0f - gs) * pred_neg[k];
|
|
|
|
if (P.guidance_rescale > 0.0f) {
|
|
pred_to_xstart(x_t, t, sm, pred_pos, x0_pos);
|
|
pred_to_xstart(x_t, t, sm, pred_v, x0_cfg);
|
|
const double std_pos = unbiased_std(x0_pos);
|
|
const double std_cfg = unbiased_std(x0_cfg);
|
|
const double ratio = (std_cfg != 0.0) ? std_pos / std_cfg : 1.0;
|
|
const float gr = P.guidance_rescale;
|
|
for (size_t k = 0; k < n; ++k) {
|
|
const double rescaled = x0_cfg[k] * ratio;
|
|
x0_cfg[k] = (float) (gr * rescaled + (1.0 - gr) * x0_cfg[k]);
|
|
}
|
|
xstart_to_pred(x_t, t, sm, x0_cfg, pred_v);
|
|
}
|
|
}
|
|
|
|
const double dt = t - t_prev;
|
|
for (size_t k = 0; k < n; ++k) x_t[k] = (float) (x_t[k] - dt * pred_v[k]);
|
|
|
|
if (P.verbose) {
|
|
std::fprintf(stderr, "\r[tex slat sample] step %2d/%d t=%.4f->%.4f %s ",
|
|
i + 1, P.steps, t, t_prev, in_interval ? "cfg" : "uncond");
|
|
std::fflush(stderr);
|
|
}
|
|
if (P.progress) P.progress(P.progress_user, i + 1, P.steps);
|
|
}
|
|
if (P.verbose) std::fprintf(stderr, "\n");
|
|
|
|
if (denormalize) {
|
|
for (int v = 0; v < n_voxels; ++v)
|
|
for (int c = 0; c < Cout; ++c)
|
|
x_t[(size_t) v * Cout + c] = x_t[(size_t) v * Cout + c] * hp.norm_std[c] + hp.norm_mean[c];
|
|
}
|
|
|
|
std::memcpy(out_latent, x_t.data(), n * sizeof(float));
|
|
return true;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** Shape-SLAT VAE decoder (FlexiDualGridVaeDecoder) — GGUF loader
|
|
*****************************************************************************/
|
|
|
|
struct trellis2_shape_dec_model {
|
|
gguf_context * gguf = nullptr;
|
|
ggml_context * ctx = nullptr;
|
|
trellis2_shape_dec_hparams hp;
|
|
bool has_data = false;
|
|
|
|
ggml_backend_t backend = nullptr;
|
|
ggml_backend_buffer_t weights_buf = nullptr;
|
|
std::string backend_name;
|
|
|
|
std::unordered_map<std::string, ggml_tensor *> tensors;
|
|
};
|
|
|
|
static trellis2_shape_dec_model *
|
|
dec_load_impl(const std::string & path, bool load_tensors, std::string * error,
|
|
const char * device, const char * expect_arch, const char * kv_prefix) {
|
|
auto * m = new trellis2_shape_dec_model();
|
|
|
|
gguf_init_params params;
|
|
params.no_alloc = true;
|
|
params.ctx = &m->ctx;
|
|
|
|
m->gguf = gguf_init_from_file(path.c_str(), params);
|
|
if (!m->gguf) {
|
|
set_error(error, "gguf_init_from_file failed (not a GGUF file?): " + path);
|
|
delete m;
|
|
return nullptr;
|
|
}
|
|
|
|
const char * arch = kv_str(m->gguf, "general.architecture", "");
|
|
if (std::strcmp(arch, expect_arch) != 0) {
|
|
set_error(error, std::string("unexpected architecture '") + arch +
|
|
"' (expected '" + expect_arch + "')");
|
|
trellis2_shape_dec_free(m);
|
|
return nullptr;
|
|
}
|
|
|
|
trellis2_shape_dec_hparams & hp = m->hp;
|
|
const char * P = kv_prefix;
|
|
auto K = [&](const std::string & suffix) { return std::string(P) + suffix; };
|
|
|
|
hp.latent_channels = (int32_t) kv_u32(m->gguf, K("latent_channels").c_str(), 0);
|
|
hp.out_channels = (int32_t) kv_u32(m->gguf, K("out_channels").c_str(), 0);
|
|
hp.n_levels = (int32_t) kv_u32(m->gguf, K("n_levels").c_str(), 0);
|
|
hp.norm_eps = kv_f32(m->gguf, K("norm_eps").c_str(), 1e-6f);
|
|
hp.voxel_margin = kv_f32(m->gguf, K("voxel_margin").c_str(), 0.5f);
|
|
hp.file_type = (int32_t) kv_u32(m->gguf, "general.file_type", 0);
|
|
for (int i = 0; i < hp.n_levels && i < 8; ++i) {
|
|
hp.channels[i] = (int32_t) kv_u32(m->gguf, K("channels." + std::to_string(i)).c_str(), 0);
|
|
hp.num_blocks[i] = (int32_t) kv_u32(m->gguf, K("num_blocks." + std::to_string(i)).c_str(), 0);
|
|
}
|
|
|
|
for (ggml_tensor * t = ggml_get_first_tensor(m->ctx); t != nullptr;
|
|
t = ggml_get_next_tensor(m->ctx, t)) {
|
|
m->tensors[t->name] = t;
|
|
}
|
|
|
|
if (load_tensors) {
|
|
m->backend = init_best_backend(m->backend_name, device);
|
|
m->weights_buf = ggml_backend_alloc_ctx_tensors(m->ctx, m->backend);
|
|
if (!m->weights_buf) {
|
|
set_error(error, "failed to allocate weights on backend " + m->backend_name);
|
|
trellis2_shape_dec_free(m);
|
|
return nullptr;
|
|
}
|
|
std::ifstream fin(path, std::ios::binary);
|
|
if (!fin) {
|
|
set_error(error, "cannot reopen file for weight data: " + path);
|
|
trellis2_shape_dec_free(m);
|
|
return nullptr;
|
|
}
|
|
const size_t data_off = gguf_get_data_offset(m->gguf);
|
|
const int64_t nt = gguf_get_n_tensors(m->gguf);
|
|
std::vector<uint8_t> buf;
|
|
for (int64_t i = 0; i < nt; ++i) {
|
|
const char * name = gguf_get_tensor_name(m->gguf, i);
|
|
ggml_tensor * t = m->tensors[name];
|
|
const size_t nb = ggml_nbytes(t);
|
|
const size_t off = data_off + gguf_get_tensor_offset(m->gguf, i);
|
|
buf.resize(nb);
|
|
fin.seekg((std::streamoff) off, std::ios::beg);
|
|
if (!fin.read(reinterpret_cast<char *>(buf.data()), (std::streamsize) nb)) {
|
|
set_error(error, std::string("failed reading weight '") + name + "' from file");
|
|
trellis2_shape_dec_free(m);
|
|
return nullptr;
|
|
}
|
|
ggml_backend_tensor_set(t, buf.data(), 0, nb);
|
|
}
|
|
m->has_data = true;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
trellis2_shape_dec_model *
|
|
trellis2_shape_dec_load(const std::string & path, bool load_tensors, std::string * error,
|
|
const char * device) {
|
|
return dec_load_impl(path, load_tensors, error, device,
|
|
"trellis2-shape-dec", "trellis2.shape_dec.");
|
|
}
|
|
|
|
// The texture decoder is the same struct/driver as the shape decoder (out=6,
|
|
// pred_subdiv=False supplied at decode time via trellis2_tex_dec_decode).
|
|
trellis2_shape_dec_model *
|
|
trellis2_tex_dec_load(const std::string & path, bool load_tensors, std::string * error,
|
|
const char * device) {
|
|
return dec_load_impl(path, load_tensors, error, device,
|
|
"trellis2-tex-dec", "trellis2.tex_dec.");
|
|
}
|
|
|
|
void trellis2_shape_dec_free(trellis2_shape_dec_model * m) {
|
|
if (!m) return;
|
|
if (m->weights_buf) ggml_backend_buffer_free(m->weights_buf);
|
|
if (m->backend) ggml_backend_free(m->backend);
|
|
if (m->gguf) gguf_free(m->gguf);
|
|
if (m->ctx) ggml_free(m->ctx);
|
|
delete m;
|
|
}
|
|
|
|
const char * trellis2_shape_dec_backend_name(const trellis2_shape_dec_model * m) {
|
|
return (m && !m->backend_name.empty()) ? m->backend_name.c_str() : "none";
|
|
}
|
|
|
|
const trellis2_shape_dec_hparams &
|
|
trellis2_shape_dec_hparams_of(const trellis2_shape_dec_model * m) {
|
|
return m->hp;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** Shape-SLAT VAE decoder — forward
|
|
**
|
|
** Mirrors SparseUnetVaeDecoder.forward level by level. Submanifold sparse
|
|
** 3x3x3 convolutions are expressed as 27 x (get_rows gather + GEMM): the
|
|
** neighbor row index of every voxel for each kernel offset is precomputed on
|
|
** the host with a hash map (missing neighbors point at an appended zero row).
|
|
** The subdivision decision of each up-block crosses the device boundary (its
|
|
** logits pick which children exist), so each level runs as its own graph and
|
|
** the feature matrix round-trips through host memory between levels.
|
|
*****************************************************************************/
|
|
|
|
namespace {
|
|
|
|
// key for a voxel coordinate (10 bits per axis is plenty: res <= 1024)
|
|
inline uint64_t voxel_key(int32_t c1, int32_t c2, int32_t c3) {
|
|
return ((uint64_t) (uint32_t) c1 << 40) |
|
|
((uint64_t) (uint32_t) c2 << 20) |
|
|
(uint64_t) (uint32_t) c3;
|
|
}
|
|
|
|
// Neighbor row indices for all 27 offsets of a 3^3 submanifold conv.
|
|
// idx[k][v] = row of voxel v's neighbor at offset k, or L (the zero row).
|
|
// Kernel flattening matches the [Co, kD, kH, kW, Ci] weight layout with
|
|
// kD -> c1, kH -> c2, kW -> c3.
|
|
void build_neighbor_indices(const std::vector<int32_t> & coords, int L,
|
|
std::vector<std::vector<int32_t>> & idx) {
|
|
std::unordered_map<uint64_t, int32_t> map;
|
|
map.reserve((size_t) L * 2);
|
|
for (int v = 0; v < L; ++v) {
|
|
map[voxel_key(coords[(size_t) v * 3], coords[(size_t) v * 3 + 1], coords[(size_t) v * 3 + 2])] = v;
|
|
}
|
|
idx.assign(27, std::vector<int32_t>((size_t) L));
|
|
for (int k = 0; k < 27; ++k) {
|
|
const int d1 = k / 9 - 1, d2 = (k / 3) % 3 - 1, d3 = k % 3 - 1;
|
|
std::vector<int32_t> & ik = idx[k];
|
|
for (int v = 0; v < L; ++v) {
|
|
const int32_t c1 = coords[(size_t) v * 3] + d1;
|
|
const int32_t c2 = coords[(size_t) v * 3 + 1] + d2;
|
|
const int32_t c3 = coords[(size_t) v * 3 + 2] + d3;
|
|
if (c1 < 0 || c2 < 0 || c3 < 0) { ik[v] = L; continue; }
|
|
auto it = map.find(voxel_key(c1, c2, c3));
|
|
ik[v] = (it == map.end()) ? L : it->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Shared driver for the shape decoder. upsample_times < 0 runs the full decode
|
|
// (all levels + output layer; fills out_feats and out_coords) — the validated
|
|
// behavior. upsample_times in [1, n_levels-1] runs only that many subdivision
|
|
// levels and returns the expanded coordinate set in out_coords (out_feats
|
|
// untouched) — mirrors FlexiDualGridVaeDecoder.upsample().
|
|
static bool shape_dec_run(trellis2_shape_dec_model * m,
|
|
const float * slat, int n_voxels, const int32_t * coords_in,
|
|
int upsample_times,
|
|
const std::vector<trellis2_subdiv_level> * guide,
|
|
std::vector<trellis2_subdiv_level> * predicted_subs,
|
|
bool pbr_scale,
|
|
std::vector<float> & out_feats,
|
|
std::vector<int32_t> & out_coords,
|
|
trellis2_shape_dec_taps * taps,
|
|
std::string * error) {
|
|
if (!m) { set_error(error, "null model"); return false; }
|
|
if (!m->has_data) { set_error(error, "model loaded metadata-only; reload with load_tensors=true"); return false; }
|
|
// Texture decoder (pred_subdiv=False): the per-level subdivision is supplied
|
|
// by the integrated shape decoder or standalone shape encoder (`guide`)
|
|
// instead of predicted by a to_subdiv head.
|
|
if (guide && (int) guide->size() < m->hp.n_levels - 1) {
|
|
set_error(error, "guide subdivisions shorter than n_levels-1"); return false;
|
|
}
|
|
|
|
const trellis2_shape_dec_hparams & hp = m->hp;
|
|
const int n_levels = hp.n_levels;
|
|
const float eps = hp.norm_eps;
|
|
const size_t es = sizeof(float);
|
|
|
|
if (upsample_times >= 0 && (upsample_times < 1 || upsample_times > n_levels - 1)) {
|
|
set_error(error, "upsample_times out of range [1, n_levels-1]");
|
|
return false;
|
|
}
|
|
if (predicted_subs) predicted_subs->assign((size_t) std::max(0, n_levels - 1), {});
|
|
|
|
std::string missing;
|
|
|
|
// host-side level state
|
|
std::vector<int32_t> coords(coords_in, coords_in + (size_t) n_voxels * 3);
|
|
int L = n_voxels;
|
|
std::vector<float> feats; // [L * C] voxel-major, current level features
|
|
|
|
auto cap = [&](const std::string & name, const float * data, size_t count) {
|
|
if (!taps) return;
|
|
taps->names.push_back(name);
|
|
taps->data.emplace_back(data, data + count);
|
|
};
|
|
auto cap_coords = [&](const std::string & name) {
|
|
if (!taps) return;
|
|
std::vector<float> c4((size_t) L * 4, 0.0f);
|
|
for (int v = 0; v < L; ++v) {
|
|
c4[(size_t) v * 4 + 1] = (float) coords[(size_t) v * 3];
|
|
c4[(size_t) v * 4 + 2] = (float) coords[(size_t) v * 3 + 1];
|
|
c4[(size_t) v * 4 + 3] = (float) coords[(size_t) v * 3 + 2];
|
|
}
|
|
cap(name, c4.data(), c4.size());
|
|
};
|
|
|
|
// The previous up-block's outputs, already gathered down to the surviving
|
|
// children (see the host_gather at the bottom of the loop): up_hch is the
|
|
// conv1 output [C_next, L_child], up_xch the skip source [C/8, L_child].
|
|
// Pre-gathering here — instead of reading back the full [C_next*8, L] conv
|
|
// output and gathering in the next graph — keeps the finest level's ~8 GB
|
|
// conv output from being duplicated in host RAM.
|
|
std::vector<float> up_hch, up_xch, up_subdiv;
|
|
int prev_C = 0, prev_L = 0;
|
|
|
|
const bool t2_timing = std::getenv("TRELLIS2_TIMING") != nullptr;
|
|
double ms_nbr = 0, ms_graph = 0, ms_gather = 0;
|
|
auto t_now = [] { return std::chrono::steady_clock::now(); };
|
|
auto ms_since = [](std::chrono::steady_clock::time_point a) {
|
|
return std::chrono::duration<double, std::milli>(
|
|
std::chrono::steady_clock::now() - a).count();
|
|
};
|
|
|
|
for (int lvl = 0; lvl < n_levels; ++lvl) {
|
|
const int C = hp.channels[lvl];
|
|
const bool has_up = lvl < n_levels - 1;
|
|
const int C_next = has_up ? hp.channels[lvl + 1] : 0;
|
|
|
|
// ── host: neighbor maps for this level's coords ─────────────────────
|
|
std::vector<std::vector<int32_t>> nidx;
|
|
const bool needs_conv = hp.num_blocks[lvl] > 0 || has_up || lvl > 0;
|
|
if (needs_conv) {
|
|
auto t0 = t_now();
|
|
build_neighbor_indices(coords, L, nidx);
|
|
ms_nbr += ms_since(t0);
|
|
}
|
|
|
|
// ── graph: [child head from previous level] + blocks + up part A ────
|
|
const size_t gsize = 65536;
|
|
const size_t mem = ggml_tensor_overhead() * gsize + ggml_graph_overhead_custom(gsize, false);
|
|
ggml_init_params ip{ mem, nullptr, true };
|
|
ggml_context * ctx = ggml_init(ip);
|
|
ggml_cgraph * gf = ggml_new_graph_custom(ctx, gsize, false);
|
|
|
|
auto W = [&](const std::string & n) -> ggml_tensor * {
|
|
auto it = m->tensors.find(n);
|
|
if (it == m->tensors.end()) { if (missing.empty()) missing = n; return nullptr; }
|
|
return it->second;
|
|
};
|
|
auto lin = [&](ggml_tensor * in, const std::string & pfx) -> ggml_tensor * {
|
|
ggml_tensor * y = ggml_mul_mat(ctx, W(pfx + ".weight"), in);
|
|
ggml_tensor * b = W(pfx + ".bias");
|
|
if (b) y = ggml_add(ctx, y, b);
|
|
return y;
|
|
};
|
|
auto ln_affine = [&](ggml_tensor * h, const std::string & pfx) -> ggml_tensor * {
|
|
ggml_tensor * y = ggml_norm(ctx, h, eps);
|
|
y = ggml_mul(ctx, y, W(pfx + ".weight"));
|
|
y = ggml_add(ctx, y, W(pfx + ".bias"));
|
|
return y;
|
|
};
|
|
|
|
// Per-offset neighbor leaves, shared by every conv in this level. A
|
|
// missing neighbor is handled without an appended zero row (the CUDA
|
|
// CONCAT/PAD kernels abort past 65535 voxels): idx_t[k] holds the
|
|
// neighbor row *clamped* into [0, L) and mask_t[k] is 0 there, 1 for a
|
|
// real neighbor — so get_rows gathers a valid (harmless) row and the
|
|
// mask multiply zeroes the missing contributions. Numerically identical
|
|
// to gathering an explicit zero row, but every op (get_rows, broadcast
|
|
// mul, mul_mat) stays within ggml kernels that tile the voxel dimension,
|
|
// so the decoder runs on the GPU as well as the CPU.
|
|
std::vector<ggml_tensor *> idx_t(27, nullptr), mask_t(27, nullptr);
|
|
if (needs_conv) {
|
|
for (int k = 0; k < 27; ++k) {
|
|
idx_t[k] = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, L);
|
|
mask_t[k] = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, L); // [1, L]
|
|
ggml_set_input(idx_t[k]);
|
|
ggml_set_input(mask_t[k]);
|
|
}
|
|
}
|
|
|
|
// submanifold conv: x [Cin, L] -> [Cout, L]
|
|
auto conv = [&](ggml_tensor * x, const std::string & pfx) -> ggml_tensor * {
|
|
ggml_tensor * w = W(pfx + ".weight"); // ne [Ci, 27, Co]
|
|
ggml_tensor * b = W(pfx + ".bias"); // [Co]
|
|
if (!w || !b) return x;
|
|
const int64_t Ci = w->ne[0], Co = w->ne[2];
|
|
ggml_tensor * acc = nullptr;
|
|
for (int k = 0; k < 27; ++k) {
|
|
ggml_tensor * wk = ggml_cont(ctx, ggml_view_3d(ctx, w, Ci, 1, Co,
|
|
w->nb[1], w->nb[2], (size_t) k * w->nb[1]));
|
|
wk = ggml_reshape_2d(ctx, wk, Ci, Co);
|
|
ggml_tensor * g = ggml_get_rows(ctx, x, idx_t[k]); // [Ci, L]
|
|
g = ggml_mul(ctx, g, mask_t[k]); // zero missing (broadcast [1,L])
|
|
ggml_tensor * y = ggml_mul_mat(ctx, wk, g); // [Co, L]
|
|
acc = acc ? ggml_add(ctx, acc, y) : y;
|
|
}
|
|
return ggml_add(ctx, acc, b);
|
|
};
|
|
|
|
// ConvNeXt block: x + mlp(LN(conv(x)))
|
|
auto convnext = [&](ggml_tensor * x, const std::string & pfx) -> ggml_tensor * {
|
|
ggml_tensor * h = conv(x, pfx + ".conv");
|
|
h = ln_affine(h, pfx + ".norm");
|
|
h = lin(h, pfx + ".mlp.0");
|
|
h = ggml_silu(ctx, h);
|
|
h = lin(h, pfx + ".mlp.2");
|
|
return ggml_add(ctx, h, x);
|
|
};
|
|
|
|
ggml_tensor * h = nullptr;
|
|
ggml_tensor * in_a = nullptr, * in_hch = nullptr, * in_xch = nullptr;
|
|
|
|
if (lvl == 0) {
|
|
// from_latent on the input slat
|
|
in_a = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, hp.latent_channels, L);
|
|
ggml_set_input(in_a);
|
|
h = lin(in_a, "from_latent");
|
|
} else {
|
|
// child head of the previous level's up-block. The surviving
|
|
// children were already gathered on host (up_hch/up_xch), so we
|
|
// receive hch [C, L] and xch [prev_C/8, L] directly — no on-device
|
|
// get_rows over the full [C*8, prev_L]:
|
|
// h = conv2(silu(LN_free(hch))) + repeat_interleave(xch)
|
|
const std::string up = "blocks." + std::to_string(lvl - 1) + "." +
|
|
std::to_string(hp.num_blocks[lvl - 1]);
|
|
in_hch = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, C, L); // hch
|
|
in_xch = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, prev_C / 8, L); // xch
|
|
ggml_set_input(in_hch);
|
|
ggml_set_input(in_xch);
|
|
|
|
const int r = C / (prev_C / 8); // repeat_interleave factor
|
|
ggml_tensor * skip = ggml_reshape_3d(ctx, in_xch, 1, prev_C / 8, L);
|
|
skip = ggml_repeat(ctx, skip, ggml_new_tensor_3d(ctx, GGML_TYPE_F32, r, prev_C / 8, L));
|
|
skip = ggml_reshape_2d(ctx, skip, C, L);
|
|
|
|
ggml_tensor * hn = ggml_norm(ctx, in_hch, eps); // norm2, affine-free
|
|
hn = ggml_silu(ctx, hn);
|
|
hn = conv(hn, up + ".conv2");
|
|
h = ggml_add(ctx, hn, skip);
|
|
}
|
|
|
|
for (int b = 0; b < hp.num_blocks[lvl]; ++b) {
|
|
const std::string pfx = "blocks." + std::to_string(lvl) + "." + std::to_string(b);
|
|
h = convnext(h, pfx);
|
|
}
|
|
|
|
std::vector<std::pair<std::string, ggml_tensor *>> outs;
|
|
if (has_up) {
|
|
const std::string up = "blocks." + std::to_string(lvl) + "." +
|
|
std::to_string(hp.num_blocks[lvl]);
|
|
if (!guide) { // shape decoder: predict which children exist
|
|
ggml_tensor * subdiv = lin(h, up + ".to_subdiv"); // [8, L]
|
|
outs.emplace_back("subdiv", ggml_cont(ctx, subdiv));
|
|
}
|
|
ggml_tensor * hn = ln_affine(h, up + ".norm1");
|
|
hn = ggml_silu(ctx, hn);
|
|
ggml_tensor * h1 = conv(hn, up + ".conv1"); // [C_next*8, L]
|
|
outs.emplace_back("h1", ggml_cont(ctx, h1));
|
|
outs.emplace_back("x", ggml_cont(ctx, h));
|
|
} else {
|
|
// final: affine-free LN (eps 1e-5) + output projection
|
|
ggml_tensor * hn = ggml_norm(ctx, h, 1e-5f);
|
|
ggml_tensor * o = lin(hn, "output_layer"); // [7, L]
|
|
outs.emplace_back("out", ggml_cont(ctx, o));
|
|
}
|
|
if (taps) outs.emplace_back("pre_up", ggml_cont(ctx, h));
|
|
|
|
for (auto & o : outs) {
|
|
ggml_set_output(o.second);
|
|
ggml_build_forward_expand(gf, o.second);
|
|
}
|
|
|
|
if (!missing.empty()) {
|
|
set_error(error, "missing tensor: " + missing + " (level " + std::to_string(lvl) + ")");
|
|
ggml_free(ctx);
|
|
return false;
|
|
}
|
|
|
|
ggml_gallocr_t alloc = ggml_gallocr_new(ggml_backend_get_default_buffer_type(m->backend));
|
|
if (!ggml_gallocr_alloc_graph(alloc, gf)) {
|
|
set_error(error, "ggml_gallocr_alloc_graph failed (level " + std::to_string(lvl) + ")");
|
|
ggml_gallocr_free(alloc); ggml_free(ctx);
|
|
return false;
|
|
}
|
|
|
|
// upload inputs: clamp the missing-neighbor sentinel (L) into range and
|
|
// build its 0/1 mask (see the conv leaves above).
|
|
if (needs_conv) {
|
|
std::vector<int32_t> clamped((size_t) L);
|
|
std::vector<float> mask((size_t) L);
|
|
for (int k = 0; k < 27; ++k) {
|
|
const std::vector<int32_t> & ik = nidx[k];
|
|
for (int v = 0; v < L; ++v) {
|
|
const bool miss = ik[(size_t) v] >= L;
|
|
clamped[(size_t) v] = miss ? 0 : ik[(size_t) v];
|
|
mask[(size_t) v] = miss ? 0.0f : 1.0f;
|
|
}
|
|
ggml_backend_tensor_set(idx_t[k], clamped.data(), 0, (size_t) L * sizeof(int32_t));
|
|
ggml_backend_tensor_set(mask_t[k], mask.data(), 0, (size_t) L * sizeof(float));
|
|
}
|
|
}
|
|
if (lvl == 0) {
|
|
ggml_backend_tensor_set(in_a, slat, 0, (size_t) hp.latent_channels * L * es);
|
|
} else {
|
|
// pre-gathered by the previous level (host_gather below)
|
|
ggml_backend_tensor_set(in_hch, up_hch.data(), 0, up_hch.size() * es);
|
|
ggml_backend_tensor_set(in_xch, up_xch.data(), 0, up_xch.size() * es);
|
|
}
|
|
|
|
auto t_g = t_now();
|
|
const ggml_status st = ggml_backend_graph_compute(m->backend, gf);
|
|
if (t2_timing) ms_graph += ms_since(t_g);
|
|
if (st != GGML_STATUS_SUCCESS) {
|
|
set_error(error, "graph compute failed (level " + std::to_string(lvl) + ")");
|
|
ggml_gallocr_free(alloc); ggml_free(ctx);
|
|
return false;
|
|
}
|
|
|
|
// taps + read-back
|
|
if (taps) {
|
|
cap_coords("lvl" + std::to_string(lvl) + ".in_coords");
|
|
}
|
|
for (auto & o : outs) {
|
|
if (o.first == "pre_up") {
|
|
std::vector<float> buf((size_t) ggml_nelements(o.second));
|
|
ggml_backend_tensor_get(o.second, buf.data(), 0, buf.size() * es);
|
|
cap("lvl" + std::to_string(lvl) + ".pre_up", buf.data(), buf.size());
|
|
}
|
|
}
|
|
|
|
if (has_up) {
|
|
ggml_tensor * h1_o = nullptr, * x_o = nullptr, * subdiv_o = nullptr;
|
|
for (auto & o : outs) {
|
|
if (o.first == "subdiv") subdiv_o = o.second;
|
|
if (o.first == "h1") h1_o = o.second;
|
|
if (o.first == "x") x_o = o.second;
|
|
}
|
|
if (!guide) { // shape decoder: read the predicted subdivision logits
|
|
up_subdiv.resize((size_t) 8 * L);
|
|
ggml_backend_tensor_get(subdiv_o, up_subdiv.data(), 0, up_subdiv.size() * es);
|
|
if (taps) cap("lvl" + std::to_string(lvl) + ".subdiv", up_subdiv.data(), up_subdiv.size());
|
|
}
|
|
|
|
// The surviving children + their gather index (child slot o + 8*parent,
|
|
// in [0, 8L)). Shape decoder: expand from predicted subdivision logits.
|
|
// Texture decoder: replay the encoder's recorded subdivision (`guide`),
|
|
// which also reproduces the encoder's exact input voxel order.
|
|
std::vector<int32_t> child_coords, cidx;
|
|
if (guide) {
|
|
const trellis2_subdiv_level & g = (*guide)[lvl];
|
|
child_coords = g.fine_coords;
|
|
cidx = g.cidx;
|
|
} else {
|
|
child_coords.reserve((size_t) L * 3);
|
|
cidx.reserve((size_t) L);
|
|
for (int v = 0; v < L; ++v) {
|
|
for (int o = 0; o < 8; ++o) {
|
|
if (up_subdiv[(size_t) v * 8 + o] > 0.0f) {
|
|
cidx.push_back(o + 8 * v);
|
|
child_coords.push_back(2 * coords[(size_t) v * 3] + (o & 1));
|
|
child_coords.push_back(2 * coords[(size_t) v * 3 + 1] + ((o >> 1) & 1));
|
|
child_coords.push_back(2 * coords[(size_t) v * 3 + 2] + ((o >> 2) & 1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (predicted_subs) {
|
|
trellis2_subdiv_level & sub = (*predicted_subs)[(size_t) lvl];
|
|
sub.fine_coords = child_coords;
|
|
sub.cidx = cidx;
|
|
}
|
|
const int L_child = (int) cidx.size();
|
|
if (L_child == 0) {
|
|
set_error(error, "no children at level " + std::to_string(lvl));
|
|
ggml_gallocr_free(alloc); ggml_free(ctx);
|
|
return false;
|
|
}
|
|
|
|
// Gather the surviving children of h1 [C_next*8, L] (viewed as
|
|
// [C_next, 8L]) and x [C, L] (viewed as [C/8, 8L]) — byte-identical
|
|
// to get_rows(cidx) on the reshaped tensors. On the CPU backend the
|
|
// conv output is already in host memory, so we read it in place
|
|
// instead of duplicating the full [C_next*8, L] (~8 GB at 1024^3).
|
|
auto host_gather = [&](ggml_tensor * t, int chans, std::vector<float> & out) {
|
|
out.resize((size_t) chans * L_child);
|
|
if (t->buffer && ggml_backend_buffer_is_host(t->buffer)) {
|
|
const float * d = (const float *) t->data;
|
|
for (int j = 0; j < L_child; ++j)
|
|
std::memcpy(out.data() + (size_t) j * chans,
|
|
d + (size_t) cidx[(size_t) j] * chans, (size_t) chans * es);
|
|
} else {
|
|
std::vector<float> full((size_t) ggml_nelements(t));
|
|
ggml_backend_tensor_get(t, full.data(), 0, full.size() * es);
|
|
for (int j = 0; j < L_child; ++j)
|
|
std::memcpy(out.data() + (size_t) j * chans,
|
|
full.data() + (size_t) cidx[(size_t) j] * chans, (size_t) chans * es);
|
|
}
|
|
};
|
|
auto t_gh = t_now();
|
|
host_gather(h1_o, C_next, up_hch); // [C_next, L_child]
|
|
host_gather(x_o, C / 8, up_xch); // [C/8, L_child]
|
|
if (t2_timing) ms_gather += ms_since(t_gh);
|
|
|
|
prev_C = C;
|
|
prev_L = L;
|
|
coords = std::move(child_coords);
|
|
L = L_child;
|
|
} else {
|
|
out_feats.resize((size_t) hp.out_channels * L);
|
|
for (auto & o : outs) {
|
|
if (o.first == "out") {
|
|
ggml_backend_tensor_get(o.second, out_feats.data(), 0, out_feats.size() * es);
|
|
}
|
|
}
|
|
if (pbr_scale) // tex decoder: map to [0,1] like the reference *0.5+0.5
|
|
for (float & f : out_feats) f = f * 0.5f + 0.5f;
|
|
out_coords = coords;
|
|
if (taps) {
|
|
cap(pbr_scale ? "pbr" : "out7", out_feats.data(), out_feats.size());
|
|
cap_coords("out_coords");
|
|
}
|
|
}
|
|
|
|
ggml_gallocr_free(alloc);
|
|
ggml_free(ctx);
|
|
|
|
// upsample-to-level-N: at this point `coords` holds the child set this
|
|
// level's up-block produced (has_up is always true for lvl < n_levels-1,
|
|
// which upsample_times-1 always is), so return it and skip the rest.
|
|
if (upsample_times >= 0 && lvl == upsample_times - 1) {
|
|
out_coords = coords;
|
|
if (t2_timing)
|
|
std::fprintf(stderr, "[shape_dec] nbr=%.0f graph=%.0f gather=%.0f ms\n",
|
|
ms_nbr, ms_graph, ms_gather);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (t2_timing)
|
|
std::fprintf(stderr, "[shape_dec] nbr=%.0f graph=%.0f gather=%.0f ms\n",
|
|
ms_nbr, ms_graph, ms_gather);
|
|
return true;
|
|
}
|
|
|
|
bool trellis2_shape_dec_decode(trellis2_shape_dec_model * m,
|
|
const float * slat, int n_voxels, const int32_t * coords_in,
|
|
std::vector<float> & out_feats,
|
|
std::vector<int32_t> & out_coords,
|
|
trellis2_shape_dec_taps * taps,
|
|
std::string * error) {
|
|
return shape_dec_run(m, slat, n_voxels, coords_in, /*upsample_times*/ -1,
|
|
/*guide*/ nullptr, /*predicted_subs*/ nullptr, /*pbr_scale*/ false,
|
|
out_feats, out_coords, taps, error);
|
|
}
|
|
|
|
bool trellis2_shape_dec_decode_with_subs(trellis2_shape_dec_model * m,
|
|
const float * slat, int n_voxels, const int32_t * coords_in,
|
|
std::vector<float> & out_feats,
|
|
std::vector<int32_t> & out_coords,
|
|
std::vector<trellis2_subdiv_level> & out_subs,
|
|
trellis2_shape_dec_taps * taps,
|
|
std::string * error) {
|
|
return shape_dec_run(m, slat, n_voxels, coords_in, /*upsample_times*/ -1,
|
|
/*guide*/ nullptr, &out_subs, /*pbr_scale*/ false,
|
|
out_feats, out_coords, taps, error);
|
|
}
|
|
|
|
bool trellis2_shape_dec_upsample(trellis2_shape_dec_model * m,
|
|
const float * slat, int n_voxels, const int32_t * coords,
|
|
int upsample_times,
|
|
std::vector<int32_t> & out_coords,
|
|
std::string * error) {
|
|
std::vector<float> unused;
|
|
return shape_dec_run(m, slat, n_voxels, coords, upsample_times,
|
|
/*guide*/ nullptr, /*predicted_subs*/ nullptr, /*pbr_scale*/ false,
|
|
unused, out_coords, /*taps*/ nullptr, error);
|
|
}
|
|
|
|
// Texture decoder: the shape-decoder driver with the supplied shape subdivision
|
|
// replayed (guide) and the PBR [0,1] output scale.
|
|
bool trellis2_tex_dec_decode(trellis2_shape_dec_model * m,
|
|
const float * slat, int n_voxels, const int32_t * coords,
|
|
const std::vector<trellis2_subdiv_level> & subs,
|
|
std::vector<float> & out_feats,
|
|
std::vector<int32_t> & out_coords,
|
|
std::string * error) {
|
|
return shape_dec_run(m, slat, n_voxels, coords, /*upsample_times*/ -1,
|
|
&subs, /*predicted_subs*/ nullptr, /*pbr_scale*/ true,
|
|
out_feats, out_coords, /*taps*/ nullptr, error);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** Shape-SLAT VAE encoder (FlexiDualGridVaeEncoder) — the mirror of the shape
|
|
** decoder. The 6-channel dual grid at resolution R is downsampled 16x through
|
|
** SparseSpatial2Channel (S2C) blocks into the 32-channel shape SLAT at R/16.
|
|
** Unlike the decoder, an S2C step needs no learned decision — the parent set is
|
|
** determined by the coordinates alone — so a whole level (ConvNeXt blocks + the
|
|
** down-block's fine conv1, the S2C gather, and the coarse conv2) runs as one
|
|
** graph. The per-level subdivision (which fine child maps to which coarse
|
|
** parent) is recorded for the texture decoder to replay.
|
|
*****************************************************************************/
|
|
|
|
struct trellis2_shape_enc_model {
|
|
gguf_context * gguf = nullptr;
|
|
ggml_context * ctx = nullptr;
|
|
trellis2_shape_enc_hparams hp;
|
|
bool has_data = false;
|
|
|
|
ggml_backend_t backend = nullptr;
|
|
ggml_backend_buffer_t weights_buf = nullptr;
|
|
std::string backend_name;
|
|
|
|
std::unordered_map<std::string, ggml_tensor *> tensors;
|
|
};
|
|
|
|
trellis2_shape_enc_model *
|
|
trellis2_shape_enc_load(const std::string & path, bool load_tensors, std::string * error,
|
|
const char * device) {
|
|
auto * m = new trellis2_shape_enc_model();
|
|
|
|
gguf_init_params params;
|
|
params.no_alloc = true;
|
|
params.ctx = &m->ctx;
|
|
m->gguf = gguf_init_from_file(path.c_str(), params);
|
|
if (!m->gguf) {
|
|
set_error(error, "gguf_init_from_file failed (not a GGUF file?): " + path);
|
|
delete m; return nullptr;
|
|
}
|
|
|
|
const char * arch = kv_str(m->gguf, "general.architecture", "");
|
|
if (std::strcmp(arch, "trellis2-shape-enc") != 0) {
|
|
set_error(error, std::string("unexpected architecture '") + arch +
|
|
"' (expected 'trellis2-shape-enc')");
|
|
trellis2_shape_enc_free(m); return nullptr;
|
|
}
|
|
|
|
trellis2_shape_enc_hparams & hp = m->hp;
|
|
const char * P = "trellis2.shape_enc.";
|
|
auto K = [&](const std::string & s) { return std::string(P) + s; };
|
|
hp.in_channels = (int32_t) kv_u32(m->gguf, K("in_channels").c_str(), 6);
|
|
hp.latent_channels = (int32_t) kv_u32(m->gguf, K("latent_channels").c_str(), 0);
|
|
hp.n_levels = (int32_t) kv_u32(m->gguf, K("n_levels").c_str(), 0);
|
|
hp.norm_eps = kv_f32(m->gguf, K("norm_eps").c_str(), 1e-6f);
|
|
hp.file_type = (int32_t) kv_u32(m->gguf, "general.file_type", 0);
|
|
for (int i = 0; i < hp.n_levels && i < 8; ++i) {
|
|
hp.channels[i] = (int32_t) kv_u32(m->gguf, K("channels." + std::to_string(i)).c_str(), 0);
|
|
hp.num_blocks[i] = (int32_t) kv_u32(m->gguf, K("num_blocks." + std::to_string(i)).c_str(), 0);
|
|
}
|
|
|
|
for (ggml_tensor * t = ggml_get_first_tensor(m->ctx); t != nullptr;
|
|
t = ggml_get_next_tensor(m->ctx, t)) {
|
|
m->tensors[t->name] = t;
|
|
}
|
|
|
|
if (load_tensors) {
|
|
m->backend = init_best_backend(m->backend_name, device);
|
|
m->weights_buf = ggml_backend_alloc_ctx_tensors(m->ctx, m->backend);
|
|
if (!m->weights_buf) {
|
|
set_error(error, "failed to allocate weights on backend " + m->backend_name);
|
|
trellis2_shape_enc_free(m); return nullptr;
|
|
}
|
|
std::ifstream fin(path, std::ios::binary);
|
|
if (!fin) { set_error(error, "cannot reopen file for weight data: " + path); trellis2_shape_enc_free(m); return nullptr; }
|
|
const size_t data_off = gguf_get_data_offset(m->gguf);
|
|
const int64_t nt = gguf_get_n_tensors(m->gguf);
|
|
std::vector<uint8_t> buf;
|
|
for (int64_t i = 0; i < nt; ++i) {
|
|
const char * name = gguf_get_tensor_name(m->gguf, i);
|
|
ggml_tensor * t = m->tensors[name];
|
|
const size_t nb = ggml_nbytes(t);
|
|
const size_t off = data_off + gguf_get_tensor_offset(m->gguf, i);
|
|
buf.resize(nb);
|
|
fin.seekg((std::streamoff) off, std::ios::beg);
|
|
if (!fin.read(reinterpret_cast<char *>(buf.data()), (std::streamsize) nb)) {
|
|
set_error(error, std::string("failed reading weight '") + name + "' from file");
|
|
trellis2_shape_enc_free(m); return nullptr;
|
|
}
|
|
ggml_backend_tensor_set(t, buf.data(), 0, nb);
|
|
}
|
|
m->has_data = true;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
void trellis2_shape_enc_free(trellis2_shape_enc_model * m) {
|
|
if (!m) return;
|
|
if (m->weights_buf) ggml_backend_buffer_free(m->weights_buf);
|
|
if (m->backend) ggml_backend_free(m->backend);
|
|
if (m->gguf) gguf_free(m->gguf);
|
|
if (m->ctx) ggml_free(m->ctx);
|
|
delete m;
|
|
}
|
|
|
|
const char * trellis2_shape_enc_backend_name(const trellis2_shape_enc_model * m) {
|
|
return (m && !m->backend_name.empty()) ? m->backend_name.c_str() : "none";
|
|
}
|
|
|
|
const trellis2_shape_enc_hparams &
|
|
trellis2_shape_enc_hparams_of(const trellis2_shape_enc_model * m) { return m->hp; }
|
|
|
|
bool trellis2_shape_enc_encode(trellis2_shape_enc_model * m,
|
|
const float * in6, int n_voxels, const int32_t * coords_in,
|
|
std::vector<float> & out_slat,
|
|
std::vector<int32_t> & out_coords,
|
|
std::vector<trellis2_subdiv_level> & out_subs,
|
|
trellis2_shape_dec_taps * taps,
|
|
std::string * error) {
|
|
if (!m) { set_error(error, "null model"); return false; }
|
|
if (!m->has_data) { set_error(error, "model loaded metadata-only; reload with load_tensors=true"); return false; }
|
|
|
|
const trellis2_shape_enc_hparams & hp = m->hp;
|
|
const int n_levels = hp.n_levels;
|
|
const float eps = hp.norm_eps;
|
|
const size_t es = sizeof(float);
|
|
std::string missing;
|
|
|
|
// host-side level state
|
|
std::vector<int32_t> coords(coords_in, coords_in + (size_t) n_voxels * 3);
|
|
int L = n_voxels;
|
|
std::vector<float> feats; // [L * C] voxel-major, current level features
|
|
|
|
// input to the network: (dual-vertex offset, intersected) - 0.5
|
|
std::vector<float> in_shift((size_t) L * hp.in_channels);
|
|
for (size_t i = 0; i < in_shift.size(); ++i) in_shift[i] = in6[i] - 0.5f;
|
|
|
|
out_subs.assign(std::max(0, n_levels - 1), {});
|
|
|
|
auto cap = [&](const std::string & name, const float * d, size_t n) {
|
|
if (!taps) return;
|
|
taps->names.push_back(name); taps->data.emplace_back(d, d + n);
|
|
};
|
|
|
|
for (int lvl = 0; lvl < n_levels; ++lvl) {
|
|
const int C_in = hp.channels[lvl];
|
|
const bool has_down = lvl < n_levels - 1;
|
|
const int C_out = has_down ? hp.channels[lvl + 1] : 0;
|
|
|
|
// ── host: fine neighbor maps (ConvNeXt + down conv1) ────────────────
|
|
std::vector<std::vector<int32_t>> nfine;
|
|
build_neighbor_indices(coords, L, nfine);
|
|
|
|
// ── host: S2C child map + coarse coords + coarse neighbor maps ──────
|
|
// coarse parent = fine // 2, subidx = bit-packed (x&1,y&1,z&1). Coarse
|
|
// coords are lexicographically sorted to match the reference's unique().
|
|
std::vector<int32_t> coarse_coords; // [Lc*3]
|
|
std::vector<int32_t> childidx; // [Lc*8] fine row of child (or -1)
|
|
std::vector<int32_t> cidx; // [L] 8*parent_row + subidx (decoder replay)
|
|
int Lc = 0;
|
|
std::vector<std::vector<int32_t>> ncoarse;
|
|
if (has_down) {
|
|
std::unordered_map<uint64_t, int32_t> fine_map;
|
|
fine_map.reserve((size_t) L * 2);
|
|
for (int v = 0; v < L; ++v)
|
|
fine_map[voxel_key(coords[(size_t) v*3], coords[(size_t) v*3+1], coords[(size_t) v*3+2])] = v;
|
|
// unique parents
|
|
std::unordered_map<uint64_t, int32_t> parent_seen;
|
|
parent_seen.reserve((size_t) L);
|
|
std::vector<std::array<int32_t,3>> parents;
|
|
for (int v = 0; v < L; ++v) {
|
|
const int32_t px = coords[(size_t) v*3] >> 1, // // 2 (coords >= 0)
|
|
py = coords[(size_t) v*3+1] >> 1,
|
|
pz = coords[(size_t) v*3+2] >> 1;
|
|
const uint64_t pk = voxel_key(px, py, pz);
|
|
if (parent_seen.emplace(pk, 0).second) parents.push_back({px, py, pz});
|
|
}
|
|
std::sort(parents.begin(), parents.end()); // lexicographic (x,y,z)
|
|
Lc = (int) parents.size();
|
|
coarse_coords.resize((size_t) Lc * 3);
|
|
std::unordered_map<uint64_t, int32_t> coarse_map;
|
|
coarse_map.reserve((size_t) Lc * 2);
|
|
for (int p = 0; p < Lc; ++p) {
|
|
coarse_coords[(size_t) p*3] = parents[p][0];
|
|
coarse_coords[(size_t) p*3+1] = parents[p][1];
|
|
coarse_coords[(size_t) p*3+2] = parents[p][2];
|
|
coarse_map[voxel_key(parents[p][0], parents[p][1], parents[p][2])] = p;
|
|
}
|
|
// childidx[pr*8+o] = fine row of child (or -1 sentinel for missing)
|
|
childidx.assign((size_t) Lc * 8, -1);
|
|
for (int p = 0; p < Lc; ++p) {
|
|
for (int o = 0; o < 8; ++o) {
|
|
const int32_t cx = 2*parents[p][0] + (o & 1),
|
|
cy = 2*parents[p][1] + ((o >> 1) & 1),
|
|
cz = 2*parents[p][2] + ((o >> 2) & 1);
|
|
auto it = fine_map.find(voxel_key(cx, cy, cz));
|
|
if (it != fine_map.end()) childidx[(size_t) p*8 + o] = it->second;
|
|
}
|
|
}
|
|
// decoder replay: for each fine voxel, its coarse parent row + subidx
|
|
cidx.resize((size_t) L);
|
|
for (int v = 0; v < L; ++v) {
|
|
const int32_t px = coords[(size_t) v*3] >> 1,
|
|
py = coords[(size_t) v*3+1] >> 1,
|
|
pz = coords[(size_t) v*3+2] >> 1;
|
|
const int o = (coords[(size_t) v*3] & 1)
|
|
| ((coords[(size_t) v*3+1] & 1) << 1)
|
|
| ((coords[(size_t) v*3+2] & 1) << 2);
|
|
cidx[(size_t) v] = 8 * coarse_map[voxel_key(px, py, pz)] + o;
|
|
}
|
|
build_neighbor_indices(coarse_coords, Lc, ncoarse);
|
|
}
|
|
|
|
// ── graph ───────────────────────────────────────────────────────────
|
|
const size_t gsize = 65536;
|
|
const size_t mem = ggml_tensor_overhead() * gsize + ggml_graph_overhead_custom(gsize, false);
|
|
ggml_init_params ip{ mem, nullptr, true };
|
|
ggml_context * ctx = ggml_init(ip);
|
|
ggml_cgraph * gf = ggml_new_graph_custom(ctx, gsize, false);
|
|
|
|
auto W = [&](const std::string & n) -> ggml_tensor * {
|
|
auto it = m->tensors.find(n);
|
|
if (it == m->tensors.end()) { if (missing.empty()) missing = n; return nullptr; }
|
|
return it->second;
|
|
};
|
|
auto lin = [&](ggml_tensor * in, const std::string & pfx) -> ggml_tensor * {
|
|
ggml_tensor * y = ggml_mul_mat(ctx, W(pfx + ".weight"), in);
|
|
ggml_tensor * b = W(pfx + ".bias");
|
|
if (b) y = ggml_add(ctx, y, b);
|
|
return y;
|
|
};
|
|
auto ln_affine = [&](ggml_tensor * h, const std::string & pfx) -> ggml_tensor * {
|
|
ggml_tensor * y = ggml_norm(ctx, h, eps);
|
|
y = ggml_mul(ctx, y, W(pfx + ".weight"));
|
|
y = ggml_add(ctx, y, W(pfx + ".bias"));
|
|
return y;
|
|
};
|
|
|
|
// 27 fine + 27 coarse neighbor leaves
|
|
std::vector<ggml_tensor *> idx_f(27), mask_f(27), idx_c(27), mask_c(27);
|
|
for (int k = 0; k < 27; ++k) {
|
|
idx_f[k] = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, L);
|
|
mask_f[k] = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, L);
|
|
ggml_set_input(idx_f[k]); ggml_set_input(mask_f[k]);
|
|
}
|
|
if (has_down) {
|
|
for (int k = 0; k < 27; ++k) {
|
|
idx_c[k] = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, Lc);
|
|
mask_c[k] = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, Lc);
|
|
ggml_set_input(idx_c[k]); ggml_set_input(mask_c[k]);
|
|
}
|
|
}
|
|
auto conv = [&](ggml_tensor * x, const std::string & pfx,
|
|
std::vector<ggml_tensor *> & idxs, std::vector<ggml_tensor *> & masks) -> ggml_tensor * {
|
|
ggml_tensor * w = W(pfx + ".weight"); // ne [Ci, 27, Co]
|
|
ggml_tensor * b = W(pfx + ".bias");
|
|
if (!w || !b) return x;
|
|
const int64_t Ci = w->ne[0], Co = w->ne[2];
|
|
ggml_tensor * acc = nullptr;
|
|
for (int k = 0; k < 27; ++k) {
|
|
ggml_tensor * wk = ggml_cont(ctx, ggml_view_3d(ctx, w, Ci, 1, Co, w->nb[1], w->nb[2], (size_t) k*w->nb[1]));
|
|
wk = ggml_reshape_2d(ctx, wk, Ci, Co);
|
|
ggml_tensor * g = ggml_get_rows(ctx, x, idxs[k]);
|
|
g = ggml_mul(ctx, g, masks[k]);
|
|
ggml_tensor * y = ggml_mul_mat(ctx, wk, g);
|
|
acc = acc ? ggml_add(ctx, acc, y) : y;
|
|
}
|
|
return ggml_add(ctx, acc, b);
|
|
};
|
|
auto convnext = [&](ggml_tensor * x, const std::string & pfx) -> ggml_tensor * {
|
|
ggml_tensor * h = conv(x, pfx + ".conv", idx_f, mask_f);
|
|
h = ln_affine(h, pfx + ".norm");
|
|
h = lin(h, pfx + ".mlp.0");
|
|
h = ggml_silu(ctx, h);
|
|
h = lin(h, pfx + ".mlp.2");
|
|
return ggml_add(ctx, h, x);
|
|
};
|
|
|
|
// input for this level: 6-channel dual grid at lvl 0 (through input_layer),
|
|
// else the previous level's [C_in, L] features.
|
|
const int in_dim = (lvl == 0) ? hp.in_channels : C_in;
|
|
ggml_tensor * in_a = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, in_dim, L);
|
|
ggml_set_input(in_a);
|
|
ggml_tensor * h = (lvl == 0) ? lin(in_a, "input_layer") : in_a;
|
|
|
|
for (int b = 0; b < hp.num_blocks[lvl]; ++b)
|
|
h = convnext(h, "blocks." + std::to_string(lvl) + "." + std::to_string(b));
|
|
|
|
// S2C child-gather leaves (shared by the h1 and skip-x gathers)
|
|
ggml_tensor * cidx_t = nullptr, * cmask_t = nullptr;
|
|
ggml_tensor * out_h = nullptr;
|
|
if (has_down) {
|
|
const std::string down = "blocks." + std::to_string(lvl) + "." + std::to_string(hp.num_blocks[lvl]);
|
|
cidx_t = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, (int64_t) Lc * 8);
|
|
cmask_t = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, (int64_t) Lc * 8);
|
|
ggml_set_input(cidx_t); ggml_set_input(cmask_t);
|
|
|
|
ggml_tensor * hn1 = ggml_silu(ctx, ln_affine(h, down + ".norm1"));
|
|
ggml_tensor * h1 = conv(hn1, down + ".conv1", idx_f, mask_f); // [C_out/8, L]
|
|
// S2C gather: [ch, L] -> [ch, Lc*8] (masked) -> reshape [ch*8, Lc]
|
|
auto s2c = [&](ggml_tensor * t, int ch) -> ggml_tensor * {
|
|
ggml_tensor * g = ggml_get_rows(ctx, t, cidx_t); // [ch, Lc*8]
|
|
g = ggml_mul(ctx, g, cmask_t);
|
|
return ggml_reshape_2d(ctx, ggml_cont(ctx, g), (int64_t) ch * 8, Lc);
|
|
};
|
|
ggml_tensor * h1c = s2c(h1, C_out / 8); // [C_out, Lc]
|
|
ggml_tensor * xc = s2c(h, C_in); // [C_in*8, Lc]
|
|
ggml_tensor * hn2 = ggml_silu(ctx, ggml_norm(ctx, h1c, eps)); // norm2 affine-free
|
|
ggml_tensor * h2 = conv(hn2, down + ".conv2", idx_c, mask_c); // [C_out, Lc]
|
|
// skip: mean over the (C_in*8 / C_out) group of xc
|
|
const int gsz = (C_in * 8) / C_out;
|
|
ggml_tensor * skip = ggml_reshape_3d(ctx, xc, gsz, C_out, Lc);
|
|
skip = ggml_reshape_2d(ctx, ggml_cont(ctx, ggml_mean(ctx, skip)), C_out, Lc);
|
|
out_h = ggml_add(ctx, h2, skip); // [C_out, Lc]
|
|
} else {
|
|
ggml_tensor * hn = ggml_norm(ctx, h, 1e-5f); // F.layer_norm affine-free
|
|
ggml_tensor * z = lin(hn, "to_latent"); // [2*latent, L]
|
|
// take the mean half (posterior mean): channels [0, latent)
|
|
out_h = ggml_cont(ctx, ggml_view_2d(ctx, z, hp.latent_channels, L, z->nb[1], 0));
|
|
}
|
|
out_h = ggml_cont(ctx, out_h);
|
|
ggml_set_output(out_h);
|
|
ggml_build_forward_expand(gf, out_h);
|
|
|
|
if (!missing.empty()) {
|
|
set_error(error, "missing tensor: " + missing + " (level " + std::to_string(lvl) + ")");
|
|
ggml_free(ctx); return false;
|
|
}
|
|
|
|
ggml_gallocr_t alloc = ggml_gallocr_new(ggml_backend_get_default_buffer_type(m->backend));
|
|
if (!ggml_gallocr_alloc_graph(alloc, gf)) {
|
|
set_error(error, "ggml_gallocr_alloc_graph failed (enc level " + std::to_string(lvl) + ")");
|
|
ggml_gallocr_free(alloc); ggml_free(ctx); return false;
|
|
}
|
|
|
|
// upload neighbor leaves (clamp missing to row 0, mask 0)
|
|
auto upload_nbr = [&](std::vector<std::vector<int32_t>> & nb, int Ln,
|
|
std::vector<ggml_tensor *> & idxs, std::vector<ggml_tensor *> & masks) {
|
|
std::vector<int32_t> cl((size_t) Ln); std::vector<float> mk((size_t) Ln);
|
|
for (int k = 0; k < 27; ++k) {
|
|
for (int v = 0; v < Ln; ++v) {
|
|
const bool miss = nb[k][(size_t) v] >= Ln;
|
|
cl[(size_t) v] = miss ? 0 : nb[k][(size_t) v];
|
|
mk[(size_t) v] = miss ? 0.0f : 1.0f;
|
|
}
|
|
ggml_backend_tensor_set(idxs[k], cl.data(), 0, (size_t) Ln * sizeof(int32_t));
|
|
ggml_backend_tensor_set(masks[k], mk.data(), 0, (size_t) Ln * sizeof(float));
|
|
}
|
|
};
|
|
upload_nbr(nfine, L, idx_f, mask_f);
|
|
if (lvl == 0) ggml_backend_tensor_set(in_a, in_shift.data(), 0, in_shift.size() * es);
|
|
else ggml_backend_tensor_set(in_a, feats.data(), 0, feats.size() * es);
|
|
if (has_down) {
|
|
upload_nbr(ncoarse, Lc, idx_c, mask_c);
|
|
std::vector<int32_t> cl((size_t) Lc * 8); std::vector<float> mk((size_t) Lc * 8);
|
|
for (size_t i = 0; i < cl.size(); ++i) {
|
|
const bool miss = childidx[i] < 0;
|
|
cl[i] = miss ? 0 : childidx[i];
|
|
mk[i] = miss ? 0.0f : 1.0f;
|
|
}
|
|
ggml_backend_tensor_set(cidx_t, cl.data(), 0, cl.size() * sizeof(int32_t));
|
|
ggml_backend_tensor_set(cmask_t, mk.data(), 0, mk.size() * sizeof(float));
|
|
}
|
|
|
|
if (ggml_backend_graph_compute(m->backend, gf) != GGML_STATUS_SUCCESS) {
|
|
set_error(error, "enc graph compute failed (level " + std::to_string(lvl) + ")");
|
|
ggml_gallocr_free(alloc); ggml_free(ctx); return false;
|
|
}
|
|
|
|
const int C_next = has_down ? C_out : hp.latent_channels;
|
|
const int L_next = has_down ? Lc : L;
|
|
feats.resize((size_t) C_next * L_next);
|
|
ggml_backend_tensor_get(out_h, feats.data(), 0, feats.size() * es);
|
|
|
|
if (taps) cap("enc_lvl" + std::to_string(lvl), feats.data(), feats.size());
|
|
|
|
if (has_down) {
|
|
// record subdivision for the decoder (decoder order = reverse of encode)
|
|
trellis2_subdiv_level & sl = out_subs[(size_t)(n_levels - 2 - lvl)];
|
|
sl.fine_coords = coords; // this level's (fine) coords, in fine order
|
|
sl.cidx = std::move(cidx);
|
|
coords = std::move(coarse_coords);
|
|
L = Lc;
|
|
} else {
|
|
out_slat = feats;
|
|
out_coords = coords;
|
|
}
|
|
ggml_gallocr_free(alloc); ggml_free(ctx);
|
|
}
|
|
return true;
|
|
}
|