Apple Foundation Models: 온디바이스 LLM 프레임워크 완벽 해설
Foundation Models 프레임워크를 쓰면 앱이 Apple Intelligence를 구동하는 바로 그 온디바이스 대규모 언어 모델에 직접, 무료로, 오프라인으로 접근할 수 있어요1. API 키도, 토큰당 청구서도, 네트워크 왕복도, 기기 밖으로 빠져나가는 데이터도 없어요. 예전 같으면 클라우드 LLM과 개인정보 검토를 의미했던 종류의 기능이, 이제는 비용이 사실상 0으로 수렴해요. 대신 내주는 건 역량이에요. 온디바이스 모델은 작고, 컨텍스트 윈도우는 유한하며, 프레임워크는 무엇을 하고 무엇을 하지 않을지에 대해 단단한 선을 그어 두었어요. 그 선이 어디 있는지를 아는 것이 전부예요.
이 글은 프레임워크 자체에 대한 레퍼런스예요. 여러분이 실제로 호출하는 타입들, 이 프레임워크를 쓸 가치가 있게 만드는 단 하나의 기능, 그리고 멈춰 서서 더 큰 무언가로 손을 뻗어야 하는 지점을 다뤄요.
TL;DR
LanguageModelSession이 진입점이에요. 하나 만들고,respond(to:)를 호출하면, 텍스트가 돌아와요. 멀티턴 컨텍스트는 세션 안에 살아 있고, 단일 턴 작업은 매번 새 세션을 받아요2.- Guided generation이 이 프레임워크를 쓸 이유예요. Swift 타입에
@Generable을 붙이면, 파싱해야 하는 문자열 대신 값이 채워지고 타입 검사까지 끝난 그 타입을 모델이 돌려줘요3. Tool프로토콜을 쓰면 모델이 생성 도중에 여러분의 코드를 호출해 데이터를 가져오거나 동작을 수행한 다음, 그 결과를 답변에 다시 엮어 넣을 수 있어요4.- 무엇이든 하기 전에
SystemLanguageModel.default.availability를 확인하세요. 자격이 없는 기기에서는, Apple Intelligence가 꺼져 있을 때는, 또는 모델을 내려받는 동안에는 모델이 존재하지 않아요5. - 컨텍스트 윈도우는 실재하고 작아요.
SystemLanguageModel.default.contextSize는 프롬프트와 응답이 함께 나눠 쓰는 토큰 예산을 알려줘요6. 이를 염두에 두고 설계하지 않으면 세션이 예외를 던져요. - iOS 26과 Apple Intelligence를 지원하는 기기가 필요해요. 그 기준 아래에서는 프레임워크 자체가 존재하지 않아요.
이 프레임워크는 무엇이고, 무엇이 아닌가
Foundation Models는 클라우드 엔드포인트를 감싼 래퍼가 아니에요. 모델은 기기 위에 살고, 운영체제와 함께 배포되며, Neural Engine을 상대로 실행돼요. 이 단 하나의 사실이 API의 모든 설계 결정을, 그리고 그것을 사용하는 여러분의 모든 결정을 좌우해요.
여러분이 얻는 것: 텍스트 생성, 요약, 분류, 추출, 짧은 형식의 재작성, 그리고 구조화된 출력. 전부 온디바이스이고 전부 무료예요. 여러분이 얻지 못하는 것: 프런티어 모델이에요. Apple은 이 온디바이스 모델을 열린 형태의 추론이나 긴 문서 분석, 캐물어 볼 수 있는 세상 지식이 아니라, 앱 안에서 이뤄지는 집중된 언어 작업을 위해 만들었어요. Apple도 그렇게 말하고 있고, 이 규정이 중요한 이유는 그렇지 않으면 API가 여러분이 어기도록 내버려 둘 기대치를 미리 설정해 주기 때문이에요1.
곤경에 빠지지 않게 해 주는 사고 모델은 이거예요. 온디바이스 모델을, 텍스트를 다듬는 데는 탁월하지만 사실을 아는 데는 형편없는, 빠르고 사적이며 공짜인 인턴이라고 생각하세요. 재료와 명확한 작업을 건네주세요. 답할 방법이 없는 질문을 던지지 마세요.
LanguageModelSession: 시작점
모든 상호작용은 세션에서 시작해요.
import FoundationModels
let session = LanguageModelSession()
let response = try await session.respond(to: "Summarize this review in one sentence: \(reviewText)")
print(response.content)
세션은 대화 상태를 담고 있어요. respond(to:)를 호출할 때마다 진행 중인 트랜스크립트에 내용이 덧붙으므로, 계속 유지하는 세션은 앞서 무슨 일이 있었는지 기억해요. 채팅 기능이라면 그게 바로 원하는 동작이에요. 서로 독립적인 일회성 작업(이걸 요약해, 저걸 분류해)이라면, 오래된 컨텍스트가 새어 들어와 토큰 예산을 갉아먹지 않도록 호출마다 새 세션을 만드세요2.
respond(to:)는 async throws예요. 모델이 작업하는 동안 일시 중단되고, 요청이 컨텍스트 윈도우를 넘어설 때, 모델을 쓸 수 없을 때, 또는 가드레일이 콘텐츠를 거부할 때 예외를 던져요. 이 하나하나가 무시할 예외적 상황이 아니라 여러분이 처리해야 할 실제 분기예요.
반응성 좋은 UI를 위해서라면 기다리지 말고 스트리밍하세요. streamResponse(to:)는 모델이 출력을 만들어 내는 대로 부분 결과를 내보내는데, 이렇게 하면 3초간의 멈춤이 글자가 형성되는 대로 나타나는 텍스트로 바뀌어요7.
Guided generation: 이 프레임워크를 쓸 이유가 되는 기능
여기가 입장료를 낼 만한 부분이에요. 대부분의 LLM 통합은 코드의 3분의 1을 모델에게서 유효한 JSON을 끌어내려고 어르는 데 쓰고, 나머지 3분의 2를 그래도 실패할 때를 대비해 방어하는 데 써요. Foundation Models는 그 작업을 통째로 없애 줘요.
Swift 타입에 @Generable을 붙이고, 세션에게 그 타입을 생성하라고 요청하면, 모델은 값이 채워지고 타입 안전한 그 타입의 인스턴스를 돌려줘요3:
@Generable
struct Recipe {
@Guide(description: "The dish name")
let title: String
@Guide(description: "Ingredients, each as 'quantity item'")
let ingredients: [String]
@Guide(description: "Total minutes, start to finish", .range(5...240))
let minutes: Int
}
let session = LanguageModelSession()
let response = try await session.respond(
to: "A weeknight pasta for two.",
generating: Recipe.self
)
let recipe = response.content // a Recipe, not a String
파싱도 없어요. JSONDecoder도 없어요. 잘못된 출력에 대비한 재시도 루프도 없어요. @Guide 매크로는 개별 필드에 제약을 걸어요. 모델이 지시로 읽는 설명, 그리고 숫자 범위나 출력이 반드시 일치해야 하는 정규 표현식 같은 선택적 한계를 함께요8. 프레임워크는 5에서 240 사이의 숫자를 모델에게 정중히 부탁하는 게 아니에요. 그 필드가 그 외의 값으로는 돌아올 수 없도록 디코딩 자체를 제약해요.
이것이 강제하는 규율이 진짜 가치예요. 여러분은 출력 타입을 먼저, Swift로, 컴파일러가 검사하는 상태에서 설계해요. 모델은 여러분이 역공학으로 풀어내야 하는 산문을 돌려주는 대신, 여러분이 정의한 계약을 채워요. 추출, 양식 채우기, 그리고 언어를 데이터로 바꾸는 모든 기능에서, guided generation은 데모와 출시 가능한 코드 사이의 차이예요.
알아 둘 만한 제어 하나: respond(to:generating:)는 includeSchemaInPrompt를 true로 기본 설정하는데, 이는 여러분 타입의 형태를 프롬프트에 주입해 모델이 그쪽으로 치우치도록 만들어요. 모델이 학습에서, 또는 세션의 앞선 턴에서 이미 그 형식을 알고 있는 경우가 아니라면 켜진 채로 두세요. 모델이 본 적 없는 형식에서 토큰을 아끼려고 이걸 끄는 것이 바로 쓰레기 같은 결과를 받는 방법이에요9.
Tool calling: 모델이 여러분의 코드에 접근하게 하기
Guided generation은 나오는 것의 모양을 잡아요. Tool calling은 들어가는 것을 바꿔요. 도구란 모델이 생성 도중에 호출할 수 있는 여러분 코드의 한 조각이에요. 가지고 있지 않은 정보를 가져오거나 동작을 수행한 다음, 그 결과를 써서 답변을 이어 가도록요4.
도구는 Tool 프로토콜을 따라요. name, 모델이 언제 호출할지 판단하려고 읽는 description, @Generable로 선언된 Arguments 타입, 그리고 실제 작업을 수행하는 call(arguments:) 메서드로 이뤄져요4:
struct FindContacts: Tool {
let name = "findContacts"
let description = "Find a specific number of contacts from the address book"
@Generable
struct Arguments {
@Guide(description: "How many contacts to return", .range(1...10))
let count: Int
}
func call(arguments: Arguments) async throws -> [String] {
// Fetch contacts, return formatted names.
}
}
let session = LanguageModelSession(tools: [FindContacts()])
let response = try await session.respond(to: "Draft a dinner invite to three of my contacts.")
흐름은 이래요. 모델은 연락처가 필요하다고 판단하고, 검증된 count로 여러분의 도구를 호출하고, 여러분은 데이터를 돌려주고, 모델은 실제 이름을 써서 초대장을 작성해요. 인수는 guided generation과 똑같은 메커니즘을 거쳐 타입 검사가 끝난 상태로 도착하므로, 모델의 의도를 자유 텍스트에서 파싱해 낼 일이 결코 없어요. 도구 설명은 모델이 그 도구로 손을 뻗는 시점에 대한 여러분의 유일한 지렛대예요. 그러니 다른 엔지니어가(아무런 다른 맥락 없이) 읽고 올바르게 써야 하는 함수 문서처럼 쓰세요.
여기가 또한 Foundation Models가 에이전트 이야기의 나머지와 만나는 이음새이기도 해요. 온디바이스 모델이 호출하는 도구와 Apple Intelligence가 호출하는 App Intent는 형태는 같지만 표면이 다른 것이에요. 이름이 붙고, 설명이 달리고, 타입이 정해진 역량이라는 점에서요. 그 역량을 한 번 설계하면 둘 모두를 통해 노출할 수 있어요.
Availability: 건너뛸 수 없는 확인
모델이 늘 거기 있는 건 아니에요. Apple Intelligence를 지원하지 않는 기기에서는, 사용자가 그것을 꺼 두었을 때는, 그리고 운영체제가 아직 모델 자산을 내려받는 중인 구간에서는 모델이 존재하지 않아요. 모델이 존재한다고 가정하는 코드를 출시하면, 여러분이 한 번도 테스트해 본 적 없는 사용자 집단에서 그 코드는 충돌하거나, 조용히 품질이 떨어지거나, 멈춰 버려요.
SystemLanguageModel.default.availability를 확인하고 그 이유에 따라 분기하세요5:
switch SystemLanguageModel.default.availability {
case .available:
// Show the intelligence feature.
case .unavailable(.deviceNotEligible):
// Hide it. This device will never have the model.
case .unavailable(.appleIntelligenceNotEnabled):
// Prompt the user to turn on Apple Intelligence.
case .unavailable(.modelNotReady):
// Downloading or otherwise not ready yet. Try again later.
case .unavailable(let other):
// Unknown reason. Fail closed.
}
이 세 가지 이유는 서로 다른 세 가지 제품 대응을 요구하고, 이들을 뭉뚱그리는 것이 이런 기능들이 망가진 것처럼 느껴지게 만드는 가장 흔한 길이에요. deviceNotEligible은 영구적이에요. 기능을 숨기고, 보채지 마세요. appleIntelligenceNotEnabled은 사용자가 제어하는 설정이에요. 한 번의 안내는 정당해요. modelNotReady는 일시적이에요. 다시 시도하고, 오류를 보여 주지 마세요. 사용할 수 없는 경로를 행복한 경로와 똑같은 정성으로 만드세요. 실제 기기의 한 부분에게는 그것이 유일한 경로니까요.
모델을 쓸 수 있고 요청이 들어올 것을 알 때는, 세션의 prewarm()이 모델을 미리 데워서 첫 실제 응답이 더 빨리 도착하게 해 줘요10. 사용자가 곧 행동을 취하려는 화면에서는 그럴 가치가 있고, 추측에 기대 호출하면 낭비예요.
컨텍스트 윈도우, 그리고 그것으로 부족해지는 지점
SystemLanguageModel.default.contextSize는 모델이 그 안에서 작업하는 토큰 예산을 알려주는데, 이 예산은 공유돼요. 프롬프트와 응답이 합쳐서 그 안에 들어맞아야 해요6. 이 숫자는 클라우드 모델에 비해 작고, 실제 입력에서는 그것을 금방 체감하게 돼요. 긴 문서, 대화 기록 전체, 부피 큰 도구 결과. 이 중 어느 것이든 예산을 터뜨려 respond가 예외를 던지게 만들 수 있어요.
두 가지 실패 양상이 뒤따르고, 둘 다 여러분이 막아야 할 몫이에요. 첫째는 느린 잠식이에요. 멀티턴 세션은 한 턴이 더 얹어지면 넘쳐 버리는 지점까지 트랜스크립트를 쌓아 가요. 관련 없는 작업은 새 세션으로 시작하고 턴마다 입력을 가볍게 유지해서 관리하세요. 둘째는 한 번의 지나치게 큰 요청이에요. 20페이지짜리 PDF는 들어맞지 않아요, 끝이에요. 그것을 잘게 나누고, 조각들을 요약하고, 그 요약들을 두고 추론하세요(LLM 엔지니어들이 잘 아는 그 map-reduce예요). 아니면 그 작업이 온디바이스 모델에 맞지 않는 모양이라는 사실을 받아들이세요.
컨텍스트 윈도우는 이 프레임워크에서 정작 중요한 결정, 즉 언제 온디바이스에 머물고 언제 떠날지에 대한 가장 깨끗한 신호예요.
Foundation Models를 쓰지 말아야 할 때
이 프레임워크는 무료이고, 사적이고, 오프라인이어서 어디에나 손을 뻗고 싶어지게 만들어요. 참으세요. 다음과 같은 경우에는 이걸 지나쳐 손을 뻗으세요:
- 진짜 추론이나 폭넓은 세상 지식이 필요할 때. 온디바이스 모델은 설계상 작아요. 열린 형태의 추론, 코드 생성, 깊은 분석은 프런티어 클라우드 모델의 몫이에요. 온디바이스 모델에게 그것들을 요청하면 자신만만하면서도 틀린 답이 나와요.
- 입력이 컨텍스트 윈도우에 들어맞지 않고 잘게 나누면 의미가 파괴될 때. 어떤 작업은 모든 것을 한 번에 봐야 해요.
- 여러분이 제어하는 모델이 필요할 때: 특정 체크포인트, 파인튜닝, 커스텀 가중치, OS 업데이트를 가로지르는 결정론적 버전 관리. Apple은 자기네 일정에 따라 모델을 배포하고 업데이트해요, 여러분의 일정이 아니라요.
- iOS 26보다 낮거나 자격이 없는 기기일 때. 프레임워크가 그냥 거기 없고, 가용성 확인이 실행될 때마다 그렇다고 알려줄 거예요.
프레임워크가 다루지 않는 온디바이스 사례(커스텀 모델, 여러분 자신의 가중치, 기기 위에서의 학습)에는 그 아래 계층인 Core ML과 Apple의 MLX가 있어요. 진짜로 규모가 필요한 사례에는 개인정보 경계 뒤의 클라우드 LLM이 여전히 정직한 답이에요. Foundation Models는 둘 중 어느 것의 대체물도 아니에요. 이미 손안에 쥐고 있는 텍스트에 대한 집중된 언어 작업에는 가장 먼저 손을 뻗기에 옳은 도구이고, 그 밖의 모든 것에는 그른 도구예요.
이 프레임워크가 보상하는 기술은 프롬프트 솜씨가 아니에요. 그것은 범위를 보는 안목이에요. 모델이 잘하는 작업을 먹여 주고, 정확히 필요한 것을 담아내는 @Generable 타입을 설계하고, 작업이 기기를 넘어서는 순간을 알아차리는 것이죠. 그런 직관을 가지고 만들면 온디바이스 모델은 놀랄 만큼 많은 실제 작업을 공짜로 해내요. 그걸 무시하면, 입력이 토큰 하나만큼 더 길어졌던 모든 사용자에게서 망가지는 기능을 출시하게 돼요.
-
Apple Developer, “Foundation Models” framework overview. Apple describes the framework as access to the on-device model that powers Apple Intelligence, suited to focused language tasks such as text generation, summarization, classification, and structured output rather than open-ended reasoning or world knowledge. ↩↩
-
Apple Developer, “LanguageModelSession” and “Generating content and performing tasks with Foundation Models”. A session holds multi-turn context; Apple’s guidance is to create a new session for each distinct single-turn interaction. ↩↩
-
Apple Developer, “Generable” and “Prompting an on-device foundation model”. The
@Generablemacro lets the framework return a populated, type-checked Swift value rather than a string. ↩↩ -
Apple Developer, “Tool” protocol. Defines
protocol Tool<Arguments, Output>: Sendablewith requiredname,description, andparameters: GenerationSchema, pluscall(arguments:) async throws -> Output. TheArgumentstype conforms toConvertibleFromGeneratedContentand is typically declared@Generable. ↩↩↩ -
Apple Developer, “SystemLanguageModel.Availability” and its
UnavailableReason. Cases:.availableand.unavailable(...)with reasonsdeviceNotEligible,appleIntelligenceNotEnabled, andmodelNotReady.SystemLanguageModel.default.isAvailableis the convenience boolean. ↩↩ -
Apple Developer, “SystemLanguageModel.contextSize”. An instance property (reached through
SystemLanguageModel.default) documented as the maximum context size, representing the total tokens across input prompt and generated response. ↩↩ -
Apple Developer, “LanguageModelSession.streamResponse(to:)”. Streams partial generated output as the model produces it, for incremental UI updates. ↩
-
Apple Developer, “Guide(description:_:)”. A peer macro that attaches a natural-language description and optional constraints (numeric ranges, regular-expression guides) to a
@Generableproperty. Requires iOS 26.0+. ↩ -
Apple Developer, “respond(to:schema:includeSchemaInPrompt:options:)”.
includeSchemaInPromptdefaults totrue; Apple’s discussion recommends keeping the default unless the model already knows the expected format. ↩ -
Apple Developer, “LanguageModelSession.prewarm()”. Asks the framework to load model resources ahead of a known upcoming request to reduce first-response latency. ↩
-
글쓴이의 관련 분석: Apple Foundation Models로 만드는 온디바이스 LLM, Foundation Models용 커스텀 어댑터, Foundation Models 활용 사례, 그리고 Foundation Models 위에서의 에이전트형 워크플로. App Intents와 도구 표면에 대한 논의는 App Intents는 여러분의 앱으로 들어가는 Apple의 새로운 API이다에서 전개해요. ↩