Multimodal input
pkg/mtmd sends more than text to a model. It handles images, audio, and video.
Load the projector
A multimodal model needs a projector file. The projector turns the image, the sound, or the video into tokens.
mtmd.Load(libPath)
mctxParams := mtmd.ContextParamsDefault()
mtmdCtx, err := mtmd.InitFromFile(projFile, model, mctxParams)
defer mtmd.Free(mtmdCtx)
Check what the model accepts
mtmd.SupportVision(mtmdCtx)
mtmd.SupportAudio(mtmdCtx)
mtmd.SupportVideo(mtmdCtx)
Each one gives a boolean. Check before you send the input.
Images
Read an image from a file:
bitmap := mtmd.BitmapInitFromFile(mtmdCtx, imageFile, false, mtmd.InitOptDefault())
defer mtmd.BitmapFree(bitmap.Bitmap)
Read an image from memory:
bitmap := mtmd.BitmapInitFromBuf(mtmdCtx, &data[0], uint64(len(data)), false, mtmd.InitOptDefault())
See Vision for the complete program.
Audio
BitmapInitFromAudio takes the samples as float32 values.
bitmap := mtmd.BitmapInitFromAudio(uint64(len(samples)), &samples[0])
defer mtmd.BitmapFree(bitmap)
The model expects one sample rate. GetAudioSampleRate gives it:
rate := mtmd.GetAudioSampleRate(mtmdCtx)
Resample your audio to that rate before you send it.
Video
Video needs ffmpeg and ffprobe on the PATH. mtmd calls them to read the frames.
params := mtmd.VideoInitParamsDefault()
params.FPSTarget = 1.0
params.TimestampIntervalMs = 10000
video := mtmd.VideoInit(mtmdCtx, videoFile, params)
| Field | What it does |
|---|---|
FPSTarget |
How many frames to take each second. A value of 0 or less takes the native rate of the video. |
FFmpegBinDir |
The directory that holds ffmpeg and ffprobe. A nil value searches the PATH. Use utils.BytePtrFromString to make the value. |
TimestampIntervalMs |
How often to put a time marker in the tokens, such as [10m50.5s]. A value of 0 or less adds no markers. |
A lower FPSTarget gives fewer frames. Fewer frames need less time and less memory. Start with 1.0.
VideoInitFromBuf reads a video from memory.
Send the input to the model
The steps are the same for every kind of input.
output := mtmd.InputChunksInit()
defer mtmd.InputChunksFree(output)
input := mtmd.NewInputText(prompt, true, true)
mtmd.Tokenize(mtmdCtx, output, input, []mtmd.Bitmap{bitmap})
var n llama.Pos
mtmd.HelperEvalChunks(mtmdCtx, lctx, output, 0, 0, nBatch, true, &n)
The prompt must hold the marker that says where the input goes. mtmd.DefaultMarker() gives it.
More than one image
Put more bitmaps in the slice, and put one marker in the text for each one.
mtmd.Tokenize(mtmdCtx, output, input, []mtmd.Bitmap{first, second})
Parts
TokenizeFromParts builds the input from a list of parts. Use it when you need text, then an image, then more text.
parts := []*mtmd.InputPart{
mtmd.NewInputTextPart(mtmd.NewInputText("Look at this:", true, true)),
mtmd.NewInputBitmapPart(bitmap),
}
mtmd.TokenizeFromParts(mtmdCtx, output, parts, true)
Speed
The projector runs one time for each image. This is the slow part. On a CPU it takes half a minute or more. On a GPU it takes a second or two.
Use a GPU for a program that reads images. See Hardware acceleration.
In a browser
The browser package reads images. It has no audio and no video. See WebAssembly.