Skip to content

Commit b1ac299

Browse files
committed
changed config setting
Signed-off-by: Xiyue Yu <xiyue@google.com>
1 parent e7a66d4 commit b1ac299

5 files changed

Lines changed: 110 additions & 59 deletions

File tree

pkg/epp/framework/plugins/requestcontrol/dataproducer/tokenizer/README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,20 +76,20 @@ apply to every video in the request.
7676
| ------------------------------- | --------------- | ----------------------------------------------- |
7777
| `x-llm-d-video-duration-seconds`| float seconds | Video length; overrides `defaultDuration`. |
7878
| `x-llm-d-video-resolution` | `WIDTHxHEIGHT` | Frame resolution; overrides `defaultResolution`.|
79-
| `x-llm-d-video-fps` | float | Source frame rate; overrides `frames.defaultSourceFPS` (strided mode). |
79+
| `x-llm-d-video-fps` | float | Source frame rate; overrides `frames.strided.defaultSourceFPS` (strided mode). |
8080

8181
| Parameter | Default | Description |
8282
| ------------------------------------- | --------- | ------------------------------------------------------------------------------- |
8383
| `estimate.video.tokensPerFrame.mode` | `dynamic` | `dynamic` (width×height/factor) or `static` (a constant per-frame count). |
84-
| `estimate.video.tokensPerFrame.factor`| `1024` | Dynamic-mode pixels-per-placeholder-token divisor. |
85-
| `estimate.video.tokensPerFrame.staticToken` || Static-mode per-frame placeholder count. |
86-
| `estimate.video.frames.mode` | `sampled` | `sampled` (clamp(duration×sampleFPS, minFrames, maxFrames) / temporalPatchSize) or `strided` (min(duration×defaultSourceFPS/frameStride, maxFrames)). |
87-
| `estimate.video.frames.sampleFPS` | `1` | Sampled-mode sampling rate. |
88-
| `estimate.video.frames.minFrames` || Sampled-mode frame floor (0 = none). Models a processor's minimum sampled frames. |
89-
| `estimate.video.frames.temporalPatchSize` || Sampled-mode: merge every N sampled frames into one token group (qwen3-vl = 2; <2 = no merge). |
90-
| `estimate.video.frames.defaultSourceFPS` | `24` | Strided-mode source frame rate; fallback for the `x-llm-d-video-fps` header. |
91-
| `estimate.video.frames.frameStride` | `1` | Strided-mode divisor: keep every Nth source frame. |
84+
| `estimate.video.tokensPerFrame.dynamic.factor`| `1024` | Dynamic-mode pixels-per-placeholder-token divisor. |
85+
| `estimate.video.tokensPerFrame.static.staticToken` || Static-mode per-frame placeholder count. |
86+
| `estimate.video.frames.mode` | `sampled` | `sampled` (clamp(duration×sampleFPS, minFrames, maxFrames) / temporalPatchSize) or `strided` (clamp(duration×defaultSourceFPS/frameStride, minFrames, maxFrames)). |
87+
| `estimate.video.frames.minFrames` || Sampled/strided frame floor (0 = none). Models a processor's minimum frames. |
9288
| `estimate.video.frames.maxFrames` || Sampled/strided frame cap (0 = uncapped). |
89+
| `estimate.video.frames.sampled.sampleFPS` | `1` | Sampled-mode sampling rate. |
90+
| `estimate.video.frames.sampled.temporalPatchSize` || Sampled-mode: merge every N sampled frames into one token group (qwen3-vl = 2; <2 = no merge). |
91+
| `estimate.video.frames.strided.defaultSourceFPS` | `24` | Strided-mode source frame rate; fallback for the `x-llm-d-video-fps` header. |
92+
| `estimate.video.frames.strided.frameStride` | `1` | Strided-mode divisor: keep every Nth source frame. |
9393
| `estimate.video.defaultResolution` | 640×360 | Per-frame resolution for dynamic tokens-per-frame; fallback for the `x-llm-d-video-resolution` header. |
9494
| `estimate.video.defaultDuration` | `10` | Video length in seconds for frame counting; fallback for the `x-llm-d-video-duration-seconds` header. |
9595
| `estimate.video.maxVideoTokens` || Overall placeholder cap for a video (0 = uncapped). |

pkg/epp/framework/plugins/requestcontrol/dataproducer/tokenizer/estimate_live_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ var qwen3vlVideoCase = videoModelCase{
7070
name: "qwen3vl",
7171
model: "Qwen/Qwen3-VL-30B-A3B-Instruct",
7272
cfg: &estimateConfig{Video: &videoEstimateConfig{
73-
Frames: &framesConfig{Mode: videoFramesModeSampled, SampleFPS: 2, MinFrames: 4, TemporalPatchSize: 2},
74-
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeDynamic, Factor: 32 * 32},
73+
Frames: &framesConfig{Mode: videoFramesModeSampled, MinFrames: 4, Sampled: &framesSampledMode{SampleFPS: 2, TemporalPatchSize: 2}},
74+
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeDynamic, Dynamic: &tokensPerFrameDynamicMode{Factor: 32 * 32}},
7575
MaxVideoTokens: 12288,
7676
}},
7777
}
@@ -86,8 +86,8 @@ var gemma4VideoCase = videoModelCase{
8686
name: "gemma4",
8787
model: "google/gemma-4-31B-it",
8888
cfg: &estimateConfig{Video: &videoEstimateConfig{
89-
Frames: &framesConfig{Mode: videoFramesModeStrided, FrameStride: 4, MaxFrames: 8},
90-
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, StaticToken: 296},
89+
Frames: &framesConfig{Mode: videoFramesModeStrided, MaxFrames: 8, Strided: &framesStridedMode{FrameStride: 4}},
90+
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, Static: &tokensPerFrameStaticMode{StaticToken: 296}},
9191
}},
9292
}
9393

pkg/epp/framework/plugins/requestcontrol/dataproducer/tokenizer/estimate_test.go

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ func TestVideoEstimator_Default(t *testing.T) {
243243
// per-frame count.
244244
func TestVideoEstimator_StaticTokensPerFrame(t *testing.T) {
245245
b := estimateBackend{vid: newVideoEstimator(&estimateConfig{Video: &videoEstimateConfig{
246-
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, StaticToken: 100},
246+
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, Static: &tokensPerFrameStaticMode{StaticToken: 100}},
247247
}})}
248248
tp, err := b.produce(context.Background(), chatVideoBody("https://example.com/clip.mp4"))
249249
require.NoError(t, err)
@@ -255,7 +255,7 @@ func TestVideoEstimator_StaticTokensPerFrame(t *testing.T) {
255255
// per-frame count for the default resolution.
256256
func TestVideoEstimator_DynamicFactor(t *testing.T) {
257257
b := estimateBackend{vid: newVideoEstimator(&estimateConfig{Video: &videoEstimateConfig{
258-
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeDynamic, Factor: 2048},
258+
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeDynamic, Dynamic: &tokensPerFrameDynamicMode{Factor: 2048}},
259259
}})}
260260
tp, err := b.produce(context.Background(), chatVideoBody("https://example.com/clip.mp4"))
261261
require.NoError(t, err)
@@ -269,8 +269,8 @@ func TestVideoEstimator_DynamicFactor(t *testing.T) {
269269
func TestVideoEstimator_SampledFrames(t *testing.T) {
270270
b := estimateBackend{vid: newVideoEstimator(&estimateConfig{Video: &videoEstimateConfig{
271271
DefaultDuration: 8,
272-
Frames: &framesConfig{Mode: videoFramesModeSampled, SampleFPS: 2},
273-
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, StaticToken: 10},
272+
Frames: &framesConfig{Mode: videoFramesModeSampled, Sampled: &framesSampledMode{SampleFPS: 2}},
273+
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, Static: &tokensPerFrameStaticMode{StaticToken: 10}},
274274
}})}
275275
tp, err := b.produce(context.Background(), chatVideoBody("https://example.com/clip.mp4"))
276276
require.NoError(t, err)
@@ -282,15 +282,29 @@ func TestVideoEstimator_SampledFrames(t *testing.T) {
282282
func TestVideoEstimator_StridedFramesCapped(t *testing.T) {
283283
b := estimateBackend{vid: newVideoEstimator(&estimateConfig{Video: &videoEstimateConfig{
284284
DefaultDuration: 10,
285-
Frames: &framesConfig{Mode: videoFramesModeStrided, DefaultSourceFPS: 24, FrameStride: 4, MaxFrames: 16},
286-
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, StaticToken: 100},
285+
Frames: &framesConfig{Mode: videoFramesModeStrided, MaxFrames: 16, Strided: &framesStridedMode{DefaultSourceFPS: 24, FrameStride: 4}},
286+
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, Static: &tokensPerFrameStaticMode{StaticToken: 100}},
287287
}})}
288288
tp, err := b.produce(context.Background(), chatVideoBody("https://example.com/clip.mp4"))
289289
require.NoError(t, err)
290290
// duration*sourceFPS/stride = 10*24/4 = 60, capped to 16; 16*100 tokens.
291291
assert.Equal(t, 16*100, tp.MultiModalFeatures[0].Length, "strided-frames video length")
292292
}
293293

294+
// TestVideoEstimator_StridedFramesFloored asserts strided frames apply the
295+
// minFrames floor when the strided count falls below it.
296+
func TestVideoEstimator_StridedFramesFloored(t *testing.T) {
297+
b := estimateBackend{vid: newVideoEstimator(&estimateConfig{Video: &videoEstimateConfig{
298+
DefaultDuration: 1,
299+
Frames: &framesConfig{Mode: videoFramesModeStrided, MinFrames: 8, Strided: &framesStridedMode{DefaultSourceFPS: 24, FrameStride: 4}},
300+
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, Static: &tokensPerFrameStaticMode{StaticToken: 100}},
301+
}})}
302+
tp, err := b.produce(context.Background(), chatVideoBody("https://example.com/clip.mp4"))
303+
require.NoError(t, err)
304+
// duration*sourceFPS/stride = 1*24/4 = 6, floored to 8; 8*100 tokens.
305+
assert.Equal(t, 8*100, tp.MultiModalFeatures[0].Length, "strided-frames floored video length")
306+
}
307+
294308
// TestVideoEstimator_MaxVideoTokens asserts the overall cap bounds the total
295309
// placeholder count.
296310
func TestVideoEstimator_MaxVideoTokens(t *testing.T) {
@@ -309,8 +323,8 @@ func TestVideoEstimator_Qwen3AndGemma4(t *testing.T) {
309323
qwen3 := estimateBackend{vid: newVideoEstimator(&estimateConfig{Video: &videoEstimateConfig{
310324
DefaultResolution: &resolution{Width: 640, Height: 480},
311325
DefaultDuration: 10,
312-
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeDynamic, Factor: 1024},
313-
Frames: &framesConfig{Mode: videoFramesModeSampled, SampleFPS: 2},
326+
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeDynamic, Dynamic: &tokensPerFrameDynamicMode{Factor: 1024}},
327+
Frames: &framesConfig{Mode: videoFramesModeSampled, Sampled: &framesSampledMode{SampleFPS: 2}},
314328
MaxVideoTokens: 100000,
315329
}})}
316330
tp, err := qwen3.produce(context.Background(), chatVideoBody("https://example.com/clip.mp4"))
@@ -320,8 +334,8 @@ func TestVideoEstimator_Qwen3AndGemma4(t *testing.T) {
320334

321335
gemma4 := estimateBackend{vid: newVideoEstimator(&estimateConfig{Video: &videoEstimateConfig{
322336
DefaultDuration: 10,
323-
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, StaticToken: 256},
324-
Frames: &framesConfig{Mode: videoFramesModeStrided, DefaultSourceFPS: 24, FrameStride: 4, MaxFrames: 16},
337+
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, Static: &tokensPerFrameStaticMode{StaticToken: 256}},
338+
Frames: &framesConfig{Mode: videoFramesModeStrided, MaxFrames: 16, Strided: &framesStridedMode{DefaultSourceFPS: 24, FrameStride: 4}},
325339
}})}
326340
tp, err = gemma4.produce(context.Background(), chatVideoBody("https://example.com/clip.mp4"))
327341
require.NoError(t, err)
@@ -428,8 +442,8 @@ func TestVideoEstimator_HeaderMetadataOverridesDefaults(t *testing.T) {
428442
// drive strided frame counting.
429443
func TestVideoEstimator_HeaderFPSStridedMode(t *testing.T) {
430444
b := estimateBackend{vid: newVideoEstimator(&estimateConfig{Video: &videoEstimateConfig{
431-
Frames: &framesConfig{Mode: videoFramesModeStrided, FrameStride: 2},
432-
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, StaticToken: 1},
445+
Frames: &framesConfig{Mode: videoFramesModeStrided, Strided: &framesStridedMode{FrameStride: 2}},
446+
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, Static: &tokensPerFrameStaticMode{StaticToken: 1}},
433447
}})}
434448
// strided frames = int(duration(3)*fps(30))/2 = 45.
435449
tp, err := b.produce(videoCtx(videoMetadata{duration: 3, fps: 30}), chatVideoBody("https://cdn.example.com/movie.mp4"))
@@ -441,8 +455,8 @@ func TestVideoEstimator_HeaderFPSStridedMode(t *testing.T) {
441455
// header duration but not the header source FPS (sampleFPS is authoritative).
442456
func TestVideoEstimator_SampledIgnoresHeaderFPS(t *testing.T) {
443457
b := estimateBackend{vid: newVideoEstimator(&estimateConfig{Video: &videoEstimateConfig{
444-
Frames: &framesConfig{Mode: videoFramesModeSampled, SampleFPS: 2},
445-
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, StaticToken: 1},
458+
Frames: &framesConfig{Mode: videoFramesModeSampled, Sampled: &framesSampledMode{SampleFPS: 2}},
459+
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, Static: &tokensPerFrameStaticMode{StaticToken: 1}},
446460
}})}
447461
// Same 3s duration, different source fps: both must yield 3*2 = 6.
448462
fps30, err := b.produce(videoCtx(videoMetadata{duration: 3, fps: 30}), chatVideoBody("https://example.com/clip.mp4"))
@@ -468,8 +482,8 @@ func TestVideoEstimator_NoHeadersUseDefaults(t *testing.T) {
468482
// isolates the frame-group count.
469483
func TestVideoEstimator_TemporalMergeAndMinFrames(t *testing.T) {
470484
b := estimateBackend{vid: newVideoEstimator(&estimateConfig{Video: &videoEstimateConfig{
471-
Frames: &framesConfig{Mode: videoFramesModeSampled, SampleFPS: 2, MinFrames: 4, TemporalPatchSize: 2},
472-
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, StaticToken: 1},
485+
Frames: &framesConfig{Mode: videoFramesModeSampled, MinFrames: 4, Sampled: &framesSampledMode{SampleFPS: 2, TemporalPatchSize: 2}},
486+
TokensPerFrame: &tokensPerFrameConfig{Mode: videoTPFModeStatic, Static: &tokensPerFrameStaticMode{StaticToken: 1}},
473487
}})}
474488
// 10s: 10*2 = 20 sampled frames, /2 temporal merge = 10 groups.
475489
long, err := b.produce(videoCtx(videoMetadata{duration: 10}), chatVideoBody("https://example.com/clip.mp4"))

pkg/epp/framework/plugins/requestcontrol/dataproducer/tokenizer/mm_estimate.go

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -231,17 +231,25 @@ func newVideoEstimator(cfg *estimateConfig) videoEstimator {
231231
}
232232
if vid.TokensPerFrame != nil {
233233
est.tpfMode = vid.TokensPerFrame.Mode
234-
est.factor = vid.TokensPerFrame.Factor
235-
est.staticToken = vid.TokensPerFrame.StaticToken
234+
if vid.TokensPerFrame.Dynamic != nil {
235+
est.factor = vid.TokensPerFrame.Dynamic.Factor
236+
}
237+
if vid.TokensPerFrame.Static != nil {
238+
est.staticToken = vid.TokensPerFrame.Static.StaticToken
239+
}
236240
}
237241
if vid.Frames != nil {
238242
est.framesMode = vid.Frames.Mode
239-
est.sampleFPS = vid.Frames.SampleFPS
240-
est.sourceFPS = vid.Frames.DefaultSourceFPS
241-
est.frameStride = vid.Frames.FrameStride
242-
est.maxFrames = vid.Frames.MaxFrames
243243
est.minFrames = vid.Frames.MinFrames
244-
est.temporalPatchSize = vid.Frames.TemporalPatchSize
244+
est.maxFrames = vid.Frames.MaxFrames
245+
if vid.Frames.Sampled != nil {
246+
est.sampleFPS = vid.Frames.Sampled.SampleFPS
247+
est.temporalPatchSize = vid.Frames.Sampled.TemporalPatchSize
248+
}
249+
if vid.Frames.Strided != nil {
250+
est.sourceFPS = vid.Frames.Strided.DefaultSourceFPS
251+
est.frameStride = vid.Frames.Strided.FrameStride
252+
}
245253
}
246254
return est
247255
}
@@ -266,13 +274,13 @@ func (e videoEstimator) placeholderCount(ctx context.Context, meta videoMetadata
266274
return tokens
267275
}
268276

269-
// frameCount returns the number of frame token-groups. Sampled mode samples
270-
// duration*sampleFPS frames, clamps to [minFrames, maxFrames], then merges every
271-
// temporalPatchSize frames into one group (models e.g. qwen3-vl, which samples
272-
// ~2fps and merges frame pairs). Strided mode takes
273-
// min(duration*sourceFPS/frameStride, maxFrames). A header-provided duration and
274-
// source FPS take precedence over configuration. sampleFPS is a model sampling
275-
// rate, not a source property, so it is never overridden.
277+
// frameCount returns the number of frame token-groups. Both modes clamp the raw
278+
// count to [minFrames, maxFrames]. Sampled mode samples duration*sampleFPS
279+
// frames, then merges every temporalPatchSize frames into one group (models e.g.
280+
// qwen3-vl, which samples ~2fps and merges frame pairs). Strided mode takes
281+
// duration*sourceFPS/frameStride. A header-provided duration and source FPS take
282+
// precedence over configuration. sampleFPS is a model sampling rate, not a source
283+
// property, so it is never overridden.
276284
func (e videoEstimator) frameCount(meta videoMetadata) int {
277285
duration := meta.duration
278286
if duration <= 0 {
@@ -294,6 +302,9 @@ func (e videoEstimator) frameCount(meta videoMetadata) int {
294302
stride = 1
295303
}
296304
n := int(duration*fps) / stride
305+
if e.minFrames > 0 && n < e.minFrames {
306+
n = e.minFrames
307+
}
297308
if e.maxFrames > 0 && n > e.maxFrames {
298309
n = e.maxFrames
299310
}

pkg/epp/framework/plugins/requestcontrol/dataproducer/tokenizer/tokenizer.go

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -144,32 +144,58 @@ type videoEstimateConfig struct {
144144
type tokensPerFrameConfig struct {
145145
// Mode selects "dynamic" (width*height/factor) or "static" (a constant count).
146146
Mode string `json:"mode,omitempty"`
147+
// Static configures the static (constant per-frame) mode.
148+
Static *tokensPerFrameStaticMode `json:"static,omitempty"`
149+
// Dynamic configures the dynamic (pixels/factor) mode.
150+
Dynamic *tokensPerFrameDynamicMode `json:"dynamic,omitempty"`
151+
}
152+
153+
// tokensPerFrameStaticMode is the static-mode parameter.
154+
type tokensPerFrameStaticMode struct {
155+
// StaticToken is the per-frame placeholder count.
156+
StaticToken int `json:"staticToken,omitempty"`
157+
}
158+
159+
// tokensPerFrameDynamicMode is the dynamic-mode parameter.
160+
type tokensPerFrameDynamicMode struct {
147161
// Factor maps a frame's pixels to placeholder tokens (width*height/factor).
148162
Factor int `json:"factor,omitempty"`
149-
// StaticToken is the per-frame placeholder count in static mode.
150-
StaticToken int `json:"staticToken,omitempty"`
151163
}
152164

153-
// framesConfig configures how many frames are counted from a video.
165+
// framesConfig configures how many frames are counted from a video. MinFrames
166+
// and MaxFrames clamp the count in both modes; the mode sub-structs hold the
167+
// mode-specific knobs.
154168
type framesConfig struct {
155169
// Mode selects "sampled" (duration*sampleFPS) or "strided"
156-
// (min(duration*sourceFPS/frameStride, maxFrames)).
170+
// (duration*sourceFPS/frameStride).
157171
Mode string `json:"mode,omitempty"`
158-
// SampleFPS is the sampling rate in sampled mode.
172+
// MinFrames floors the frame count. Zero means no floor.
173+
MinFrames int `json:"minFrames,omitempty"`
174+
// MaxFrames caps the frame count. Zero means uncapped.
175+
MaxFrames int `json:"maxFrames,omitempty"`
176+
// Sampled configures the sampled (duration*sampleFPS) mode.
177+
Sampled *framesSampledMode `json:"sampled,omitempty"`
178+
// Strided configures the strided (duration*sourceFPS/frameStride) mode.
179+
Strided *framesStridedMode `json:"strided,omitempty"`
180+
}
181+
182+
// framesSampledMode configures the sampled frame-count mode.
183+
type framesSampledMode struct {
184+
// SampleFPS is the sampling rate.
159185
SampleFPS float64 `json:"sampleFPS,omitempty"`
160-
// DefaultSourceFPS is the fallback source frame rate in strided mode, used
161-
// when the x-llm-d-video-fps header is absent.
186+
// TemporalPatchSize merges every N sampled frames into one token group,
187+
// modeling temporal patch merging (e.g. qwen3-vl uses 2). Values < 2 apply
188+
// no merging.
189+
TemporalPatchSize int `json:"temporalPatchSize,omitempty"`
190+
}
191+
192+
// framesStridedMode configures the strided frame-count mode.
193+
type framesStridedMode struct {
194+
// DefaultSourceFPS is the fallback source frame rate, used when the
195+
// x-llm-d-video-fps header is absent.
162196
DefaultSourceFPS float64 `json:"defaultSourceFPS,omitempty"`
163-
// FrameStride keeps every Nth source frame in strided mode.
197+
// FrameStride keeps every Nth source frame.
164198
FrameStride int `json:"frameStride,omitempty"`
165-
// MaxFrames caps the sampled/strided frame count. Zero means uncapped.
166-
MaxFrames int `json:"maxFrames,omitempty"`
167-
// MinFrames floors the sampled frame count (sampled mode). Zero means no floor.
168-
MinFrames int `json:"minFrames,omitempty"`
169-
// TemporalPatchSize merges every N sampled frames into one token group
170-
// (sampled mode), modeling temporal patch merging (e.g. qwen3-vl uses 2).
171-
// Values < 2 apply no merging.
172-
TemporalPatchSize int `json:"temporalPatchSize,omitempty"`
173199
}
174200

175201
// PluginFactory is the factory function for the tokenizer plugin.

0 commit comments

Comments
 (0)