liblloyal 1.0.0
Branched Inference for llama.cpp
Loading...
Searching...
No Matches
mtmd.hpp
Go to the documentation of this file.
1#pragma once
2
3// SPDX-License-Identifier: LicenseRef-FSL-1.1-Apache-2.0
4// Copyright 2026 Lloyal Labs
5
6#include "decode.hpp"
7
8#include <mtmd.h>
9#include <mtmd-helper.h>
10
11#include <algorithm>
12#include <cstdint>
13#include <cstdio>
14#include <cstdlib>
15#include <cstring>
16#include <functional>
17#include <limits>
18#include <memory>
19#include <numeric>
20#include <optional>
21#include <span>
22#include <stdexcept>
23#include <string>
24#include <string_view>
25#include <vector>
26
62namespace lloyal {
63
64// The text struct is filled field by field below; a new upstream field would be
65// value-initialised to zero without a word. Fail the build instead: the size on
66// 64-bit, and the member count, which padding cannot hide.
67#if INTPTR_MAX == INT64_MAX
68static_assert(sizeof(mtmd_input_text) == 24, "mtmd_input_text changed shape: revisit MtmdSource's construction");
69#endif
70namespace shape_guard {
72 [[maybe_unused]] auto [text, text_len, add_special, parse_special] = mtmd_input_text{};
73}
74} // namespace shape_guard
75
77struct VideoNote {
78 int64_t at_ms = 0;
79 std::string_view text;
80};
81
96 std::span<const std::span<const uint8_t>> frames;
97 std::span<const int64_t> timestamps_ms;
98 int64_t timestamp_interval_ms = 5000;
99 std::span<const VideoNote> notes;
100};
101
104 enum class Kind { Image, Audio, Video };
106 std::span<const uint8_t> bytes;
108};
109
112 size_t max_bytes = 0;
113 size_t max_samples = 0;
114};
115
116namespace mtmd_detail {
117
118inline bool tag(std::span<const uint8_t> bytes, size_t offset, const char* value) {
119 return offset <= bytes.size() && bytes.size() - offset >= 4 &&
120 std::memcmp(bytes.data() + offset, value, 4) == 0;
121}
122
123inline uint16_t u16(std::span<const uint8_t> bytes, size_t offset) {
124 if (offset > bytes.size() || bytes.size() - offset < 2) {
125 throw std::runtime_error("MtmdSource - truncated PCM WAV");
126 }
127 return static_cast<uint16_t>(bytes[offset] | (uint16_t(bytes[offset + 1]) << 8));
128}
129
130inline uint32_t u32(std::span<const uint8_t> bytes, size_t offset) {
131 return uint32_t(u16(bytes, offset)) | (uint32_t(u16(bytes, offset + 2)) << 16);
132}
133
134// The pinned helper's audio sniff is private. Refuse its audio signatures on
135// the image entry point before it can allocate a decoded recording.
136inline bool encoded_audio(std::span<const uint8_t> bytes) {
137 if (bytes.size() < 12) return false;
138 return (tag(bytes, 0, "RIFF") && tag(bytes, 8, "WAVE")) ||
139 tag(bytes, 0, "fLaC") || std::memcmp(bytes.data(), "ID3", 3) == 0 ||
140 (bytes[0] == 0xff && (bytes[1] & 0xe0) == 0xe0);
141}
142
143// Inspect framing only; the existing mtmd/miniaudio helper still owns sample
144// conversion and resampling. Restrict the admitted format to integer PCM WAV.
145inline size_t wav_sample_bound(std::span<const uint8_t> bytes, uint32_t target_rate) {
146 if (bytes.size() < 12 || !tag(bytes, 0, "RIFF") || !tag(bytes, 8, "WAVE") ||
147 u32(bytes, 4) != bytes.size() - 8) {
148 throw std::runtime_error("MtmdSource - invalid PCM WAV container");
149 }
150 std::span<const uint8_t> format;
151 std::span<const uint8_t> data;
152 bool found_data = false;
153 for (size_t offset = 12; offset < bytes.size();) {
154 if (bytes.size() - offset < 8) {
155 throw std::runtime_error("MtmdSource - truncated WAV chunk");
156 }
157 const size_t length = u32(bytes, offset + 4);
158 const size_t remaining = bytes.size() - offset - 8;
159 const size_t padding = length & 1;
160 if (length > remaining || padding > remaining - length) {
161 throw std::runtime_error("MtmdSource - truncated WAV chunk");
162 }
163 const size_t padded = length + padding;
164 const auto chunk = bytes.subspan(offset + 8, length);
165 if (tag(bytes, offset, "fmt ")) {
166 if (!format.empty() || (length != 16 && length != 18)) {
167 throw std::runtime_error("MtmdSource - invalid WAV format chunk");
168 }
169 format = chunk;
170 } else if (tag(bytes, offset, "data")) {
171 if (format.empty() || found_data) {
172 throw std::runtime_error("MtmdSource - invalid WAV data chunk");
173 }
174 data = chunk;
175 found_data = true;
176 } else if (!tag(bytes, offset, "JUNK") &&
177 !(tag(bytes, offset, "LIST") && tag(chunk, 0, "INFO"))) {
178 throw std::runtime_error("MtmdSource - unsupported WAV chunk");
179 }
180 offset += 8 + padded;
181 }
182 if (format.empty() || !found_data || u16(format, 0) != 1 ||
183 (format.size() == 18 && u16(format, 16) != 0)) {
184 throw std::runtime_error("MtmdSource - expected integer PCM WAV");
185 }
186 const uint32_t channels = u16(format, 2);
187 const uint32_t rate = u32(format, 4);
188 const uint32_t bits = u16(format, 14);
189 const bool integer_pcm = bits == 8 || bits == 16 || bits == 24 || bits == 32;
190 const uint32_t alignment = channels * (bits / 8);
191 if (!channels || !rate || !integer_pcm || !alignment ||
192 u16(format, 12) != alignment || u32(format, 8) != uint64_t(rate) * alignment ||
193 data.size() % alignment != 0) {
194 throw std::runtime_error("MtmdSource - inconsistent PCM WAV format");
195 }
196 const uint64_t frames = data.size() / alignment;
197 // b9581's miniaudio may allocate floor(ratio * frames) + 1 after resampling.
198 const uint64_t samples = rate == target_rate ? frames : frames * target_rate / rate + 1;
199 if (frames < 2 || samples < 2 ||
200 samples > std::numeric_limits<int32_t>::max() / sizeof(float)) {
201 throw std::runtime_error("MtmdSource - unsupported audio sample count");
202 }
203 return static_cast<size_t>(samples);
204}
205
206// The helper's timestamp text, character for character ("[%dm%.2fs]").
207inline std::string timestamp_text(int64_t ms) {
208 const float elapsed_s = static_cast<float>(ms) / 1000.0f;
209 // Minutes as 64-bit: any int64 millisecond count fits, where the helper's int would overflow.
210 const int64_t minutes = static_cast<int64_t>(elapsed_s / 60);
211 const float seconds = elapsed_s - static_cast<float>(minutes) * 60.0f;
212 char buf[48];
213 std::snprintf(buf, sizeof buf, "[%lldm%.2fs]", static_cast<long long>(minutes), seconds);
214 return buf;
215}
216
219 struct Part {
220 ::mtmd::bitmap_ptr frame;
221 std::string text;
222 };
223 std::vector<Part> parts;
224
225 static int next(size_t i, void* self, mtmd_bitmap** out_bitmap, char** out_text) {
226 auto& parts = static_cast<VideoLayout*>(self)->parts;
227 if (i >= parts.size()) return -1;
228 auto& part = parts[i];
229 if (part.frame) {
230 *out_bitmap = part.frame.release();
231 return 0;
232 }
233 // mtmd frees the text with free(); malloc is the portable strdup (MSVC deprecates the POSIX name).
234 const size_t size = part.text.size() + 1;
235 *out_text = static_cast<char*>(std::malloc(size));
236 if (!*out_text) return -2;
237 std::memcpy(*out_text, part.text.c_str(), size);
238 return 0;
239 }
240};
241
242inline VideoLayout video_layout(std::vector<::mtmd::bitmap_ptr> frames, const VideoFrames& video) {
243 std::vector<VideoNote> notes(video.notes.begin(), video.notes.end());
244 std::stable_sort(notes.begin(), notes.end(),
245 [](const VideoNote& a, const VideoNote& b) { return a.at_ms < b.at_ms; });
246 auto note = notes.begin();
247 const auto notes_until = [&](auto stop) {
248 std::string text;
249 for (; note != notes.end() && stop(*note); ++note) text += note->text;
250 return text;
251 };
252
253 VideoLayout layout;
254 layout.parts.push_back({nullptr, "Video:"});
255 // The next threshold, counted from the first frame (see VideoFrames), or none
256 // once it would lie past any time an int64 can hold.
257 std::optional<int64_t> next_timestamp_ms =
258 video.timestamp_interval_ms > 0 ? std::optional<int64_t>{video.timestamps_ms.front()} : std::nullopt;
259 for (size_t i = 0; i < frames.size(); ++i) {
260 layout.parts.push_back({std::move(frames[i]), {}});
261 const int64_t t = video.timestamps_ms[i];
262 if (next_timestamp_ms && t >= *next_timestamp_ms) {
263 layout.parts.push_back({nullptr, timestamp_text(t) +
264 notes_until([t](const VideoNote& n) { return n.at_ms <= t; })});
265 const bool exhausted = *next_timestamp_ms > std::numeric_limits<int64_t>::max() - video.timestamp_interval_ms;
266 next_timestamp_ms = exhausted ? std::nullopt : std::optional<int64_t>{*next_timestamp_ms + video.timestamp_interval_ms};
267 }
268 }
269 if (auto closing = notes_until([](const VideoNote&) { return true; }); !closing.empty()) {
270 layout.parts.push_back({nullptr, std::move(closing)});
271 }
272 return layout;
273}
274
275inline std::vector<MediaInput> image_inputs(const std::vector<std::vector<uint8_t>>& images) {
276 std::vector<MediaInput> inputs;
277 inputs.reserve(images.size());
278 for (const auto& bytes : images) inputs.push_back({MediaInput::Kind::Image, bytes});
279 return inputs;
280}
281
282} // namespace mtmd_detail
283
312class MtmdSource final : public decode::SegmentSource {
313public:
315 MtmdSource(mtmd_context* ctx,
316 const std::string& prompt,
317 const std::vector<std::vector<uint8_t>>& images,
318 std::span<const llama_token> sep,
319 int32_t n_embd_inp,
320 bool add_special = false)
321 : MtmdSource(ctx, prompt, mtmd_detail::image_inputs(images), sep, n_embd_inp, {}, add_special) {}
322
332 MtmdSource(mtmd_context* ctx,
333 const std::string& prompt,
334 std::span<const MediaInput> inputs,
335 std::span<const llama_token> sep,
336 int32_t n_embd_inp,
337 AudioLimits limits,
338 bool add_special = false)
339 : ctx_(ctx), sep_(sep), n_embd_inp_(n_embd_inp) {
340 if (!ctx_) throw std::runtime_error("MtmdSource - NULL mtmd context");
341 if (n_embd_inp_ <= 0) throw std::runtime_error("MtmdSource - invalid embedding width");
342 validate_markers(prompt, inputs.size());
343 const auto sample_bounds = validate_inputs(inputs, limits);
344
345 // A video is one lazy bitmap at its marker, expanded by mtmd_tokenize into
346 // its frames and timestamps: the mechanism llama.cpp's video helper uses.
347 std::vector<std::unique_ptr<mtmd_detail::VideoLayout>> videos;
348 std::vector<const mtmd_bitmap*> ptrs;
349 ptrs.reserve(inputs.size());
350 for (size_t i = 0; i < inputs.size(); ++i) {
351 const auto& input = inputs[i];
352 if (input.kind == MediaInput::Kind::Video) {
353 videos.push_back(std::make_unique<mtmd_detail::VideoLayout>(
354 mtmd_detail::video_layout(decode_frames(input.video, i), input.video)));
355 auto* lazy = mtmd_bitmap_init_lazy(ctx_, "", videos.back().get(),
356 &mtmd_detail::VideoLayout::next);
357 if (!lazy) throw std::runtime_error("MtmdSource - video layout failed");
358 bitmaps_.emplace_back(lazy);
359 ptrs.push_back(lazy);
360 continue;
361 }
362 auto wrap = decode_media(input.bytes, i);
363 bitmaps_.emplace_back(wrap);
364 name_input(wrap, i);
365 const bool audio = input.kind == MediaInput::Kind::Audio;
366 if (mtmd_bitmap_is_audio(wrap) != audio) {
367 throw std::runtime_error("MtmdSource - decoded media kind does not match input");
368 }
369 if (audio && (mtmd_bitmap_get_nx(wrap) < 2 ||
370 mtmd_bitmap_get_nx(wrap) > sample_bounds[i])) {
371 throw std::runtime_error("MtmdSource - decoded audio exceeds validated sample bound");
372 }
373 ptrs.push_back(wrap);
374 }
375
376 chunks_.reset(mtmd_input_chunks_init());
377 // Named on purpose: a positional initialiser compiled unchanged when upstream
378 // inserted text_len and silently meant text length false, add_special true.
379 mtmd_input_text txt{};
380 txt.text = prompt.c_str();
381 txt.text_len = prompt.size();
382 txt.add_special = add_special;
383 txt.parse_special = true;
384 if (mtmd_tokenize(ctx_, chunks_.get(), &txt, ptrs.data(), ptrs.size()) != 0) {
385 throw std::runtime_error("MtmdSource - media preprocessing failed");
386 }
387 mrope_ = mtmd_decode_use_mrope(ctx_);
388 n_chunks_ = mtmd_input_chunks_size(chunks_.get());
389 lead_ = sep_.empty() ? 0 : 1;
390 cells_ = 0;
391 for (size_t i = 0; i < size(); ++i) {
392 if (i >= lead_) {
393 const auto kind = mtmd_input_chunk_get_type(chunk_at(i));
394 if (kind != MTMD_INPUT_CHUNK_TYPE_TEXT && kind != MTMD_INPUT_CHUNK_TYPE_IMAGE &&
395 kind != MTMD_INPUT_CHUNK_TYPE_AUDIO) {
396 throw std::runtime_error("MtmdSource - unsupported media chunk");
397 }
398 }
399 const size_t rows = cells_at(i);
400 if (rows > std::numeric_limits<int32_t>::max() ||
401 rows > std::numeric_limits<size_t>::max() - cells_) {
402 throw std::runtime_error("MtmdSource - media token count overflow");
403 }
404 cells_ += rows;
405 }
406 map_inputs(inputs.size());
407 }
408
409 size_t size() override { return lead_ + n_chunks_; }
410
419 size_t cells() const override { return cells_; }
420
421 int32_t input_of(size_t i) const override { return i < input_of_.size() ? input_of_[i] : -1; }
422
423 size_t total_of(int32_t input) const override {
424 return input >= 0 && static_cast<size_t>(input) < total_of_.size() ? total_of_[input] : 0;
425 }
426
427 decode::Segment at(size_t i) override {
428 decode::Segment seg;
429
430 if (lead_ != 0 && i == 0) {
431 seg.kind = decode::Segment::Kind::Text;
432 seg.tokens = sep_;
433 return seg;
434 }
435
436 const mtmd_input_chunk* chunk = chunk_at(i);
437 switch (mtmd_input_chunk_get_type(chunk)) {
438 case MTMD_INPUT_CHUNK_TYPE_TEXT: {
439 size_t n_text = 0;
440 const llama_token* toks =
441 mtmd_input_chunk_get_tokens_text(chunk, &n_text);
442 seg.kind = decode::Segment::Kind::Text;
443 seg.tokens = std::span<const llama_token>(toks, n_text);
444 return seg;
445 }
446
447 case MTMD_INPUT_CHUNK_TYPE_IMAGE:
448 case MTMD_INPUT_CHUNK_TYPE_AUDIO: {
449 if (mtmd_encode_chunk(ctx_, chunk) != 0) {
450 throw std::runtime_error("MtmdSource - media encode failed");
451 }
452 seg.kind = decode::Segment::Kind::Embd;
453 // Context-owned buffer, reused by the next encode — the in-order
454 // contract is what makes handing it out safe.
455 seg.rows = mtmd_get_output_embd(ctx_);
456 seg.n_rows = static_cast<int32_t>(
457 mtmd_input_chunk_get_n_tokens(chunk));
458 seg.n_embd_inp = n_embd_inp_;
459 seg.n_pos = mtmd_input_chunk_get_n_pos(chunk);
460 seg.n_pos_per_embd = mrope_ ? 4 : 1;
461 seg.non_causal = mtmd_decode_use_non_causal(ctx_, chunk);
462 return seg;
463 }
464
465 default:
466 throw std::runtime_error("MtmdSource - unsupported media chunk");
467 }
468 }
469
470 void positions(size_t i, llama_pos base, llama_pos* out) override {
471 const mtmd_input_chunk* chunk = chunk_at(i);
472 const int32_t n =
473 static_cast<int32_t>(mtmd_input_chunk_get_n_tokens(chunk));
474
475 if (!mrope_ || mtmd_input_chunk_get_type(chunk) == MTMD_INPUT_CHUNK_TYPE_AUDIO) {
476 if (int64_t(base) + n > std::numeric_limits<llama_pos>::max()) {
477 throw std::runtime_error("MtmdSource - media position overflow");
478 }
479 const size_t axes = mrope_ ? 4 : 1;
480 for (size_t axis = 0; axis < axes; ++axis) {
481 std::iota(out + axis * n, out + (axis + 1) * n, base);
482 }
483 return;
484 }
485
486 const mtmd_image_tokens* img = mtmd_input_chunk_get_tokens_image(chunk);
487 if (!img) {
488 throw std::runtime_error("MtmdSource - image tokens missing");
489 }
490
491 // The impl applies `base` itself (the header's "relative position" note
492 // is stale) and the rules differ per model — M-RoPE freezes t and leaves
493 // z at 0, HunyuanVL uses row/col — which is exactly why the base is
494 // passed in rather than the geometry passed out.
495 rel_.resize(static_cast<size_t>(n));
496 mtmd_helper_image_get_decoder_pos(img, base, rel_.data());
497
498 // Section-major, and note the order: mtmd's decoder convention puts
499 // y in section 1 and x in section 2.
500 for (int32_t k = 0; k < n; ++k) {
501 out[k] = static_cast<llama_pos>(rel_[k].t);
502 out[k + n] = static_cast<llama_pos>(rel_[k].y);
503 out[k + static_cast<size_t>(2) * n] = static_cast<llama_pos>(rel_[k].x);
504 out[k + static_cast<size_t>(3) * n] = static_cast<llama_pos>(rel_[k].z);
505 }
506 }
507
508private:
510 mtmd_bitmap* decode_media(std::span<const uint8_t> bytes, size_t index) const {
511 auto wrap = mtmd_helper_bitmap_init_from_buf(
512 ctx_, bytes.data(), bytes.size(), /*placeholder*/ false, mtmd_helper_init_opt_default());
513 if (wrap.video_ctx) {
514 mtmd_helper_video_free(wrap.video_ctx);
515 if (wrap.bitmap) mtmd_bitmap_free(wrap.bitmap);
516 throw std::runtime_error("MtmdSource - a video container is not an input; pass its frames");
517 }
518 if (!wrap.bitmap) {
519 throw std::runtime_error("MtmdSource - unsupported media bytes at input " + std::to_string(index));
520 }
521 return wrap.bitmap;
522 }
523
525 std::vector<::mtmd::bitmap_ptr> decode_frames(const VideoFrames& video, size_t input) const {
526 std::vector<::mtmd::bitmap_ptr> frames;
527 frames.reserve(video.frames.size());
528 for (size_t k = 0; k < video.frames.size(); ++k) {
529 frames.emplace_back(decode_media(video.frames[k], k));
530 auto* frame = frames.back().get();
531 name_input(frame, input);
532 if (mtmd_bitmap_is_audio(frame)) {
533 throw std::runtime_error("MtmdSource - decoded media kind does not match input");
534 }
535 if (mtmd_bitmap_get_nx(frame) != mtmd_bitmap_get_nx(frames.front().get()) ||
536 mtmd_bitmap_get_ny(frame) != mtmd_bitmap_get_ny(frames.front().get())) {
537 throw std::runtime_error("MtmdSource - video frames must share one size");
538 }
539 mtmd_bitmap_set_mergeable(frame, true);
540 }
541 return frames;
542 }
543
544 void validate_video(const VideoFrames& video) const {
545 if (!mtmd_support_vision(ctx_)) {
546 throw std::runtime_error("MtmdSource - projector does not support image input");
547 }
548 if (video.frames.empty()) {
549 throw std::runtime_error("MtmdSource - a video needs at least one frame");
550 }
551 if (video.timestamps_ms.size() != video.frames.size()) {
552 throw std::runtime_error("MtmdSource - a video needs one timestamp per frame");
553 }
554 const auto& t = video.timestamps_ms;
555 if (t.front() < 0 || std::adjacent_find(t.begin(), t.end(), std::greater_equal<int64_t>()) != t.end()) {
556 throw std::runtime_error("MtmdSource - video timestamps must increase");
557 }
558 if (video.timestamp_interval_ms < 0) {
559 throw std::runtime_error("MtmdSource - video timestamp interval must not be negative");
560 }
561 const int64_t start = t.front();
562 if (std::any_of(video.notes.begin(), video.notes.end(), [start](const VideoNote& n) { return n.at_ms < start; })) {
563 throw std::runtime_error("MtmdSource - video notes must not precede the clip");
564 }
565 for (const auto& frame : video.frames) {
566 if (mtmd_detail::encoded_audio(frame)) {
567 throw std::runtime_error("MtmdSource - audio bytes require a typed audio input");
568 }
569 }
570 }
571
572 void validate_markers(const std::string& prompt, size_t count) const {
573 const std::string marker = mtmd_get_marker(ctx_);
574 size_t markers = 0;
575 if (!marker.empty()) {
576 for (size_t p = prompt.find(marker); p != std::string::npos;
577 p = prompt.find(marker, p + marker.size())) ++markers;
578 }
579 // Upstream folds a marker mismatch into a generic preprocessing error.
580 if (markers != count) {
581 throw std::runtime_error("MtmdSource - media marker count (" + std::to_string(markers) +
582 ") does not match media count (" + std::to_string(count) + ")");
583 }
584 }
585
586 std::vector<size_t> validate_inputs(std::span<const MediaInput> inputs, AudioLimits limits) const {
587 std::vector<size_t> samples(inputs.size(), 0);
588 size_t bytes_left = limits.max_bytes;
589 size_t samples_left = limits.max_samples;
590 for (size_t i = 0; i < inputs.size(); ++i) {
591 const auto& input = inputs[i];
592 switch (input.kind) {
593 case MediaInput::Kind::Image:
594 if (mtmd_detail::encoded_audio(input.bytes)) {
595 throw std::runtime_error("MtmdSource - audio bytes require a typed audio input");
596 }
597 if (!mtmd_support_vision(ctx_)) {
598 throw std::runtime_error("MtmdSource - projector does not support image input");
599 }
600 break;
601 case MediaInput::Kind::Video:
602 validate_video(input.video);
603 break;
604 case MediaInput::Kind::Audio:
605 if (!mtmd_support_audio(ctx_) || mtmd_get_audio_sample_rate(ctx_) <= 0) {
606 throw std::runtime_error("MtmdSource - projector does not support audio input");
607 }
608 if (!limits.max_bytes || !limits.max_samples) {
609 throw std::runtime_error("MtmdSource - audio limits must be positive");
610 }
611 if (input.bytes.size() > bytes_left) {
612 throw std::runtime_error("MtmdSource - audio byte limit exceeded");
613 }
614 bytes_left -= input.bytes.size();
615 samples[i] = mtmd_detail::wav_sample_bound(input.bytes, mtmd_get_audio_sample_rate(ctx_));
616 if (samples[i] > samples_left) {
617 throw std::runtime_error("MtmdSource - audio sample limit exceeded");
618 }
619 samples_left -= samples[i];
620 break;
621 default:
622 throw std::runtime_error("MtmdSource - unsupported input kind");
623 }
624 }
625 return samples;
626 }
627
628 const mtmd_input_chunk* chunk_at(size_t i) const {
629 return mtmd_input_chunks_get(chunks_.get(), i - lead_);
630 }
631
633 static void name_input(mtmd_bitmap* bitmap, size_t input) {
634 mtmd_bitmap_set_id(bitmap, std::to_string(input).c_str());
635 }
636
638 size_t cells_at(size_t i) const {
639 if (lead_ != 0 && i == 0) return sep_.size();
640 return mtmd_input_chunk_get_n_tokens(chunk_at(i));
641 }
642
650 void map_inputs(size_t n_inputs) {
651 const size_t n = size();
652 std::vector<int32_t> media(n, -1);
653 for (size_t i = lead_; i < n; ++i) {
654 if (const char* id = mtmd_input_chunk_get_id(chunk_at(i))) media[i] = std::stoi(id);
655 }
656 // The nearest media chunk's input on either side of each segment.
657 std::vector<int32_t> before(n, -1), after(n, -1);
658 for (size_t i = 1; i < n; ++i) before[i] = media[i - 1] >= 0 ? media[i - 1] : before[i - 1];
659 for (size_t i = n; i > 1; --i) after[i - 2] = media[i - 1] >= 0 ? media[i - 1] : after[i - 1];
660
661 input_of_.assign(n, -1);
662 total_of_.assign(n_inputs, 0);
663 for (size_t i = 0; i < n; ++i) {
664 const bool inside = before[i] >= 0 && before[i] == after[i];
665 input_of_[i] = media[i] >= 0 ? media[i] : (inside ? before[i] : -1);
666 if (input_of_[i] >= 0) total_of_[input_of_[i]] += cells_at(i);
667 }
668 }
669
670 mtmd_context* ctx_ = nullptr;
671 std::span<const llama_token> sep_;
672 int32_t n_embd_inp_ = 0;
673
674 std::vector<::mtmd::bitmap_ptr> bitmaps_;
675 ::mtmd::input_chunks_ptr chunks_;
676 std::vector<mtmd_decoder_pos> rel_;
677
678 bool mrope_ = false;
679 size_t n_chunks_ = 0;
680 size_t lead_ = 0;
681 size_t cells_ = 0;
682
683 std::vector<int32_t> input_of_; // per segment: its input, or -1
684 std::vector<size_t> total_of_; // per input: its cells
685};
686
687} // namespace lloyal
Turns a marker'd prompt + admitted media into a decode segment stream.
Definition mtmd.hpp:312
decode::Segment at(size_t i) override
Segment i. Invalidates any previously returned segment.
Definition mtmd.hpp:427
size_t size() override
Number of segments in this prefill.
Definition mtmd.hpp:409
MtmdSource(mtmd_context *ctx, const std::string &prompt, std::span< const MediaInput > inputs, std::span< const llama_token > sep, int32_t n_embd_inp, AudioLimits limits, bool add_special=false)
Inputs follow prompt marker order.
Definition mtmd.hpp:332
size_t total_of(int32_t input) const override
KV cells input input takes, summed over its segments: what progress on it counts towards.
Definition mtmd.hpp:423
void positions(size_t i, llama_pos base, llama_pos *out) override
Fill positions for an EMBD segment, given the absolute base the store chose.
Definition mtmd.hpp:470
size_t cells() const override
KV cells this prefill will consume — see decode::SegmentSource::cells.
Definition mtmd.hpp:419
int32_t input_of(size_t i) const override
The input segment i belongs to (an image, a video, a recording), or -1 for prompt text that belongs t...
Definition mtmd.hpp:421
MtmdSource(mtmd_context *ctx, const std::string &prompt, const std::vector< std::vector< uint8_t > > &images, std::span< const llama_token > sep, int32_t n_embd_inp, bool add_special=false)
Existing image entry point; audio requires typed inputs and explicit limits.
Definition mtmd.hpp:315
Batch Decoding Operations.
std::string timestamp_text(int64_t ms)
Definition mtmd.hpp:207
size_t wav_sample_bound(std::span< const uint8_t > bytes, uint32_t target_rate)
Definition mtmd.hpp:145
uint16_t u16(std::span< const uint8_t > bytes, size_t offset)
Definition mtmd.hpp:123
bool encoded_audio(std::span< const uint8_t > bytes)
Definition mtmd.hpp:136
bool tag(std::span< const uint8_t > bytes, size_t offset, const char *value)
Definition mtmd.hpp:118
uint32_t u32(std::span< const uint8_t > bytes, size_t offset)
Definition mtmd.hpp:130
VideoLayout video_layout(std::vector<::mtmd::bitmap_ptr > frames, const VideoFrames &video)
Definition mtmd.hpp:242
std::vector< MediaInput > image_inputs(const std::vector< std::vector< uint8_t > > &images)
Definition mtmd.hpp:275
void mtmd_input_text_has_four_members()
Definition mtmd.hpp:71
Boundary Tracker Stub for OSS liblloyal.
Per-source audio budgets, across all inputs.
Definition mtmd.hpp:111
A borrowed admitted representation.
Definition mtmd.hpp:103
std::span< const uint8_t > bytes
Definition mtmd.hpp:106
VideoFrames video
Definition mtmd.hpp:107
The sampled frames of one video, in order, each an encoded image with the time it was sampled at.
Definition mtmd.hpp:95
int64_t timestamp_interval_ms
Definition mtmd.hpp:98
std::span< const VideoNote > notes
Definition mtmd.hpp:99
std::span< const int64_t > timestamps_ms
Definition mtmd.hpp:97
std::span< const std::span< const uint8_t > > frames
Definition mtmd.hpp:96
Text placed on a video's timeline, such as a transcript segment.
Definition mtmd.hpp:77
std::string_view text
Definition mtmd.hpp:79
int64_t at_ms
Definition mtmd.hpp:78
Supplies the segment sequence for one branch's prefill.
Definition decode.hpp:682
One run of input in a heterogeneous prefill.
Definition decode.hpp:646
const float * rows
Embd: n_rows x n_embd_inp floats.
Definition decode.hpp:655
int32_t n_pos_per_embd
1 (plain positions) or 4 (M-RoPE)
Definition decode.hpp:661
bool non_causal
Bracket this segment's decode in non-causal attention.
Definition decode.hpp:663
llama_pos n_pos
Position advance this segment costs (may be < n_rows under M-RoPE)
Definition decode.hpp:659
std::span< const llama_token > tokens
Text: ready token ids (never re-tokenized by the store)
Definition decode.hpp:651
One video expanded into the parts mtmd tokenizes: decoded frames and text.
Definition mtmd.hpp:218
static int next(size_t i, void *self, mtmd_bitmap **out_bitmap, char **out_text)
Definition mtmd.hpp:225
std::vector< Part > parts
Definition mtmd.hpp:223