Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ The format is based on Keep a Changelog.
requested floor, and second-based floors account for whole vocoder hops so a
nominal 10-second request no longer decodes to 9.996 seconds.

### Video

- added the checksum-pinned LightX2V MiniMax-H3 Ref2VA Turbo 4-step v0.1
adapter to `adapter pull`. The native Ref2VA path selects the published
four-evaluation, video/audio shift 12/3, alpha-8 recipe and fuses its 312
PEFT pairs after the managed INT8 transformer is admitted and expanded to
resident BF16.

## 0.37.0 - 2026-08-13

This release adds two substantial native Apple Silicon runtimes: LiquidAI's
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -859,12 +859,15 @@ swift run mere.run video generate \

# Managed 8-bit Ref2VA preserves reference order semantically
swift run mere.run model pull video-minimax-h3-ref2va-mlx --accept-model-license
swift run mere.run adapter pull minimax-h3-lightx2v-ref2v-4step-v0.1
swift run mere.run video generate \
"keep the subject, borrow the camera move, and follow the vocal rhythm" \
--model video-minimax-h3-ref2va-mlx \
--reference image:./subject.png \
--reference video:./camera-and-soundtrack.mp4 \
--reference audio:./voice.wav \
--h3-adapter minimax-h3-lightx2v-ref2v-4step-v0.1 \
--h3-weight-mode resident-bf16 \
--num-frames 124 \
--output ./referenced-h3.mp4

Expand Down
23 changes: 19 additions & 4 deletions Sources/MereRunCLI/Commands/VideoCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1167,13 +1167,28 @@ struct VideoGenerate: AsyncParsableCommand {
}
let h3Resources = MiniMaxH3Resources(rootURL: resolvedRootURL)
let h3Configuration = try h3Resources.loadConfiguration()
if h3Adapter != nil, !h3Resources.usesShardedBF16Transformer {
throw ValidationError("--h3-adapter currently requires the MiniMax-H3 BF16 FL2VA model.")
}
let h3AdapterBaseModelID = h3Configuration.task == MiniMaxH3TurboAdapter.Task.ref2va.rawValue
? ModelResolver.ModelID.miniMaxH3Ref2VAMLX.rawValue
: ModelResolver.ModelID.miniMaxH3FL2VABF16MLX.rawValue
let resolvedH3Adapter = try ManagedAdapterArgumentResolver.resolve(
h3Adapter,
baseModelID: ModelResolver.ModelID.miniMaxH3FL2VABF16MLX.rawValue
baseModelID: h3AdapterBaseModelID
).map { URL(fileURLWithPath: $0).standardizedFileURL }
let h3AdapterRecipe = resolvedH3Adapter.map(MiniMaxH3TurboAdapter.inferenceRecipe(for:))
if let h3AdapterRecipe,
!h3AdapterRecipe.supports(task: h3Configuration.task) {
throw ValidationError(
"MiniMax-H3 adapter \(h3AdapterRecipe.name) requires \(h3AdapterRecipe.task.rawValue), not \(h3Configuration.task)."
)
}
if h3AdapterRecipe?.task == .fl2va, !h3Resources.usesShardedBF16Transformer {
throw ValidationError("MiniMax-H3 FL2VA adapters require the BF16 FL2VA model.")
}
if h3AdapterRecipe?.task == .ref2va, h3WeightMode == .quantized {
throw ValidationError(
"MiniMax-H3 Ref2VA Turbo requires resident BF16 weights; use --h3-weight-mode resident-bf16."
)
}
let parsedReferences = try parseMiniMaxH3References()
let parsedFrameInputs = try parseMiniMaxH3FrameInputs()
if h3Configuration.task == "fl2va", !parsedReferences.isEmpty {
Expand Down
94 changes: 75 additions & 19 deletions Sources/MereRunCLI/Support/VideoGenerationPreflight.swift
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,20 @@ struct VideoGenerationPreflightAnalyzer {
return resources.validate().isEmpty && (try? resources.loadConfiguration()) != nil
}

private var usesMiniMaxH3Ref2VA: Bool {
let requested = input.model.trimmingCharacters(in: .whitespacesAndNewlines)
if requested == ModelResolver.ModelID.miniMaxH3Ref2VAMLX.rawValue {
return true
}
if requested == ModelResolver.ModelID.miniMaxH3FL2VAMLX.rawValue
|| requested == ModelResolver.ModelID.miniMaxH3FL2VABF16MLX.rawValue {
return false
}
let candidate = input.modelRoot ?? input.model
let resources = MiniMaxH3Resources(rootURL: URL(fileURLWithPath: candidate).standardizedFileURL)
return (try? resources.loadConfiguration().task) == MiniMaxH3TurboAdapter.Task.ref2va.rawValue
}

private var usesAudioConditioning: Bool {
guard let audio = input.audio else { return false }
return !audio.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
Expand Down Expand Up @@ -801,13 +815,40 @@ struct VideoGenerationPreflightAnalyzer {
message: "--h3-adapter-strength must be > 0."
))
}
if input.h3Adapter != nil, !input.references.isEmpty {
diagnostics.append(PreflightDiagnostic(
id: "h3_adapter_ref2va_unsupported",
severity: .blocker,
title: "MiniMax-H3 Turbo does not support Ref2VA",
message: "Use the Turbo adapter for FL2VA text or keyframe generation without --reference."
))
if let h3AdapterInferenceRecipe {
let requestedTask: MiniMaxH3TurboAdapter.Task = usesMiniMaxH3Ref2VA ? .ref2va : .fl2va
if h3AdapterInferenceRecipe.task != requestedTask {
diagnostics.append(PreflightDiagnostic(
id: "h3_adapter_task_mismatch",
severity: .blocker,
title: "MiniMax-H3 adapter task does not match the model",
message: "Adapter \(h3AdapterInferenceRecipe.name) requires \(h3AdapterInferenceRecipe.task.rawValue), not \(requestedTask.rawValue)."
))
}
if h3AdapterInferenceRecipe.task == .fl2va, !input.references.isEmpty {
diagnostics.append(PreflightDiagnostic(
id: "h3_fl2va_adapter_with_references",
severity: .blocker,
title: "MiniMax-H3 FL2VA adapter cannot use references",
message: "Use the FL2VA adapter for text or keyframe generation without --reference."
))
}
if h3AdapterInferenceRecipe.task == .ref2va, input.references.isEmpty {
diagnostics.append(PreflightDiagnostic(
id: "h3_ref2va_adapter_without_references",
severity: .blocker,
title: "MiniMax-H3 Ref2VA adapter requires references",
message: "Add at least one ordered image or video --reference."
))
}
if h3AdapterInferenceRecipe.task == .ref2va, input.h3WeightMode == "quantized" {
diagnostics.append(PreflightDiagnostic(
id: "h3_ref2va_adapter_requires_resident_bf16",
severity: .blocker,
title: "MiniMax-H3 Ref2VA Turbo requires resident BF16 weights",
message: "Use --h3-weight-mode resident-bf16 on a machine with sufficient memory."
))
}
}
if input.prompt.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
diagnostics.append(
Expand Down Expand Up @@ -1054,22 +1095,37 @@ struct VideoGenerationPreflightAnalyzer {
))
}
if input.h3Adapter != nil, usesMiniMaxH3Geometry {
let usesBF16: Bool
if let path = model.path {
usesBF16 = MiniMaxH3Resources(
rootURL: URL(fileURLWithPath: path).standardizedFileURL
).usesShardedBF16Transformer
} else {
usesBF16 = model.requested == ModelResolver.ModelID.miniMaxH3FL2VABF16MLX.rawValue
}
if !usesBF16 {
let expectedBaseModelID = usesMiniMaxH3Ref2VA
? ModelResolver.ModelID.miniMaxH3Ref2VAMLX.rawValue
: ModelResolver.ModelID.miniMaxH3FL2VABF16MLX.rawValue
if let reference = input.h3Adapter,
let spec = ManagedAdapterCatalog.spec(for: reference),
spec.baseModelID != expectedBaseModelID {
diagnostics.append(PreflightDiagnostic(
id: "h3_adapter_requires_bf16",
id: "h3_adapter_base_model_mismatch",
severity: .blocker,
title: "MiniMax-H3 Turbo requires the BF16 base model",
message: "Use --model \(ModelResolver.ModelID.miniMaxH3FL2VABF16MLX.rawValue)."
title: "MiniMax-H3 adapter base model does not match",
message: "Adapter \(spec.id) requires \(spec.baseModelID), not \(expectedBaseModelID)."
))
}
if h3AdapterInferenceRecipe?.task == .fl2va {
let usesBF16: Bool
if let path = model.path {
usesBF16 = MiniMaxH3Resources(
rootURL: URL(fileURLWithPath: path).standardizedFileURL
).usesShardedBF16Transformer
} else {
usesBF16 = model.requested == ModelResolver.ModelID.miniMaxH3FL2VABF16MLX.rawValue
}
if !usesBF16 {
diagnostics.append(PreflightDiagnostic(
id: "h3_adapter_requires_bf16",
severity: .blocker,
title: "MiniMax-H3 FL2VA Turbo requires the BF16 base model",
message: "Use --model \(ModelResolver.ModelID.miniMaxH3FL2VABF16MLX.rawValue)."
))
}
}
if let steps = input.steps,
let recipe = h3AdapterInferenceRecipe,
!recipe.supports(schedulePointCount: steps) {
Expand Down
23 changes: 23 additions & 0 deletions Sources/MereRunCore/ManagedAdapterCatalog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ public enum ManagedAdapterCatalog {
public static let miniMaxH3LightX2VEightStepV1ID = "minimax-h3-lightx2v-8step-v1"
public static let miniMaxH3LightX2VFourStepV1_768pID = "minimax-h3-lightx2v-4step-v1-768p"
public static let miniMaxH3LightX2VV1Revision = "e6346777701aa2b64d42ed058cdd71ae00e7cd52"
public static let miniMaxH3LightX2VRef2VFourStepV01ID = "minimax-h3-lightx2v-ref2v-4step-v0.1"
public static let miniMaxH3LightX2VRef2VFourStepV01Revision = "5d1d4829fe614c1b93fcfd9cc7718e9ba71f73e1"
public static let ltx25PixelSpatialUpscalerID = "ltx25-pixel-spatial-upscaler-x2"
public static let ltx25PixelSpatialUpscalerRevision = "74c4e68ee7dd99f3997d5a1bb1a3784941822222"

Expand Down Expand Up @@ -218,6 +220,27 @@ public enum ManagedAdapterCatalog {
sha256: "1bdabc2e9fce20b1db563b96bcf6e46adcad4c1964f423676436bf266cc7416c"
)
),
ManagedAdapterSpec(
id: miniMaxH3LightX2VRef2VFourStepV01ID,
title: "MiniMax-H3 Ref2VA Turbo 4-step v0.1 (LightX2V)",
version: String(miniMaxH3LightX2VRef2VFourStepV01Revision.prefix(12)),
summary: "LightX2V four-evaluation PEFT LoRA for native MiniMax-H3 Ref2VA.",
baseModelID: ModelResolver.ModelID.miniMaxH3Ref2VAMLX.rawValue,
format: MiniMaxH3TurboAdapter.lightX2VFormat,
license: "Apache-2.0 (adapter); MiniMax-H3 Community License (base model)",
upstreamRevision: miniMaxH3LightX2VRef2VFourStepV01Revision,
releaseManifestURL: URL(
string: "https://huggingface.co/lightx2v/Minimax-h3-Turbo/commit/\(miniMaxH3LightX2VRef2VFourStepV01Revision)"
)!,
downloadURL: URL(
string: "https://huggingface.co/lightx2v/Minimax-h3-Turbo/resolve/\(miniMaxH3LightX2VRef2VFourStepV01Revision)/minimax_h3_ref2v_turbo_4step_v0.1_bf16.safetensors?download=true"
)!,
artifact: ModelArtifactPin(
filename: "minimax_h3_ref2v_turbo_4step_v0.1_bf16.safetensors",
byteCount: 1_383_677_768,
sha256: "9e642fc8749c74f8da5e2382877ab5c7aa37b9a73b7fd0d6d457bd1b3cb1ae99"
)
),
ManagedAdapterSpec(
id: ltx25PixelSpatialUpscalerID,
title: "LTX-2.5 Pixel Spatial Upscaler x2",
Expand Down
52 changes: 40 additions & 12 deletions Sources/MereRunCore/MiniMaxH3/MiniMaxH3Generator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -663,17 +663,26 @@ public struct MiniMaxH3GenerationOptions: Sendable, Hashable {
!references.contains(where: { $0.kind != .audio }) {
throw MiniMaxH3GeneratorError.invalidOptions("an audio reference must be paired with an image or video")
}
if adapterURL != nil {
let adapterInferenceRecipe = adapterURL.map(MiniMaxH3TurboAdapter.inferenceRecipe(for:))
if let adapterInferenceRecipe {
guard adapterStrength > 0 else {
throw MiniMaxH3GeneratorError.invalidOptions("adapter strength must be greater than zero")
}
guard references.isEmpty else {
throw MiniMaxH3GeneratorError.invalidOptions(
"MiniMax-H3 Turbo supports FL2VA text/keyframe generation, not Ref2VA references"
)
switch adapterInferenceRecipe.task {
case .fl2va:
guard references.isEmpty else {
throw MiniMaxH3GeneratorError.invalidOptions(
"The selected MiniMax-H3 FL2VA adapter cannot be used with Ref2VA references"
)
}
case .ref2va:
guard !references.isEmpty else {
throw MiniMaxH3GeneratorError.invalidOptions(
"The selected MiniMax-H3 Ref2VA adapter requires ordered references"
)
}
}
}
let adapterInferenceRecipe = adapterURL.map(MiniMaxH3TurboAdapter.inferenceRecipe(for:))
let resolvedSteps: Int
if let steps {
resolvedSteps = steps
Expand Down Expand Up @@ -884,12 +893,8 @@ public final class MiniMaxH3Generator: @unchecked Sendable {
))
}
)
if let adapterURL {
guard resources.usesShardedBF16Transformer else {
throw MiniMaxH3GeneratorError.invalidOptions(
"MiniMax-H3 Turbo currently requires the BF16 FL2VA model"
)
}
let adapterInferenceRecipe = adapterURL.map(MiniMaxH3TurboAdapter.inferenceRecipe(for:))
if let adapterURL, resources.usesShardedBF16Transformer {
try MiniMaxH3TurboAdapter.install(
url: adapterURL,
into: transformer,
Expand Down Expand Up @@ -924,6 +929,23 @@ public final class MiniMaxH3Generator: @unchecked Sendable {
))
_ = transformer.materializeResidentBF16()
}
if let adapterURL, !resources.usesShardedBF16Transformer {
guard adapterInferenceRecipe?.task == .ref2va else {
throw MiniMaxH3GeneratorError.invalidOptions(
"MiniMax-H3 FL2VA adapters require the BF16 FL2VA model"
)
}
guard transformer.usesResidentBF16 else {
throw MiniMaxH3GeneratorError.invalidOptions(
"MiniMax-H3 Ref2VA Turbo requires resident BF16 weights; use --h3-weight-mode resident-bf16 on a machine with sufficient memory"
)
}
try MiniMaxH3TurboAdapter.install(
url: adapterURL,
into: transformer,
strength: adapterStrength
)
}
if retainsRuntime {
retainedDenoisingRuntime = (cacheKey, transformer, resolvedAdaLNCache)
}
Expand Down Expand Up @@ -1563,6 +1585,12 @@ public final class MiniMaxH3Generator: @unchecked Sendable {
let missing = resources.validate()
guard missing.isEmpty else { throw MiniMaxH3GeneratorError.missingModelFiles(missing) }
let configuration = try resources.loadConfiguration()
if let adapterInferenceRecipe = options.adapterInferenceRecipe,
!adapterInferenceRecipe.supports(task: configuration.task) {
throw MiniMaxH3GeneratorError.invalidOptions(
"MiniMax-H3 adapter recipe \(adapterInferenceRecipe.name) requires \(adapterInferenceRecipe.task.rawValue), not \(configuration.task)"
)
}
if configuration.task == "ref2va" {
return try generateRef2VA(
options: options,
Expand Down
25 changes: 25 additions & 0 deletions Sources/MereRunCore/MiniMaxH3/MiniMaxH3TurboAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ public enum MiniMaxH3TurboAdapter {
public static let lightX2VExpectedPairCount = 312
public static let recommendedSchedulePointCount = 5

public enum Task: String, Sendable, Hashable {
case fl2va
case ref2va
}

public struct InferenceRecipe: Sendable, Hashable {
public let name: String
public let task: Task
public let defaultSchedulePointCount: Int
public let supportedSchedulePointCounts: Set<Int>
public let videoFlowShift: Float?
Expand All @@ -20,10 +26,15 @@ public enum MiniMaxH3TurboAdapter {
public func supports(schedulePointCount: Int) -> Bool {
supportedSchedulePointCounts.contains(schedulePointCount)
}

public func supports(task value: String) -> Bool {
task.rawValue == value.lowercased()
}
}

public static let fourEvaluationRecipe = InferenceRecipe(
name: "four-evaluation",
task: .fl2va,
defaultSchedulePointCount: 5,
supportedSchedulePointCounts: [5],
videoFlowShift: nil,
Expand All @@ -33,6 +44,7 @@ public enum MiniMaxH3TurboAdapter {

public static let lightX2VEightStepV1Recipe = InferenceRecipe(
name: "lightx2v-v1-8-step",
task: .fl2va,
defaultSchedulePointCount: 9,
supportedSchedulePointCounts: [5, 9],
videoFlowShift: 12,
Expand All @@ -42,19 +54,32 @@ public enum MiniMaxH3TurboAdapter {

public static let lightX2VFourStepV1_768pRecipe = InferenceRecipe(
name: "lightx2v-v1-4-step-768p",
task: .fl2va,
defaultSchedulePointCount: 5,
supportedSchedulePointCounts: [5],
videoFlowShift: 6,
audioFlowShift: 3,
lightX2VAlpha: 128
)

public static let lightX2VRef2VFourStepV01Recipe = InferenceRecipe(
name: "lightx2v-ref2v-v0.1-4-step",
task: .ref2va,
defaultSchedulePointCount: 5,
supportedSchedulePointCounts: [5],
videoFlowShift: 12,
audioFlowShift: 3,
lightX2VAlpha: 8
)

public static func inferenceRecipe(for url: URL) -> InferenceRecipe {
inferenceRecipe(filename: url.lastPathComponent)
}

public static func inferenceRecipe(filename: String) -> InferenceRecipe {
switch filename.lowercased() {
case "minimax_h3_ref2v_turbo_4step_v0.1_bf16.safetensors":
lightX2VRef2VFourStepV01Recipe
case "minimax_h3_fl2v_turbo_8step_v1.0_bf16.safetensors":
lightX2VEightStepV1Recipe
case "minimax_h3_fl2v_turbo_4step_v1.0_768p_bf16.safetensors":
Expand Down
Loading