本文へ移動
cccskills
無料GitHub で公開日本語紹介

swift-mlx-lm

Apple Silicon上でMLXを使い、Swiftから言語モデルや画像対応モデルを動かす方法を扱います。ストリーミング生成、ツール呼び出し、LoRAによる追加学習、埋め込み生成やモデル移植も支援します。

原文原文の説明を見る

MLX Swift LM - Run LLMs and VLMs on Apple Silicon using MLX. Covers local inference, streaming, wired memory coordination, tool calling, LoRA fine-tuning, embeddings, and model porting.

kellyvv/PhoneClawスター 1,284(GitHub 1,284 + cccskills 0)
連携ツールHugging Face

実行スコア 未検証

インストール方法を見る

こんなときに便利

  • SwiftアプリでローカルLLMを動かしたいとき
  • モデルの応答を逐次表示したいとき
  • 画像や動画をモデルに入力したいとき
  • LoRAでモデルを追加学習したいとき
  • MLXモデルをSwiftへ移植したいとき

日本語での紹介

できること

Apple Silicon向けの機械学習フレームワークMLXを使い、Swiftアプリで大規模言語モデル(LLM)や画像・動画を扱える視覚言語モデル(VLM)を実行する手順を案内します。モデルの読み込みから、会話履歴を保つチャット、逐次的に結果を受け取るストリーミング生成まで扱います。

モデルに関数やツールを呼び出させる方法、複数の推論処理でメモリ使用を調整する方法、LoRAなどのアダプターを使った追加学習、テキスト埋め込みの生成も対象です。Python版MLX-LMのモデル構成をSwiftへ移植する際の参照情報も含まれます。

こんなときに便利

Apple Silicon搭載端末でモデルをローカル実行するSwift開発者や、会話生成をアプリに組み込みたい人に向いています。画像・動画入力、ツール連携、追加学習、検索向け埋め込みなど、用途に応じた実装方法を確認できます。

使い方の例

  • 「SwiftアプリでローカルLLMのチャットを実装して」
  • 「モデルの応答をストリーミング表示する方法を教えて」
  • 「MLX-LMのモデルをSwiftに移植する手順を整理して」

注意点

Apple Silicon向けのMLX Swiftパッケージを前提とします。モデル読み込み例ではHugging Face関連のSwiftパッケージを利用します。

この紹介文は、公開されている SKILL.md をもとに AI(OpenAI Codex)が作成しました。正確な仕様は下の原文を確認してください。

含まれるファイル(13)

  • SKILL.md14.4 KB
  • references/concurrency.md8.3 KB
  • references/embeddings.md8.0 KB
  • references/generation.md3.9 KB
  • references/kv-cache.md7.1 KB
  • references/lora-adapters.md7.6 KB
  • references/model-container.md8.7 KB
  • references/model-porting.md13.4 KB
  • references/supported-models.md7.2 KB
  • references/tokenizer-chat.md7.4 KB
  • references/tool-calling.md8.9 KB
  • references/training.md9.0 KB
  • references/wired-memory.md3.7 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

mlx-swift-lm Skill

1. Overview & Triggers

mlx-swift-lm is a Swift package for running Large Language Models (LLMs) and Vision-Language Models (VLMs) on Apple Silicon using MLX. It supports local inference, streaming generation, wired-memory coordination, tool calling, LoRA/DoRA fine-tuning, and embeddings.

When to Use This Skill

  • Running LLM/VLM inference on macOS/iOS with Apple Silicon
  • Streaming text generation from local models
  • Coordinating concurrent inference with wired-memory policies and tickets
  • Tool calling / function calling with models
  • LoRA adapter training and fine-tuning
  • Text embeddings for RAG/semantic search
  • Porting model architectures from Python MLX-LM to Swift

Architecture Overview

MLXLMCommon     - Core infra (ModelContainer, ChatSession, Evaluate, KVCache, wired memory helpers)
MLXLLM          - Text-only LLM support (Llama, Qwen, Gemma, Phi, DeepSeek, etc.)
MLXVLM          - Vision-Language Models (Qwen-VL, PaliGemma, Gemma3, etc.)
MLXEmbedders    - Embedding models and pooling utilities

2. Key File Reference

PurposeFile Path
Thread-safe model wrapperLibraries/MLXLMCommon/ModelContainer.swift
Simplified chat APILibraries/MLXLMCommon/ChatSession.swift
Generation & streaming APIsLibraries/MLXLMCommon/Evaluate.swift
KV cache typesLibraries/MLXLMCommon/KVCache.swift
Wired-memory policiesLibraries/MLXLMCommon/WiredMemoryPolicies.swift
Wired-memory measurement helpersLibraries/MLXLMCommon/WiredMemoryUtils.swift
Model configurationLibraries/MLXLMCommon/ModelConfiguration.swift
Chat message typesLibraries/MLXLMCommon/Chat.swift
Tool call processingLibraries/MLXLMCommon/Tool/ToolCallFormat.swift
Concurrency utilitiesLibraries/MLXLMCommon/Utilities/SerialAccessContainer.swift
LLM factory & registryLibraries/MLXLLM/LLMModelFactory.swift
VLM factory & registryLibraries/MLXVLM/VLMModelFactory.swift
LoRA configurationLibraries/MLXLMCommon/Adapters/LoRA/LoRAContainer.swift
LoRA trainingLibraries/MLXLLM/LoraTrain.swift

3. Quick Start

LLM Chat (Simplest API)

import MLXLLM
import MLXLMCommon
import MLXLMHuggingFace  // from swift-huggingface-mlx
import MLXLMTokenizers   // from swift-tokenizers-mlx

let modelContainer = try await LLMModelFactory.shared.loadContainer(
    from: HubClient.default,
    using: TokenizersLoader(),
    configuration: .init(id: "mlx-community/Qwen3-4B-4bit")
)

let session = ChatSession(modelContainer)

let response = try await session.respond(to: "What is Swift?")
print(response)

for try await chunk in session.streamResponse(to: "Explain structured concurrency") {
    print(chunk, terminator: "")
}

VLM with Image

import MLXVLM
import MLXLMCommon
import MLXLMHuggingFace  // from swift-huggingface-mlx
import MLXLMTokenizers   // from swift-tokenizers-mlx

let modelContainer = try await VLMModelFactory.shared.loadContainer(
    from: HubClient.default,
    using: TokenizersLoader(),
    configuration: .init(id: "mlx-community/Qwen2-VL-2B-Instruct-4bit")
)

let session = ChatSession(modelContainer)
let image = UserInput.Image.url(imageURL)

let response = try await session.respond(
    to: "Describe this image",
    image: image,
    video: nil
)

Embeddings

import MLXEmbedders
import MLXEmbeddersHuggingFace  // from swift-huggingface-mlx
import MLXLMTokenizers          // from swift-tokenizers-mlx

let container = try await loadModelContainer(
    from: HubClient.default,
    using: TokenizersLoader(),
    configuration: ModelConfiguration(id: "mlx-community/bge-small-en-v1.5-mlx")
)

let embeddings = await container.perform { model, tokenizer, pooler in
    let tokens = tokenizer.encode(text: "Hello world")
    let input = MLXArray(tokens).expandedDimensions(axis: 0)
    let output = model(input)
    let pooled = pooler(output, normalize: true)
    eval(pooled)
    return pooled
}

4. Primary Workflow: LLM Inference

ChatSession API (Recommended)

ChatSession manages conversation history and KV cache automatically:

let session = ChatSession(
    modelContainer,
    instructions: "You are a helpful assistant",
    generateParameters: GenerateParameters(maxTokens: 500, temperature: 0.7)
)

let r1 = try await session.respond(to: "What is 2+2?")
let r2 = try await session.respond(to: "And if you multiply that by 3?")

await session.clear()

Streaming with ModelContainer.generate(...)

For lower-level control, prepare UserInput and generate directly:

let userInput = UserInput(prompt: "Hello")
let lmInput = try await modelContainer.prepare(input: userInput)

let stream = try await modelContainer.generate(
    input: lmInput,
    parameters: GenerateParameters()
)

for await generation in stream {
    switch generation {
    case .chunk(let text):
        print(text, terminator: "")
    case .toolCall(let call):
        print("Tool call: \(call.function.name)")
    case .info(let info):
        print("\nStop reason: \(info.stopReason)")
        print("\(info.tokensPerSecond) tok/s")
    }
}

Generation API Surface (Evaluate.swift)

Use these depending on your control needs:

  • generate(input:..., context:..., wiredMemoryTicket:) -> AsyncStream<Generation>: decoded text + tool calls.
  • generateTask(..., wiredMemoryTicket:) -> (AsyncStream<Generation>, Task<Void, Never>): same output, plus task handle for deterministic cleanup when consumers stop early.
  • generateTokens(..., wiredMemoryTicket:) -> AsyncStream<TokenGeneration>: raw token IDs.
  • generateTokensTask(..., wiredMemoryTicket:) -> (AsyncStream<TokenGeneration>, Task<Void, Never>): raw tokens + task handle.
  • GenerateStopReason: .stop, .length, .cancelled in final .info.

See references/generation.md for full patterns.

Tool Calling

struct WeatherInput: Codable { let location: String }
struct WeatherOutput: Codable { let temperature: Double; let conditions: String }

let weatherTool = Tool<WeatherInput, WeatherOutput>(
    name: "get_weather",
    description: "Get current weather",
    parameters: [.required("location", type: .string, description: "City name")]
) { _ in
    WeatherOutput(temperature: 22.0, conditions: "Sunny")
}

let userInput = UserInput(
    prompt: .text("What's the weather in Tokyo?"),
    tools: [weatherTool.schema]
)

let lmInput = try await modelContainer.prepare(input: userInput)
let stream = try await modelContainer.generate(input: lmInput, parameters: GenerateParameters())

for await generation in stream {
    switch generation {
    case .chunk(let text):
        print(text, terminator: "")
    case .toolCall(let call):
        let result = try await call.execute(with: weatherTool)
        print("\nWeather: \(result.conditions)")
    case .info:
        break
    }
}

See references/tool-calling.md for multi-turn tool loops.

GenerateParameters

let params = GenerateParameters(
    maxTokens: 1000,            // nil = unlimited
    maxKVSize: 4096,            // Sliding window (RotatingKVCache)
    kvBits: 4,                  // Quantized cache (4 or 8)
    kvGroupSize: 64,            // Quantization group size
    quantizedKVStart: 0,        // Token index to start KV quantization
    temperature: 0.7,           // 0 = greedy / argmax
    topP: 0.9,                  // Nucleus sampling
    repetitionPenalty: 1.1,     // Penalize repeats
    repetitionContextSize: 20,  // Penalty window
    prefillStepSize: 512        // Prompt prefill chunk size
)

Wired Memory (Optional)

Use policy tickets to coordinate concurrent inference memory:

let policy = WiredSumPolicy()
let ticket = policy.ticket(size: estimatedBytes, kind: .active)

let userInput = UserInput(prompt: "Summarize this text")
let lmInput = try await modelContainer.prepare(input: userInput)

let stream = try await modelContainer.generate(
    input: lmInput,
    parameters: GenerateParameters(),
    wiredMemoryTicket: ticket
)

for await generation in stream {
    if case .chunk(let text) = generation {
        print(text, terminator: "")
    }
}

For policy selection, reservations, and measurement-based budgeting, see references/wired-memory.md.

Prompt Caching / History Re-hydration

let history: [Chat.Message] = [
    .system("You are helpful"),
    .user("Hello"),
    .assistant("Hi there!")
]

let session = ChatSession(modelContainer, history: history)

5. Secondary Workflow: VLM Inference

Image Input Types

let imageFromURL = UserInput.Image.url(fileURL)
let imageFromCI = UserInput.Image.ciImage(ciImage)
let imageFromArray = UserInput.Image.array(mlxArray)

Video Input

let videoFromURL = UserInput.Video.url(videoURL)
let videoFromAsset = UserInput.Video.avAsset(avAsset)
let videoFromFrames = UserInput.Video.frames(videoFrames)

let response = try await session.respond(to: "What happens in this video?", video: videoFromURL)

Multiple Images

let images: [UserInput.Image] = [.url(url1), .url(url2)]
let response = try await session.respond(to: "Compare these two images", images: images, videos: [])

VLM-Specific Processing

let session = ChatSession(
    modelContainer,
    processing: UserInput.Processing(resize: CGSize(width: 512, height: 512))
)

6. Best Practices

DO

// DO: Prefer ChatSession for multi-turn chat UX
let session = ChatSession(modelContainer)

// DO: Prepare UserInput before container-level generation
let userInput = UserInput(prompt: "Hello")
let lmInput = try await modelContainer.prepare(input: userInput)

// DO: Use task-handle variants for early-stop scenarios
let (stream, task) = generateTask(
    promptTokenCount: lmInput.text.tokens.size,
    modelConfiguration: context.configuration,
    tokenizer: context.tokenizer,
    iterator: iterator
)
for await item in stream {
    if shouldStop { break }
}
await task.value

// DO: Use wired tickets when coordinating concurrent workloads
let ticket = WiredSumPolicy().ticket(size: estimatedBytes)
let _ = try await modelContainer.generate(input: lmInput, parameters: params, wiredMemoryTicket: ticket)

DON'T

// DON'T: Skip prepare(input:) before container-level generation.
// ModelContainer.generate expects LMInput, not UserInput.

// DON'T: Share MLXArray across tasks (not Sendable)
let array = MLXArray(...)
Task { _ = array.sum() } // wrong

// DON'T: Ignore task completion after early-break on low-level streams
for await item in stream {
    if shouldStop { break }
}
// await task.value is required for deterministic cleanup

Thread Safety

  • ModelContainer is Sendable and thread-safe.
  • ChatSession is not thread-safe; use one session per task/flow.
  • MLXArray is not Sendable; keep it inside one isolation domain or use SendableBox transfer patterns.

Memory Management

let slidingWindow = GenerateParameters(maxKVSize: 4096)
let quantizedKV = GenerateParameters(kvBits: 4, kvGroupSize: 64)
await session.clear()

7. Reference Links

ReferenceWhen to Use
references/model-container.mdLoading models, ModelContainer API, ModelConfiguration
references/generation.mdgenerate, generateTask, raw token streaming APIs
references/wired-memory.mdWired tickets, policies, budgeting, reservations
references/kv-cache.mdCache types, memory optimization, cache serialization
references/concurrency.mdThread safety, SerialAccessContainer, async patterns
references/tool-calling.mdFunction calling, tool formats, ToolCallProcessor
references/tokenizer-chat.mdTokenizer, Chat.Message, EOS tokens
references/supported-models.mdModel families, registries, model-specific config
references/lora-adapters.mdLoRA/DoRA/QLoRA, loading adapters
references/training.mdLoRATrain API, fine-tuning
references/embeddings.mdEmbeddingModel, pooling, use cases
references/model-porting.mdPorting models from Python MLX-LM to Swift

8. Deprecated Patterns Summary

If you see...Use instead...
generate(... didGenerate:) callbackAsyncStream-based generation APIs
perform { model, tokenizer in }perform { context in }
TokenIterator(prompt: MLXArray)TokenIterator(input: LMInput)
ModelRegistry typealiasLLMRegistry or VLMRegistry
createAttentionMask(h:cache:[KVCache]?)createAttentionMask(h:cache:KVCache?)

9. Automatic vs Manual Configuration

Automatic Behaviors

FeatureDetails
EOS token loadingLoaded from config.json
EOS overridegeneration_config.json > config.json > defaults
EOS mergingAll sources merged at generation time
EOS detectionStops generation when EOS encountered
Chat template applicationApplied by tokenizer / processor path
Tool call format detectionInferred from model_type in config.json
Cache type selectionDriven by GenerateParameters (maxKVSize, kvBits)
Tokenizer loadingLoaded automatically from model assets
Model weight loadingDownloaded and loaded from Hugging Face/local directory

Optional Configuration

FeatureWhen to Configure
extraEOSTokensModel has unlisted stop tokens
toolCallFormatOverride auto-detected tool parser format
maxKVSizeEnable sliding window cache
kvBits, kvGroupSize, quantizedKVStartEnable and tune KV quantization
prefillStepSizeTune prompt prefill chunking/perf tradeoff
wiredMemoryTicketCoordinate policy-based wired-memory limits

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

コメント

ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。

まだコメントはありません。最初のコメントを書いてみませんか。

送信するとすぐに公開されます。メールアドレスや電話番号などの個人情報は書かないでください。URL は 2 つまでです。

0 / 1000 文字

このフォームは reCAPTCHA で保護されており、Google のプライバシーポリシーと利用規約が適用されます。

同じリポジトリのスキル

Calendar

無料概要あり

予定の作成や日程の確認を行い、予定の件数や空き時間をもとに、今日や今週の忙しさ、会議を入れられる時間帯を整理するスキルです。

  • 会議や約束を登録したいとき
  • 今日や明日の予定を確認したいとき
  • 今週の忙しさを把握したいとき
kellyvv/PhoneClaw1,2842026年8月6日 更新

Clipboard

無料概要あり

システムのクリップボードからテキストを読み取り、頼まれた文章をコピーできます。内容の確認や、別の場所へ貼り付ける準備に使えます。

  • コピーした内容を確認したいとき
  • 文章をクリップボードへ移したいとき
  • 貼り付ける前に内容を読みたいとき
kellyvv/PhoneClaw1,2842026年8月6日 更新

Contacts

無料概要あり

連絡先を検索して電話番号やメールアドレスを確認し、新規登録や情報の更新、削除も行えます。名前や番号を手がかりに、連絡先をまとめて管理できます。

  • 連絡先の電話番号を調べたいとき
  • 新しい連絡先を保存したいとき
  • 登録済みの情報を更新したいとき
kellyvv/PhoneClaw1,2842026年8月6日 更新

Health

無料概要あり

HealthKitの歩数や運動、睡眠、心拍、体重を読み取り、端末内で日次の確認や期間を指定した健康データの要約を行います。

  • 今日や昨日の歩数を確認したいとき
  • 睡眠や心拍の記録を振り返りたいとき
  • 週単位・月単位の健康状況を知りたいとき
kellyvv/PhoneClaw1,2842026年8月6日 更新

Reminders

無料概要あり

指定した内容と時刻でリマインダーを作成します。いつ何を知らせるかを確認してから登録するため、日時を含む予定や用事を忘れないようにできます。

  • 用事を指定時刻に思い出したいとき
  • 買い物の予定を登録したいとき
  • 締め切り前の作業を忘れたくないとき
kellyvv/PhoneClaw1,2842026年8月6日 更新

swift-mlx

無料概要あり

Apple Silicon向けの機械学習フレームワークMLXをSwiftで使い、配列計算やニューラルネットワークの構築、勾配計算、モデル学習、Metalカーネルの実装とメモリ調整を進めるためのスキルです。

  • MLX配列を作成・操作したいとき
  • Swiftでニューラルネットワークを構築したいとき
  • 勾配計算でモデルを学習したいとき
kellyvv/PhoneClaw1,2842026年8月6日 更新

kellyvv のスキルをすべて見る

このスキルの問題を報告する