makepad/libs/ai/cuda/kernels/splat.cu
Admin 7f59912916 libs/ai: one AI stack, replacing libs/ggml, llama, mlx, cuda, tts, voice2 and pbr_paint
The model code was spread across eight crates that had grown into each other:
ggml and cuda and mlx each owned part of a tensor runtime, llama and tts and
voice2 each owned part of a model, and libs/diffusion owned everything else.
They are now one tree with an explicit shape:

  libs/ai/cuda     — kernels and launch surface
  libs/ai/metal    — Metal shaders and the shim
  libs/ai/llm      — the language-model runtime (sessions, lanes, contexts,
                     the CUDA and Metal executors, the compiled Metal path)
  libs/ai/models/  — common, flux, h3, music, paint, speech, stems, vision

libs/diffusion is not deleted but demoted: what remains is the VALIDATOR
crate — several dozen `*_validate.rs` oracles that check a native
implementation against a reference, which is where they belong now that the
implementations live next door.

The functional work inside the move is mostly in the LLM runtime: N lanes that
draft while one verify batch serves all of them, per-slot prefill over a shared
folded attention arena, speculation that survives batching, and a scheduler
that reports rather than publishes. And in the CUDA build: a machine without
usable CUDA must still LINK (and say so), the default kernel arch is the
building machine's GPU, `NO_CUDA` forces the stub even where the toolkit
exists, and kernels compile in parallel with progress.

libs/video_flow is new here: classical optical flow estimation and the `mkfl`
motion-field payload — a flow field measured from a clip without a model,
which is what drives free-rate bounce-looping playback and the uprez/tween
enhance pipe.
2026-08-23 01:34:35 +02:00

133 lines
5.2 KiB
Text

// TripoSplat device ops.
//
// The flow denoiser's positional encoding (`RePo3DRotaryEmbedding` in the
// released model.py) is a LEARNED, per-token AND per-head rope: each block
// projects its own hidden state down to three deltas per head and turns them
// into head_dim/2 complex phases. That is two things the shared rope kernels
// cannot do — the existing `rope_interleaved` table is one row per token,
// shared across heads, and it is supplied by the caller rather than derived
// from an activation. Round-tripping the tables through the host would move
// ~50 MB per block per forward (24 blocks x 20 steps x 2 CFG passes), so the
// tables are built and consumed entirely on device here.
#include <cuda_runtime.h>
#include <math_constants.h>
#include <stdint.h>
#ifndef M_PIf
#define M_PIf 3.14159265358979323846f
#endif
// Phase tables from one RePo3D delta projection.
//
// delta : [tokens][heads][3] (final_map output, reshaped)
// freqs : [pairs] ([freqs_0 | freqs_1 | freqs_2])
// cos/sin: [tokens][heads][pairs]
//
// `ang = delta[axis(p)] * freqs[p] * pi`, where axis(p) is 0 for the first
// `dim0` pairs, 1 for the next `dim1`, and 2 for the rest. The reference's
// `clamp_mul(x, f) = x*tanh(f) + x.detach()*(f - tanh(f))` is a straight
// `x * f` at inference (detach is the identity without autograd), which is
// what this computes.
static __global__ void makepad_cuda_splat_repo3d_tables_f32_kernel(
const float * __restrict__ delta,
const float * __restrict__ freqs,
float * __restrict__ cos_out,
float * __restrict__ sin_out,
uint32_t token_count,
uint32_t head_count,
uint32_t pairs,
uint32_t dim0,
uint32_t dim1) {
const uint32_t token = blockIdx.x;
const uint32_t head = blockIdx.y;
if (token >= token_count || head >= head_count) {
return;
}
const size_t delta_base = (static_cast<size_t>(token) * head_count + head) * 3;
const float d0 = delta[delta_base + 0];
const float d1 = delta[delta_base + 1];
const float d2 = delta[delta_base + 2];
const size_t out_base = (static_cast<size_t>(token) * head_count + head) * pairs;
for (uint32_t p = threadIdx.x; p < pairs; p += blockDim.x) {
const float value = (p < dim0) ? d0 : ((p < dim0 + dim1) ? d1 : d2);
const float angle = value * freqs[p] * M_PIf;
cos_out[out_base + p] = cosf(angle);
sin_out[out_base + p] = sinf(angle);
}
}
extern "C" cudaError_t makepad_cuda_splat_repo3d_tables_f32(
const float * delta,
const float * freqs,
float * cos_out,
float * sin_out,
uint32_t token_count,
uint32_t head_count,
uint32_t pairs,
uint32_t dim0,
uint32_t dim1,
cudaStream_t stream) {
if (token_count == 0 || head_count == 0 || pairs == 0) {
return cudaSuccess;
}
const dim3 block(pairs < 64 ? 32 : 64, 1, 1);
const dim3 grid(token_count, head_count, 1);
makepad_cuda_splat_repo3d_tables_f32_kernel<<<grid, block, 0, stream>>>(
delta, freqs, cos_out, sin_out, token_count, head_count, pairs, dim0, dim1);
return cudaGetLastError();
}
// Interleaved-pair rope with a PER-HEAD phase table.
//
// input : [tokens][heads][2 * pairs]
// cos/sin: [tokens][heads][pairs]
//
// Pairs are (2p, 2p+1) inside a head, matching torch.view_as_complex — the
// same pairing as the shared `rope_interleaved` kernel, but indexing a table
// that varies per head.
static __global__ void makepad_cuda_splat_rope_pairs_per_head_f32_kernel(
const float * __restrict__ input,
const float * __restrict__ cos_table,
const float * __restrict__ sin_table,
float * __restrict__ output,
uint32_t token_count,
uint32_t head_count,
uint32_t pairs) {
const uint32_t token = blockIdx.x;
const uint32_t head = blockIdx.y;
if (token >= token_count || head >= head_count) {
return;
}
const size_t base = (static_cast<size_t>(token) * head_count + head)
* (static_cast<size_t>(pairs) * 2);
const size_t table_base = (static_cast<size_t>(token) * head_count + head) * pairs;
for (uint32_t p = threadIdx.x; p < pairs; p += blockDim.x) {
const size_t even = base + static_cast<size_t>(p) * 2;
const float c = cos_table[table_base + p];
const float s = sin_table[table_base + p];
const float re = input[even];
const float im = input[even + 1];
output[even] = re * c - im * s;
output[even + 1] = re * s + im * c;
}
}
extern "C" cudaError_t makepad_cuda_splat_rope_pairs_per_head_f32(
const float * input,
const float * cos_table,
const float * sin_table,
float * output,
uint32_t token_count,
uint32_t head_count,
uint32_t pairs,
cudaStream_t stream) {
if (token_count == 0 || head_count == 0 || pairs == 0) {
return cudaSuccess;
}
const dim3 block(pairs < 64 ? 32 : 64, 1, 1);
const dim3 grid(token_count, head_count, 1);
makepad_cuda_splat_rope_pairs_per_head_f32_kernel<<<grid, block, 0, stream>>>(
input, cos_table, sin_table, output, token_count, head_count, pairs);
return cudaGetLastError();
}