316 const std::string& prompt,
317 const std::vector<std::vector<uint8_t>>& images,
318 std::span<const llama_token> sep,
320 bool add_special =
false)
321 :
MtmdSource(ctx, prompt, mtmd_detail::image_inputs(images), sep, n_embd_inp, {}, add_special) {}
333 const std::string& prompt,
334 std::span<const MediaInput> inputs,
335 std::span<const llama_token> sep,
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);
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);
362 auto wrap = decode_media(input.bytes, i);
363 bitmaps_.emplace_back(wrap);
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");
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");
373 ptrs.push_back(wrap);
376 chunks_.reset(mtmd_input_chunks_init());
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");
387 mrope_ = mtmd_decode_use_mrope(ctx_);
388 n_chunks_ = mtmd_input_chunks_size(chunks_.get());
389 lead_ = sep_.empty() ? 0 : 1;
391 for (
size_t i = 0; i < size(); ++i) {
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");
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");
406 map_inputs(inputs.size());
409 size_t size()
override {
return lead_ + n_chunks_; }
419 size_t cells()
const override {
return cells_; }
421 int32_t
input_of(
size_t i)
const override {
return i < input_of_.size() ? input_of_[i] : -1; }
424 return input >= 0 &&
static_cast<size_t>(input) < total_of_.size() ? total_of_[input] : 0;
430 if (lead_ != 0 && i == 0) {
431 seg.
kind = decode::Segment::Kind::Text;
436 const mtmd_input_chunk* chunk = chunk_at(i);
437 switch (mtmd_input_chunk_get_type(chunk)) {
438 case MTMD_INPUT_CHUNK_TYPE_TEXT: {
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);
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");
452 seg.
kind = decode::Segment::Kind::Embd;
455 seg.
rows = mtmd_get_output_embd(ctx_);
456 seg.
n_rows =
static_cast<int32_t
>(
457 mtmd_input_chunk_get_n_tokens(chunk));
459 seg.
n_pos = mtmd_input_chunk_get_n_pos(chunk);
461 seg.
non_causal = mtmd_decode_use_non_causal(ctx_, chunk);
466 throw std::runtime_error(
"MtmdSource - unsupported media chunk");
470 void positions(
size_t i, llama_pos base, llama_pos* out)
override {
471 const mtmd_input_chunk* chunk = chunk_at(i);
473 static_cast<int32_t
>(mtmd_input_chunk_get_n_tokens(chunk));
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");
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);
486 const mtmd_image_tokens* img = mtmd_input_chunk_get_tokens_image(chunk);
488 throw std::runtime_error(
"MtmdSource - image tokens missing");
495 rel_.resize(
static_cast<size_t>(n));
496 mtmd_helper_image_get_decoder_pos(img, base, rel_.data());
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);
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(),
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");
519 throw std::runtime_error(
"MtmdSource - unsupported media bytes at input " + std::to_string(index));
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");
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");
539 mtmd_bitmap_set_mergeable(frame,
true);
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");
548 if (video.frames.empty()) {
549 throw std::runtime_error(
"MtmdSource - a video needs at least one frame");
551 if (video.timestamps_ms.size() != video.frames.size()) {
552 throw std::runtime_error(
"MtmdSource - a video needs one timestamp per frame");
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");
558 if (video.timestamp_interval_ms < 0) {
559 throw std::runtime_error(
"MtmdSource - video timestamp interval must not be negative");
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");
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");
572 void validate_markers(
const std::string& prompt,
size_t count)
const {
573 const std::string marker = mtmd_get_marker(ctx_);
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;
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) +
")");
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");
597 if (!mtmd_support_vision(ctx_)) {
598 throw std::runtime_error(
"MtmdSource - projector does not support image input");
601 case MediaInput::Kind::Video:
602 validate_video(input.video);
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");
608 if (!limits.max_bytes || !limits.max_samples) {
609 throw std::runtime_error(
"MtmdSource - audio limits must be positive");
611 if (input.bytes.size() > bytes_left) {
612 throw std::runtime_error(
"MtmdSource - audio byte limit exceeded");
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");
619 samples_left -= samples[i];
622 throw std::runtime_error(
"MtmdSource - unsupported input kind");
628 const mtmd_input_chunk* chunk_at(
size_t i)
const {
629 return mtmd_input_chunks_get(chunks_.get(), i - lead_);
633 static void name_input(mtmd_bitmap* bitmap,
size_t input) {
634 mtmd_bitmap_set_id(bitmap, std::to_string(input).c_str());
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));
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);
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];
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);
670 mtmd_context* ctx_ =
nullptr;
671 std::span<const llama_token> sep_;
672 int32_t n_embd_inp_ = 0;
674 std::vector<::mtmd::bitmap_ptr> bitmaps_;
675 ::mtmd::input_chunks_ptr chunks_;
676 std::vector<mtmd_decoder_pos> rel_;
679 size_t n_chunks_ = 0;
683 std::vector<int32_t> input_of_;
684 std::vector<size_t> total_of_;