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
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,27 @@ The format is based on Keep a Changelog.

### Runtime

- added `vision-chat-q38-27b`, pinned to Qwen's official Apache-2.0
Qwen3.8-27B BF16 checkpoint. The native Qwen-family runtime now exposes the
dense 27B hybrid-attention model's text, code, and image understanding,
published 262,144-token context, thinking and sampling defaults, complete
generation EOS set, and Qwen3.8 image sizing. The 55.59 GB pull is explicit
rather than an inference-time auto-download and is gated to 64 GB unified
memory with 96 GB recommended. The coding benchmark accepts Qwen3.8 as an
explicit model and scores only visible code after separating reasoning. The
runtime can load the checkpoint's embedded dense MTP head for opt-in
three-token greedy drafts. It remains experimental because BF16 multi-token
verification can diverge from serial greedy decode; the default, sampled,
and constrained requests retain the target-only path.
- added the separate `vision-chat-q38-27b-4bit` managed lane, pinned to LM
Studio's 4-bit/group-64 MLX conversion. Its 19.47 GB composite pull mounts
only Qwen's pinned final BF16 shard under `mtp/`, retaining the official MTP
head and license without duplicating the 55.59 GB checkpoint. Target-only
decode remains the default; `MERERUN_Q35_MTP_SPECULATION=1` enables the
explicitly experimental fast path. A deterministic 24-task stride through
the official HumanEval set scored 20/24 in both modes with the same failures;
one failing case changed length by three tokens, so MTP remains opt-in rather
than claiming exact greedy-output parity.
- refreshed the owned MLX and mlx-swift forks onto current upstream cutoffs,
retained the scoped 1-bit, NVFP4, CUDA, stream-safety, and M4/H3 patches,
made the generated NAX optimizations source-reproducible, and regenerated
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ current flags.
| Area | Public commands | Current surface |
| --- | --- | --- |
| Images and LoRAs | `image generate`, `image train-lora`, `adapter list`, `adapter pull` | Klein, ZImage, HiDream O1, Krea 2, Ideogram 4, and Bonsai; text-to-image, edits, multiple references, structured prompts, local Krea/Klein training, and checksum-pinned public adapters |
| Text, code, and agents | `text chat`, `text code`, `text embed`, `text anonymize`, `text train-lora`, `agent` | Local chat and tool use, including Bonsai 27B binary/ternary vision chat; code generation, embeddings, PII redaction, text LoRA training, and guided local-agent setup |
| Text, code, and agents | `text chat`, `text code`, `text embed`, `text anonymize`, `text train-lora`, `agent` | Local chat and tool use, including Qwen3.8 27B BF16/4-bit and Bonsai 27B binary/ternary vision chat; code generation, embeddings, PII redaction, text LoRA training, and guided local-agent setup |
| Vision understanding | `vision caption`, `inspect`, `face`, `ground`, `segment`, `track`, `track-live`, `pose`, `flow`, `ocr` | Captioning and VQA, local face detection/identity embeddings, LightOn/GLM/Infinity OCR, Falcon grounding, SAM 3.1 segmentation and tracking, body/hand/face landmarks, and dense optical flow |
| Depth, geometry, and 3D | `vision depth-video`, `geometry`, `geometry-multiview`, `image-to-3d*`; `image reconstruct-3d*` | Video Depth Anything, MoGe-2, Depth Anything 3, TripoSR, InstantMesh, and TRELLIS.2; depth/confidence EXRs, cameras, point clouds, 3DGS initialization, OBJ, PLY, GLB, and PBR voxel artifacts |
| Audio enhancement | `audio enhance` | Native AP-BWE speech bandwidth extension and UniverSR general-audio super-resolution to hashed 48 kHz mono WAVs |
Expand Down
13 changes: 11 additions & 2 deletions Sources/MereRunCLI/Commands/ModelBenchmarkCodeCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ struct ModelBenchmarkCode: AsyncParsableCommand {
guard let spec = ManagedModelCatalog.spec(for: modelID) else {
return CodeBenchmarkModelResult.missing(model: modelID, reason: "Unknown model id.")
}
guard spec.category == .textCode else {
guard Self.supportsCodingBenchmark(spec) else {
return CodeBenchmarkModelResult.missing(model: modelID, reason: "Model is not a text-code model.")
}
guard let installedURL = ManagedModelResolver.resolveInstalledModel(id: modelID) else {
Expand Down Expand Up @@ -344,7 +344,7 @@ struct ModelBenchmarkCode: AsyncParsableCommand {
do {
let response = try await generate(request)
let generationSeconds = Date().timeIntervalSince(generationStart)
let candidate = task.candidateProgram(from: response.response)
let candidate = task.candidateProgram(from: Self.scoredCodeResponse(response))
let execution = try CodeExecutionSandbox.runPython(
program: task.testProgram(candidateProgram: candidate),
python: python,
Expand Down Expand Up @@ -422,6 +422,15 @@ struct ModelBenchmarkCode: AsyncParsableCommand {
|| ManagedModelCatalog.spec(for: modelID)?.validationKind == .q35
}

static func supportsCodingBenchmark(_ spec: ManagedModelSpec) -> Bool {
spec.category == .textCode
|| Q35Resources.isQ38ModelId(spec.id)
}

static func scoredCodeResponse(_ response: ChatResponse) -> String {
ChatReasoningMarkup.splitThinkBlocks(in: response.response).visibleContent
}

static let systemPrompt = """
You are completing Python programming benchmark tasks. Return only valid Python code.
Do not include Markdown fences, prose, comments about your approach, or test code.
Expand Down
57 changes: 56 additions & 1 deletion Sources/MereRunCore/ManagedModelCatalog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1652,6 +1652,56 @@ public enum ManagedModelCatalog {
defaultCLICommands: ["chat", "api serve"],
apiProfile: .q36(contextWindow: Q35Resources.defaultContextLength)
),
ManagedModelSpec(
id: Q35Resources.q38TwentySevenBModelId,
category: .visionChat,
installShape: .directoryRoot,
hubFallback: Q35Resources.profile(for: Q35Resources.q38TwentySevenBModelId)?.hubFallbackConfig,
upstreamRepoId: Q35Resources.q38TwentySevenBUpstreamRepoId,
upstreamRevision: Q35Resources.q38TwentySevenBUpstreamRevision,
validationKind: .q35,
runtimeAutoDownloadAllowed: false,
estimatedDownloadBytes: Q35Resources.q38TwentySevenBEstimatedDownloadBytes,
defaultCLICommands: [
"text chat",
"api serve",
"model benchmark chat",
"model benchmark code",
"model benchmark vlm",
],
apiProfile: .q36(contextWindow: Q35Resources.q38TwentySevenBContextLength)
),
ManagedModelSpec(
id: Q35Resources.q38TwentySevenB4BitModelId,
category: .visionChat,
installShape: .directoryRoot,
hubFallback: Q35Resources.profile(
for: Q35Resources.q38TwentySevenB4BitModelId
)?.hubFallbackConfig,
mountedHubFallbacks: [
MountedHubFallbackConfig(
destinationPath: Q35Resources.q38MTPComponentPath,
hubFallback: HubFallbackConfig(
repoId: Q35Resources.q38TwentySevenBUpstreamRepoId,
revision: Q35Resources.q38TwentySevenBUpstreamRevision,
patterns: Q35Resources.q38MTPComponentSnapshotPatterns
)
),
],
upstreamRepoId: Q35Resources.q38TwentySevenB4BitUpstreamRepoId,
upstreamRevision: Q35Resources.q38TwentySevenB4BitUpstreamRevision,
validationKind: .q35,
runtimeAutoDownloadAllowed: false,
estimatedDownloadBytes: Q35Resources.q38TwentySevenB4BitEstimatedDownloadBytes,
defaultCLICommands: [
"text chat",
"api serve",
"model benchmark chat",
"model benchmark code",
"model benchmark vlm",
],
apiProfile: .q36(contextWindow: Q35Resources.q38TwentySevenBContextLength)
),
ManagedModelSpec(
id: Q35Resources.bonsai27B1BitModelId,
category: .textChat,
Expand Down Expand Up @@ -3513,7 +3563,12 @@ public extension ManagedModelSpec {
case .lagunaDFlash:
return LagunaResources.missingDFlashFiles(rootURL: rootURL, fileManager: fileManager)
case .q35:
return Q35Resources(rootURL: rootURL).validate(fileManager: fileManager)
let resources = Q35Resources(rootURL: rootURL)
var missing = resources.validate(fileManager: fileManager)
if id == Q35Resources.q38TwentySevenB4BitModelId {
missing.append(contentsOf: resources.validateQ38MTPComponent(fileManager: fileManager))
}
return missing
case .lfm2:
return LFM2Resources(rootURL: rootURL).validate(
fileManager: fileManager,
Expand Down
14 changes: 14 additions & 0 deletions Sources/MereRunCore/ManagedModelSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,20 @@ public enum ManagedModelCapabilityCatalog {
recommended: 32,
setup: true
),
descriptor(
Q35Resources.q38TwentySevenBModelId,
"Qwen3.8 27B vision chat",
"Runs Qwen's official dense 27B BF16 vision-language checkpoint through the native Qwen-family runtime.",
minimum: 64,
recommended: 96
),
descriptor(
Q35Resources.q38TwentySevenB4BitModelId,
"Qwen3.8 27B 4-bit vision chat",
"Runs LM Studio's pinned MLX 4-bit conversion with Qwen's official MTP shard available for explicit speculative decode.",
minimum: 32,
recommended: 48
),
descriptor(
Q35Resources.bonsai27B1BitModelId,
"Bonsai 27B 1-bit vision chat",
Expand Down
33 changes: 32 additions & 1 deletion Sources/MereRunCore/MereRunModelManifest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public struct MereRunModelManifest: Codable, Hashable, Sendable {
case lfm2 = "lfm2"
/// Poolside Laguna family via the native Swift runtime.
case laguna = "laguna"
/// Q35 family (Qwen3.5 hybrid MoE + hybrid attention).
/// Qwen3.5-family dense or MoE models with hybrid attention.
case qwen35HybridMoE = "qwen3.5-hybrid-moe"
/// SAM image segmentation family.
case samSegmentation = "sam-segmentation"
Expand Down Expand Up @@ -1086,6 +1086,37 @@ public struct MereRunModelManifest: Codable, Hashable, Sendable {
upstreamRepoId: "\(Q35Resources.q36NanoUpstreamRepoId)@\(Q35Resources.q36NanoUpstreamRevision)",
createdAt: createdAt
)
case .q38TwentySevenB:
return MereRunModelManifest(
id: modelID.rawValue,
engine: .qwen35HybridMoE,
family: .qwen,
tier: .latest,
variant: .standard,
precision: .bf16,
defaults: nil,
supports: [.chat, .codeGeneration, .visionChat],
components: q35TextComponents,
upstreamRepoId: "\(Q35Resources.q38TwentySevenBUpstreamRepoId)"
+ "@\(Q35Resources.q38TwentySevenBUpstreamRevision)",
createdAt: createdAt
)
case .q38TwentySevenB4Bit:
return MereRunModelManifest(
id: modelID.rawValue,
engine: .qwen35HybridMoE,
family: .qwen,
tier: .latest,
variant: .standard,
precision: .int4,
quantization: Quantization(bits: 4, groupSize: 64, scheme: "mlx-affine"),
defaults: nil,
supports: [.chat, .codeGeneration, .visionChat],
components: q35TextComponents,
upstreamRepoId: "\(Q35Resources.q38TwentySevenB4BitUpstreamRepoId)"
+ "@\(Q35Resources.q38TwentySevenB4BitUpstreamRevision)",
createdAt: createdAt
)
case .bonsai27B1Bit:
return MereRunModelManifest(
id: modelID.rawValue,
Expand Down
4 changes: 3 additions & 1 deletion Sources/MereRunCore/MereRunModelValidator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,9 @@ public enum MereRunModelValidator {
return .liquid
}
if modelId == ModelResolver.ModelID.q36Nano.rawValue
|| modelId == ModelResolver.ModelID.q36NanoGGUF.rawValue {
|| modelId == ModelResolver.ModelID.q36NanoGGUF.rawValue
|| modelId == ModelResolver.ModelID.q38TwentySevenB.rawValue
|| modelId == ModelResolver.ModelID.q38TwentySevenB4Bit.rawValue {
return .qwen
}
return nil
Expand Down
2 changes: 2 additions & 0 deletions Sources/MereRunCore/ModelResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public struct ModelResolver {
case nemotron35LightningDSpark = "text-chat-nemotron-35-lightning-dspark"
case ltxGemma3TwelveB4Bit = "text-encoder-ltx-gemma3-12b-4bit"
case q36Nano = "text-chat-q36-nano"
case q38TwentySevenB = "vision-chat-q38-27b"
case q38TwentySevenB4Bit = "vision-chat-q38-27b-4bit"
case bonsai27B1Bit = "text-chat-bonsai-27b-1bit"
case bonsai27B2Bit = "text-chat-bonsai-27b-2bit"
case lfm25A1B8Bit = "text-chat-lfm25-a1b-8bit"
Expand Down
22 changes: 22 additions & 0 deletions Sources/MereRunCore/Q35/Q35Config.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
import Foundation

public struct Q35GenerationConfig: Codable, Sendable, Hashable {
public let eosTokenIds: [Int]

private enum CodingKeys: String, CodingKey {
case eosTokenId = "eos_token_id"
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let single = try? container.decode(Int.self, forKey: .eosTokenId) {
eosTokenIds = [single]
} else {
eosTokenIds = try container.decodeIfPresent([Int].self, forKey: .eosTokenId) ?? []
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(eosTokenIds, forKey: .eosTokenId)
}
}

public struct Q35QuantizationConfig: Codable, Sendable, Hashable {
public let groupSize: Int
public let bits: Int
Expand Down
Loading
Loading