추론 라이브러리
uzu는 이미 학습된 AI 모델을 기기에서 실행하고, 그 결과를 앱이 쓰기 좋은 대화·분류 API로 바꾸는 Rust 라이브러리입니다. 모델을 처음부터 훈련하는 프로젝트와는 목적이 다릅니다. 모델의 가중치를 읽고, 입력에 맞는 계산을 수행하고, 다음 토큰을 선택하는 일이 중심입니다.
이 저장소에서 배울 핵심은, 한 줄의 대화 요청을 실제 하드웨어 연산으로 바꾸는 여러 계층의 연결 방식입니다. 사용자 인터페이스를 만들 사람도, Rust를 배울 사람도, GPU에 관심 있는 사람도 각자 시작할 입구가 있습니다.
악보만 내려받아서는 소리가 나지 않습니다. 악보를 읽는 사람, 악기, 박자 관리가 필요합니다. 모델 파일도 마찬가지입니다. 가중치를 읽는 로더, 계산하는 CPU·GPU, 결과를 순서대로 전달하는 스트림이 함께 있어야 앱이 답변을 보여줄 수 있습니다.
Python·Swift·TypeScript 바인딩은 같은 연주 장치에 붙인 서로 다른 조작 패널입니다. 핵심 계산을 언어마다 다시 만드는 대신 Rust 구현을 함께 사용합니다.
Engine::new로 설정·저장소·모델 목록·백엔드를 준비합니다.engine.model로 모델을 찾고 engine.download로 필요한 파일을 받습니다.engine.chat으로 대화 세션을 만듭니다.session.reply 또는 스트리밍 API에 메시지를 전달합니다.// README의 Rust Quick Start와 같은 대화 호출 흐름
// model은 조회·다운로드를 마친 값이라고 가정합니다.
let session = engine.chat(model, ChatConfig::default()).await?;
let messages = vec![
ChatMessage::user().with_text("추론 엔진을 쉽게 설명해 줘".to_string()),
];
let replies = session.reply(messages, ChatReplyConfig::default()).await?;
if let Some(reply) = replies.last() {
println!("{}", reply.message.text().unwrap_or_default());
}
위 코드는 흐름을 보여주는 부분 예시입니다. 가져오기 선언과 초기화까지 포함한 예시는 README의 Quick Start에 있습니다. 실제 앱에서는 다운로드 실패, 모델 부재, 사용자의 취소도 처리해야 합니다.
uzu::engine::Engine은 모델 관리와 세션을 담당하는 공개 창구입니다. uzu_engine::engine::Engine<B>은 선택한 백엔드의 계산 문맥을 소유합니다. 이름이 같아도 책임은 다르므로, 파일의 경로와 타입 인수를 함께 보세요.
uzu는 2026-10-23 분석 대상인 GitHub Trending Rust Weekly #6입니다. 다만 이 순위만으로 특정 기능이 유행을 만들었다고 단정할 수는 없습니다. 여기서는 코드와 배포 구성이 보여주는 관심을 끌 만한 지점을 해석합니다.
GPU 계산 라이브러리만 있어도 제품을 만들기는 어렵습니다. 모델 목록, 다운로드 진행률, 대화 상태, 토큰 스트림이 추가로 필요합니다. crates/legacy/uzu/src/engine/mod.rs의 Engine::new는 이 기능들을 묶습니다. apps/mirai-gui의 데스크톱 앱과 apps/uzu-pi-extension의 코딩 에이전트 확장은 같은 엔진을 사용하는 응용 사례입니다.
앱 제작자에게는 이 결합이 장점입니다. 연산 커널의 속도뿐 아니라, 다운로드부터 취소까지 이어지는 사용 경험을 하나의 엔진 위에서 만들 수 있습니다. 다만 모델 형식과 하드웨어 지원 범위를 먼저 맞춰야 그 장점이 성립합니다.
MetalDenseBuffer::new는 MTLResourceOptions::STORAGE_MODE_SHARED로 버퍼를 만듭니다. CPU와 GPU가 공유할 수 있는 자원을 사용하고, residency set에 등록해 GPU 접근을 관리합니다. “통합 메모리를 활용한다”는 설명이 실제 코드의 자원 생성 방식으로 이어지는 지점입니다.
공유 메모리가 계산이나 동기화 비용을 없애 주지는 않습니다. 특히 긴 대화는 KV 캐시를 키우므로, 가중치 파일이 메모리에 들어간다고 해서 모든 길이의 대화를 처리할 수 있는 것은 아닙니다.
| 대상 | 주된 초점 | uzu를 선택할 이유 / 확인할 점 |
|---|---|---|
| uzu | Rust 추론 코어와 앱용 API·바인딩 | 앱 통합 경로와 GPU 내부를 함께 공부하기 좋음. 자체 모델 형식과 플랫폼 제약 확인 필요 |
| llama.cpp | C/C++ 기반 추론, GGUF, 여러 하드웨어 백엔드 | uzu는 Rust 타입 설계와 공개 세션 API를 함께 읽을 수 있음. 범용 하드웨어 폭은 별도로 비교해야 함 |
| MLX / MLX LM | Apple 기기의 배열 연산·학습·텍스트 생성 | uzu는 모델 관리와 대화 제품의 경계를 한 저장소에서 따라가기 좋음 |
| vLLM | 추론 서빙, PagedAttention, 연속 배칭 | uzu는 기기 내 앱 통합을 공부하는 입구. 다중 요청 처리량은 같은 조건으로 측정해야 함 |
비교 근거는 각 프로젝트의 공식 설명인 llama.cpp 저장소, MLX 소개, vLLM 소개입니다. 위 표는 용도 비교이며 속도 순위가 아닙니다. 같은 모델·양자화·문맥 길이·기기에서 측정한 공통 수치는 제시하지 않습니다.
README는 이런 장점을 내세우지만, 로컬 계산에도 모델 로딩과 토큰 생성 시간이 듭니다. 전력·메모리·저장 공간 비용도 남습니다. 공개 엔진에는 원격 서비스 연결 경로가 있으므로, 모든 설정에서 데이터가 기기 안에만 머문다고 일반화할 수 없습니다.
반면 GUI의 apps/mirai-gui/desktop/src/engine.rs는 클라우드 키와 Ollama·LM Studio 탐색을 명시적으로 끕니다. “라이브러리에 가능한 동작”과 “특정 앱이 선택한 정책”을 구분해야 합니다.
아래 버전은 커밋 637cf10의 의존성 선언 기준입니다. Cargo의 "1.53.3", npm의 ^18.3.1, Python 패키지의 >=2.10.0은 각각 허용 범위를 나타냅니다. 설치 결과를 재현할 때는 해당 잠금 파일도 함께 봐야 합니다.
| 기술·선언 버전 | 담당하는 일 | 확인 파일 |
|---|---|---|
| Rust edition 2024 | 워크스페이스 공통 언어 설정. rust-version = "1.98" | Cargo.toml |
| Tokio 1.53.3 | 공개 엔진의 비동기 초기화·채널·세션 작업 | Cargo.toml, crates/legacy/uzu/Cargo.toml |
| tokenizers 0.22.2 | 텍스트와 토큰 번호 사이 변환. 기본 기능을 끄고 fancy-regex 사용 | Cargo.toml |
| serde 1.0.229 serde_json 1.0.154 | 모델 설정·메타데이터·요청 데이터 읽기 | Cargo.toml |
| mtl-rs 0.3.0 | metal이라는 의존성 이름으로 Apple GPU API 연결 | Cargo.toml |
| half 2.7.1 bytemuck 1.25.2 | 반정밀도 수와 버퍼의 바이트 표현 다루기 | Cargo.toml |
| xgrammar-rs 0.2.8 | 문법에 맞는 다음 토큰만 고르는 구조화 출력 | Cargo.toml, crates/uzu-engine/Cargo.toml |
| Rocket 0.5.1 | CLI의 HTTP 서버와 스트리밍 응답 | Cargo.toml, crates/legacy/cli/Cargo.toml |
# crates/uzu-engine/Cargo.toml의 기본 기능
[features]
default = ["cpu", "metal", "grammar"]
cpu = []
# metal과 grammar에는 선택적 의존성 묶음이 연결됩니다.
# crates/legacy/uzu/Cargo.toml의 공개 기능 이름
default = ["backend-cpu", "backend-metal", "capability-grammar"]
backend-cpu = ["uzu-engine/cpu"]
backend-metal = ["uzu-engine/metal"]
capability-grammar = ["uzu-engine/grammar"]
공개 패키지와 코어의 기능 이름이 다릅니다. backend-metal은 내부의 metal을 켜는 연결선입니다. 또한 기능을 켜는 것과 해당 운영체제에서 실제 백엔드가 생성되는 것은 별개입니다. 그 최종 분기를 crates/uzu-engine/build/main.rs가 처리합니다.
| 창구 | 실제 선언 | 배울 것 |
|---|---|---|
| Python | PyO3 0.29.3, abi3-py312·experimental-async; maturin >=1.8,<2.0 | Rust 라이브러리를 Python 확장 모듈로 포장하는 방식 |
| Swift | UniFFI 0.31.2, tokio 기능; Package.swift의 tools-version 5.9 | 생성한 Swift 인터페이스와 배포용 바이너리의 결합 |
| TypeScript | napi 3.14.2, napi-derive 3.6.12; 패키지 @trymirai/uzu 0.6.13 | 타입 선언과 실제 네이티브 모듈의 역할 차이 |
| WebAssembly | wasm-bindgen 0.2.129, wasm-bindgen-futures 0.4.79 | 대상별 조건부 컴파일. 브라우저에서 모든 기능이 된다는 뜻은 아님 |
선언 출처는 루트 Cargo.toml, Python pyproject.toml, TypeScript package.json입니다. Python 패키지는 Python 3.12 이상을 요구합니다.
apps/mirai-gui/package.json은 React ^18.3.1, Zustand ^4.5.7, TanStack React Router ^1.170.41, Vite ^5.4.21, TypeScript ^5.9.3, Tailwind CSS ^4.3.3을 선언합니다. Rust 데스크톱 셸은 Tauri 2.12.4입니다. 추론 코어 자체가 React에 의존하는 구조는 아닙니다.
화면의 apps/mirai-gui/client/src/platform/services/models/tauri.ts는 명령 호출과 이벤트 구독으로 데스크톱에 연결됩니다. 같은 위치의 apps/mirai-gui/client/src/platform/services/models/web.ts는 모델 목록을 빈 배열로 돌려주고 다운로드를 거부합니다. 따라서 브라우저 화면이 열린다는 것만으로 웹 추론까지 구현됐다고 판단하면 안 됩니다.
| 설정 | 확인한 값·행동 | 의미 |
|---|---|---|
| Rust 도구 체인 | rust-toolchain.toml: channel = "nightly" | 날짜로 고정한 nightly는 아님. 재현 시점에 주의 |
| Metal 컴파일 | MetalStd::Metal4_0, 최소 OS 26.4 | 오래된 Metal 환경을 그대로 가정할 수 없음 |
| Nix | flake.nix: Apple SDK 26, Metal toolchain build 27A266a | 시스템별 개발 도구 구성. HEAD 변경도 이 도구 체인 갱신 |
| Node·pnpm | GUI package: Node >=22.19, pnpm 12.14.0; CI는 Node 26 | 패키지 최소 선언과 문서 권장 환경을 구분 |
| GitHub Actions | fmt·clippy·잠금 파일·cargo-deny·바인딩·GUI 검사 | 여러 언어로 노출하는 API의 일관성도 검증 대상 |
| Python 개발 도구 | torch >=2.10.0, transformers >=5.5.0, pytest >=9.0.2 | tools/pyproject.toml의 내보내기·분석 도구. Rust 추론 코어의 필수 Python 런타임은 아님 |
의존성 목록은 외울 대상이 아니라, 어느 계층에서 어떤 책임을 맡는지 찾는 지도입니다. 먼저 코어, 공개 API, 바인딩, 앱, 개발 도구의 다섯 칸으로 나누면 긴 설정 파일도 읽기 쉬워집니다.
다음은 모든 모듈을 나열한 그림이 아니라, 로컬 언어 모델의 대화 경로를 중심으로 정리한 구조입니다. 원격 모델을 선택하면 같은 공개 엔진에서 별도의 backend-remote 경로로 연결됩니다.
모델 목록과 파일 다운로드는 계산 코어 바깥에 있습니다. 코어의 load_language_model은 이미 존재하는 모델 디렉터리를 받습니다. 이 경계 덕분에 파일을 얻는 정책과 파일을 계산하는 구현을 따로 읽을 수 있습니다.
crates/uzu-engine/src/backends/common/backend.rs의 Backend 트레이트는 계산 문맥, 명령 버퍼, 여러 버퍼 타입, 커널 묶음을 연관 타입으로 지정합니다. LanguageModel<B>는 이 약속을 통해 CPU와 Metal의 구체 타입을 바꾸어 사용합니다.
// Backend의 선언 중 일부만 발췌
pub trait Backend: Clone + Send + Sync + 'static {
type Context: Context<Backend = Self>;
type CommandBuffer: CommandBuffer<Backend = Self>;
type GlobalBuffer: GlobalBuffer<Backend = Self>;
type Kernels: Kernels<Backend = Self>;
// 나머지 연관 타입과 상수 생략
}
<B>는 구체 타입을 나중에 지정하는 자리입니다. B::Context 같은 연관 타입은 “이 백엔드와 한 세트인 문맥 타입”을 뜻합니다. 서로 맞지 않는 백엔드와 버퍼를 연결하는 실수를 타입으로 줄일 수 있습니다.crates/uzu-engine/src/backends/mod.rs의 select_backend는 컴파일된 백엔드 중 하나를 고릅니다. 기본은 Metal 백엔드가 컴파일되어 있으면 Metal, 그렇지 않으면 CPU입니다. UZU_BACKEND 환경변수로 선택을 바꿀 수 있지만, 컴파일하지 않은 백엔드를 새로 만들어 주지는 않습니다. 알 수 없는 값이나 컴파일되지 않은 백엔드를 지정하면 호출자가 넘긴 오류를 반환합니다.
공개 계층의 ChatTokenInstanceSelection::select<B>는 구체 타입의 인스턴스를 만든 뒤 Box<dyn ChatTokenInstance>로 감쌉니다. 하드웨어별 구현은 제네릭으로 구체화하고, 상위 API의 접점에서는 공통 인터페이스로 다루는 방식입니다.
읽을 파일: 백엔드 선택, 공개 엔진과 코어 사이 브리지.
LanguageModel<B>에는 디코더, 토크나이저, 기본 생성 설정, 선택적 추측기가 있습니다. LanguageModelState<B>에는 누적 토큰, 난수 상태, Transformer 상태, 마지막 출력 토큰이 있습니다. 공유할 계산 재료와 요청 흐름에 따라 달라지는 기록을 구분한 것입니다.
// 실제 API 시그니처: 하나의 스트림이 가변 상태를 빌립니다.
pub fn stream<'a>(
&'a self,
input: &[u64],
state: &'a mut LanguageModelState<B>,
options: LanguageModelStreamOptions,
) -> Result<LanguageModelStream<'a, B>, LanguageModelStreamError<B>>
&self는 모델을 읽기용으로 빌리고, &mut state는 해당 상태를 독점해서 변경하겠다는 뜻입니다. 이 함수의 사용 범위에서는 동일한 상태를 두 스트림이 동시에 가변으로 빌릴 수 없습니다. 다만 이것만으로 요청 스케줄링이나 모든 GPU 동시성 문제가 해결되지는 않습니다.
같은 교과서를 여러 학생이 읽을 수 있어도, 각자의 풀이 노트는 따로 있어야 합니다. 모델은 교과서이고, 누적 토큰과 KV 캐시는 풀이 노트입니다. 노트가 섞이면 앞사람의 대화가 뒷사람의 계산에 들어갈 수 있습니다.
Engine::load_language_model이 config.json을 LanguageModelConfig로 읽습니다.ParameterLoader::new가 model.safetensors의 헤더 길이와 JSON 헤더를 읽습니다.Tokenizer::from_file이 tokenizer.json을 읽고, Decoder::new가 필요한 가중치를 요청합니다.DataType::BF16입니다. 저장된 모든 가중치가 비양자화 BF16이라는 뜻은 아닙니다.assert_all_tensors_read가 읽지 않은 텐서가 남았는지 확인합니다.ParameterTree::tensor는 이름뿐 아니라 예상 모양과 자료형도 비교합니다. 바이트 구간의 길이가 예상 크기와 맞는지도 검사합니다. 파일이 열렸다는 사실과 모델에 맞는 파일이라는 사실을 나눠 검증하는 사례입니다.
crates/uzu-engine/src/encodable_block/mixer/attention/kv_storage.rs의 KVStorage는 BF16과 Int8 저장 방식을 구분합니다. Int8에는 코드 버퍼 외에 스케일 버퍼가 따로 있습니다. create_kv_buffer는 기기가 SPARSE_BUFFERS를 지원하면 희소 버퍼를, 아니면 일반 버퍼를 만듭니다.
KVStorage::prepare는 희소 버퍼의 필요한 앞부분을 매핑합니다. 최대 공간을 표현하는 것과 그 공간에 물리 메모리를 모두 붙이는 것을 분리하는 방식입니다. 이 기능이 있다고 사용 중인 대화의 메모리 비용이 사라지는 것은 아닙니다.
LanguageModelStream은 단순히 반복문에서 문자열을 내보내는 객체가 아닙니다. DecodingState로 다음 연산의 진행 상태를 표현하고, generate를 호출할 때 상태를 전이합니다. GPU 작업이 제출됐지만 아직 끝나지 않은 경우도 타입의 한 상태로 다룹니다.
LanguageModelStream::new는 상태와 입력이 모두 비어 있으면 NoSeedToken, 합친 길이가 제한을 넘으면 ContextOverflow를 반환합니다. 입력이 있으면 먼저 prefill을 수행하며, 이 구현은 입력을 최대 1024토큰 단위로 나눕니다. 이 값은 현재 attention 구현의 제약과 연결되어 있다는 주석도 있습니다.
공개 브리지의 UzuChatTokenBackendInstance::stream은 취소 토큰과 생성 한도를 검사합니다. 취소, 한도 도달, 정상 종료, 오류의 경로마다 내부 스트림의 close().await를 호출하는 처리가 있습니다. 생성 루프뿐 아니라 정리 경로도 API의 일부입니다.
Drop for LanguageModelStream은 아직 닫히지 않은 스트림을 block_on(self.close())로 정리합니다. 자원 회수를 보조하지만, 소멸 과정에서도 대기가 발생할 수 있다는 뜻입니다. 비동기 자원을 설계할 때 명시적 종료 메서드가 왜 필요한지 보여 줍니다.
모델 디렉터리에 speculator가 있으면 먼저 디코더의 추측 디코딩 지원 여부를 단언문으로 검사한 뒤 DFlashTfmSpeculator 로더를 호출합니다. 지원하지 않으면 단언문 실패로 패닉이 발생합니다. propose_tree는 후보 토큰 트리를 만들고, 생성 루프는 후보를 검사해 수락한 토큰과 상태를 반영합니다. 트리 예산과 최대 깊이도 별도 설정입니다.
이는 “한 번에 무조건 여러 토큰을 출력한다”는 약속이 아닙니다. 후보가 얼마나 채택되는지, 제안 비용이 얼마나 드는지에 따라 효과가 달라집니다. 모든 모델에 추측기가 들어 있는 것도 아닙니다.
crates/uzu-engine/src/engine/language_model/grammar/grammar.rs의 Grammar::new는 JSON Schema·정규식·기본 JSON 문법을 컴파일합니다. next_bitmask는 다음에 허용할 토큰을 비트마스크로 만들고, accept_token은 선택한 토큰에 따라 문법 상태를 갱신합니다.
문법에 맞는 JSON을 얻는 것과 내용이 사실에 맞는 것은 별개입니다. 예를 들어 숫자 필드를 보장해도 그 숫자가 정확한지는 앱이 검증해야 합니다. 또한 빌드 설정상 wasm32에서는 이 grammar 기능이 활성화되지 않습니다.
읽을 파일: 생성 상태 머신, 취소·한도·정리, 후보 트리, 문법 제약.
crates/uzu-engine/build/main.rs의 main은 공통 GPU 타입을 수집한 뒤 CPU·Metal 컴파일러를 구성하고, 커널을 빌드한 결과로 traitgen_all을 호출합니다. Rust 타입과 커널 인자를 연결하는 반복 작업을 빌드 단계에서 처리하는 구조입니다.
Metal 컴파일러의 build와 compile은 타입·래퍼·바인딩 생성에 참여합니다. crates/uzu-engine/build/metal/toolchain.rs에서는 metal4.0 표준을 사용합니다. 커널 파일만 읽어서는 완성된 인터페이스의 출처를 모두 찾을 수 없는 이유입니다.
실행 중에도 모든 행렬에 같은 GPU 구현을 적용하지는 않습니다. GemmProblem::select_plan은 Trellis 형식 여부, MXU 지원 여부, 행렬 모양과 GPU 계열을 바탕으로 계획을 고릅니다. 빌드할 수 있는 후보를 준비하는 단계와 현재 입력에 맞는 후보를 고르는 단계가 함께 있습니다.
읽을 파일: 빌드 진입점, Metal 컴파일러, GEMM 실행 계획 선택.
crates/legacy/cli/src/server/runner.rs의 run_server는 세션을 Arc<Mutex<ChatSession>>로 공유하고 루트와 /v1에 경로를 등록합니다. 생성 처리도 세션 잠금을 얻습니다. 이 구조만 보고 다수 사용자를 연속 배칭하는 서버와 같은 처리량을 기대할 수는 없습니다.
성능 코드를 읽을 때는 계산식뿐 아니라, 상태의 소유자와 작업 완료를 기다리는 위치를 함께 찾아야 합니다. uzu의 큰 그림은 이 두 질문으로 다시 정리할 수 있습니다.
아닙니다. 루트 Cargo.toml은 이 구역에 to be rewritten이라는 주석을 붙이지만, 현재 워크스페이스 구성원으로 포함합니다. GUI도 이 안의 공개 uzu 패키지를 사용합니다. 폴더 이름은 개편 방향을 알려줄 뿐, 현재 실행 경로에서 제외됐다는 증거가 아닙니다.
커밋 637cf10 기준 확장자별 파일 수와 물리적 줄 수입니다. 빈 줄·주석·테스트·생성된 바인딩을 포함하고, .git은 제외했습니다. 실행 코드만 센 수치나 GitHub 언어 비율과는 다릅니다.
| 분류 | 파일 수 | 물리적 줄 수 |
|---|---|---|
| Rust · .rs | 1,046 | 104,738 |
| Swift · .swift | 48 | 17,917 |
| TypeScript · .ts | 204 | 14,394 |
| React · .tsx | 147 | 13,530 |
| Python · .py | 76 | 9,245 |
| Metal · .metal | 52 | 7,096 |
| C/C++ 헤더 · .h/.hpp | 85 | 6,606 |
| C++ · .cpp | 6 | 945 |
| C · .c | 5 | 484 |
| JavaScript · .js/.mjs | 7 | 1,033 |
Rust 안에서는 crates/uzu-engine이 43,041줄, crates/legacy가 54,210줄, 나머지가 7,487줄입니다. “작은 코어에 얇은 포장만 붙은 프로젝트”라고 보기에는 앱 통합 계층도 상당한 비중을 차지합니다.
README.md에서 사용 흐름을 보고, crates/legacy/uzu/src/engine/bridge/backend.rs에서 연결을 찾고, crates/uzu-engine/src/engine/language_model/mod.rs에서 모델 로딩을 읽으세요. 마지막으로 crates/uzu-engine/src/engine/language_model/stream/stream.rs의 상태 이름을 표시하면 다음에 볼 폴더가 정해집니다.
| 기술 | 이 저장소에서 배울 것 | 작은 실습 아이디어 |
|---|---|---|
| Rust 타입 설계 | Backend의 연관 타입, Engine<B>, 상위 계층의 dyn 경계 | 프린터 두 종류를 같은 인터페이스로 다루는 장난감 예제로 옮기기 |
| 소유권·수명 | 모델은 공유하고 상태는 가변으로 빌리는 stream 시그니처 | 동일 상태의 두 번째 가변 대여가 왜 안 되는지 그림으로 설명하기 |
| 비동기 처리 | GPU 작업의 Pending 상태와 상위 스트림의 취소·종료 | 가짜 계산 작업에 취소를 넣고 정리가 한 번만 일어나게 설계하기 |
| 메모리 관리 | 공유 버퍼, 희소 버퍼, KV 저장 형식과 메모리 추정 | 문맥 길이를 두 배로 늘린 경우를 표로 계산하기 |
| GPU 계산 | Decoder::encode에서 커널 호출까지 내려가는 경로 | 작은 행렬곱을 손으로 계산하고 GPU 타일링이 나누는 영역 표시하기 |
| 언어 바인딩 | PyO3·UniFFI·N-API의 역할, 기능 전파와 생성 코드 | 세 언어의 Quick Start에서 같은 개념의 이름을 대응시키기 |
| 프론트엔드 경계 | GUI의 PlatformClient와 Tauri·웹 서비스 구현 | 엔진 없이 모델 목록을 흉내 내는 구현의 계약 작성하기 |
| 정확성 검증 | CPU 기준 결과, 오차 허용, 무작위 입력 테스트 | 양자화로 오차가 생겨도 통과할 수 있는 판정 기준 토론하기 |
crates/uzu-engine/unit/backends/common/kernel/tensor_add_scale_test.rs를 먼저 읽어 보세요. 입력과 편향 버퍼를 만들고, 명령을 인코딩하고, 제출·완료 대기를 한 뒤 결과를 읽습니다. 작은 연산으로도 GPU 프로그램의 전체 생애를 볼 수 있습니다.
// 테스트에서 사용하는 명령 버퍼 처리 흐름
// 앞에서 커널 인코딩과 출력 버퍼 준비를 마친 부분 예시입니다.
block_on(command_buffer.end_encoding().submit().wait_until_completed()).unwrap();
// 결과를 읽는 시점은 완료 대기 뒤입니다.
buffer_to_vec::<B, T>(&output_buffer)
crates/uzu-engine/unit/common/assert.rs의 assert_eq_float는 배열 길이를 확인하고 각 원소의 절대 오차를 eps와 비교합니다. 모든 부동소수점 연산을 무조건 정확히 같은 비트로 비교하지 않는 이유를 생각해 보세요. 어떤 커널에는 절대 오차만으로 충분하지 않을 수도 있습니다.
crates/uzu-engine/unit/encodable_block/sampling_test.rs는 난수 입력과 시드를 만들고, 여러 백엔드의 샘플링 결과를 CPU 기준 결과와 비교하는 구조를 갖습니다. ComparableTestResults::compare에서 결과를 어떻게 판정하는지 먼저 보면 복잡한 입력 생성 코드의 목적이 보입니다.
crates/uzu-engine/unit/encodable_block/kv_cache_state_test.rs의 ring_accept_over_capacity_keeps_last는 용량을 넘긴 링 캐시에 최근 항목이 남는 상황을 검사합니다. 이름만 읽고 끝내지 말고, source와 destination 숫자를 칸 그림으로 옮겨 보세요.
복잡한 기계의 설명서를 처음부터 읽기 어렵다면 검사표를 먼저 보는 것과 같습니다. 무엇이 잘못되면 실패하는지 알면, 구현의 여러 분기가 왜 필요한지도 이해하기 쉬워집니다.
apps/mirai-gui/client/src/platform/platform-singleton.ts의 initPlatform은 중복 초기화를 거부하고, getPlatform은 초기화 전 접근을 거부합니다. 전역 객체를 아무 때나 쓰기보다 초기화 순서를 계약으로 만든 예입니다. 웹 구현의 “이용 불가” 응답도 그 계약 안에 들어갑니다.
연산 엔진을 몰라도 “화면은 어떤 능력을 요청하고, 플랫폼은 무엇을 보장하는가”를 읽을 수 있습니다. 이 원리는 브라우저·데스크톱·모바일을 함께 지원하는 일반 앱에도 적용됩니다.
좋은 학습의 단위는 파일 하나를 다 읽는 것이 아니라, 입력·출력·불변 조건 하나를 끝까지 설명하는 것입니다.
| 사용 경로 | 확인 가능한 요구사항 | 해석할 때 주의할 점 |
|---|---|---|
| 소스 읽기·설계 실습 | 텍스트 편집기, 파일 검색 도구 | GPU·모델 다운로드 없이 대부분의 읽기 과제 가능 |
| Mirai GUI | README: Apple silicon, macOS 26.4 이상, Xcode 26 및 Metal 도구 체인 | 빌드 명령의 대상은 aarch64-apple-darwin |
| Metal 코어 | 빌드 코드: metal4.0, 최소 OS 26.4; Metal 4 명령 큐 사용 | CPU로 컴파일 가능한 환경과 GPU 기능 지원 환경은 다름 |
| Swift 패키지 | Package.swift: iOS·macOS·Mac Catalyst 26.4, Swift tools 5.9 | tools-version은 지원 OS 버전과 별개 |
| Python 바인딩 | Python 3.12 이상 | Python 버전만 맞아도 모든 OS에서 같은 바이너리를 쓸 수 있다는 뜻은 아님 |
| TypeScript 패키지 | os = darwin, cpu = arm64/x64 | 명시된 패키지 대상과 실제 기기의 Metal 기능을 함께 확인 |
| Pi 확장 | Darwin·arm64, Node 22.19.0 이상 | GUI와 별개의 패키지 제약 |
| Linux·Windows·WASM | README의 여러 대상이 in progress; CPU 구현과 일부 CI 경로 존재 | 앱·바인딩·모델 형식별로 지원 범위를 따로 확인 |
근거: GUI 요구사항, Swift 플랫폼 설정, Metal 도구 체인. GUI README는 Node 26 이상을 요구하지만 package.json의 최소 선언은 22.19입니다. 실제 개발 환경을 맞출 때는 이 차이도 확인해야 합니다.
crates/uzu-engine/src/backends/cpu/kernel/matmul/reference.rs의 WeightData::from_b는 MatmulB::Trellis를 UnsupportedLayout 오류로 반환합니다. CPU 코드가 존재해도 모든 양자화 형식과 모델 경로를 지원하는 것은 아닙니다. 하드웨어 이름뿐 아니라 모델의 가중치 표현도 호환성 조건입니다.
문서 원본 docs/index.mdx의 FAQ는 현재 지원 기기를 Apple silicon의 iOS·macOS로 더 좁게 안내합니다. README의 대상 목록, npm의 x64 선언, 문서의 제품 지원 범위가 같은 수준의 약속은 아닙니다.
CI에는 Linux CLI 빌드와 WASI 스레드 대상의 커널 테스트가 있습니다. 이 검증 범위와 Windows를 포함한 모든 대상의 제품 수준 지원을 같은 것으로 읽으면 안 됩니다. rust-toolchain.toml의 target 목록은 필요한 도구를 설치할 목록이기도 합니다.
고정된 “최소 RAM 몇 GB”를 모든 모델에 적용할 수는 없습니다. 출발점으로 다음 근삿값을 계산할 수 있습니다. 아래는 특정 uzu 모델의 실측값이 아닌 일반적인 크기 계산 예입니다.
# 이상적인 가중치 데이터 크기
파라미터 수 × 파라미터당 비트 수 ÷ 8
0.8B × 4비트 ÷ 8 = 약 0.4 GB
4B × 4비트 ÷ 8 = 약 2.0 GB
7B × 4비트 ÷ 8 = 약 3.5 GB
# BF16 KV 캐시의 단순 추정: 전체 attention 층에 같은 구조를 가정
2 × 레이어 수 × KV 헤드 수 × 헤드 차원 × 토큰 수 × 2바이트
2 × 24 × 8 × 128 × 4096 × 2 = 384 MiB
가중치 예시의 GB는 10억 바이트, 캐시 예시의 MiB는 2의 20제곱 바이트입니다. 실제 양자화 파일에는 스케일·보조 정보가 붙고, 실행 시에는 활성값·임시 버퍼·토크나이저·운영체제 메모리도 필요합니다. 서로 다른 attention·mixer를 섞는 모델에는 위 캐시 공식을 그대로 적용할 수 없습니다.
KVStorage는 BF16과 Int8 경로를 별도로 갖습니다.LanguageModel::recommended_context_length는 희소 버퍼 지원 시 모델의 최대 문맥 길이를 반환합니다. 지원하지 않고 최대 길이가 유한하면 iOS에서는 8192, 그 외에는 16384와 모델 한도 중 작은 값을 고릅니다.
소스 주석도 이를 정식 메모리 모델이 아닌 단순한 접근이라고 설명합니다. 이 값이 모든 기기에서 메모리 부족을 막아 주는 보장은 아닙니다. iOS에 대해서는 README가 Increased Memory Capability와 여러 모델을 동시에 유지하지 않는 사용 방식을 권합니다.
모델 파일이 들어가는지와 대화를 끝까지 생성할 수 있는지는 서로 다른 메모리 질문입니다.
다음 과제는 모델 설치 없이 시작할 수 있습니다. 처음부터 대규모 엔진을 수정하려 하기보다, 호출 경로·오류 조건·작은 시뮬레이터를 통해 이해를 확인하세요.
목표: 공개 API와 계산 코어가 만나는 지점을 찾습니다.
읽을 곳: README Quick Start → crates/legacy/uzu/src/engine/mod.rs → crates/legacy/uzu/src/engine/bridge/backend.rs → crates/uzu-engine/src/engine/language_model/mod.rs.
할 일: 각 단계의 입력·출력·실패 원인 한 가지를 표로 적습니다. 공개 Engine과 제네릭 Engine을 별도 칸에 놓고, 둘 사이를 잇는 타입을 표시합니다.
완료 기준: “모델 조회가 성공해도 로딩은 왜 실패할 수 있는가?”에 파일 형식·자료형·백엔드 중 두 가지 근거를 들어 답할 수 있습니다.
# 저장소 루트에서 실행하는 읽기 전용 검색 예시
rg -n 'pub async fn chat|pub async fn chat_instance' crates/legacy/uzu/src/engine/mod.rs
rg -n 'select_backend|impl BackendSelection' crates/legacy/uzu/src/engine/bridge/backend.rs
rg -n 'load_language_model|assert_all_tensors_read' crates/uzu-engine/src/engine/language_model/mod.rs
목표: 설정 파일을 근거로 읽는 연습입니다.
읽을 곳: rust-toolchain.toml, 루트 Cargo.toml, apps/mirai-gui/package.json, 같은 앱의 README, Package.swift.
할 일: 최소 버전, 권장 버전, 패키지 버전, 도구 체인 채널을 분류합니다. Node 최소 선언과 README 요구사항, nightly 채널과 날짜 고정의 차이를 설명합니다.
완료 기준: 서로 다른 의미의 버전 값 여섯 개를 출처와 함께 적고, “버전 숫자가 크다”만으로 호환성을 판단하지 않을 수 있습니다.
목표: 제한된 캐시에 최근 토큰만 남기는 상태 전이를 이해합니다.
읽을 곳: crates/uzu-engine/unit/encodable_block/kv_cache_state_test.rs의 ring_accepts_append_then_wrap, ring_accept_over_capacity_keeps_last.
할 일: 두 칸을 그리고 각 호출 뒤의 prefix 길이·ring offset·복사 원본과 목적지를 기록합니다. 테스트의 입력 인덱스와 실제 토큰 ID를 구분합니다.
완료 기준: 용량보다 많은 후보를 수락할 때 어떤 복사가 불필요해지는지 설명하고, 직접 만든 배열 시뮬레이터에서 같은 표를 얻습니다.
목표: 정상 출력보다 종료 경로를 먼저 설계합니다.
읽을 곳: crates/legacy/uzu/src/engine/bridge/chat_token_backend.rs의 스트림 생성 부분과 코어의 LanguageModelStream::close.
할 일: 정수 세 개를 차례로 내보내는 가짜 생성기를 만듭니다. 시작 전 취소, 두 번째 출력 뒤 취소, 한도 도달, 생성 오류를 표로 정리하고 각 경우의 반환·정리 횟수를 정합니다.
완료 기준: 한도 도달과 사용자 취소를 서로 다른 결과로 처리하고, 반복 종료 요청에도 같은 자원을 두 번 정리하지 않는 설계를 제시합니다.
# 과제 4의 언어 독립적인 의사코드
반복:
취소되었으면:
정리하고 종료
생성 개수가 한도에 도달했으면:
정리하고 한도 도달 알림
다음 항목을 요청
오류 또는 정상 끝이면:
정리하고 종료
항목 전달 후 생성 개수 증가
목표: 성능 주장을 재현 가능한 질문으로 바꿉니다.
읽을 곳: crates/uzu-engine/unit/common/metrics.rs, crates/uzu-engine/unit/common/perf.rs, tools/model_compare/comparison.py, README의 Benchmarks 절.
할 일: 모델·가중치 형식·기기·입력 길이·출력 한도·샘플링·추측기 유무를 고정하는 표를 만듭니다. 다운로드, 최초 로딩, prefill, decode를 서로 다른 구간으로 정의합니다.
완료 기준: 첫 토큰 시간, decode 처리 속도, 최대 메모리, 출력 유효성의 네 지표와 반복·예열·온도 조건을 적습니다. 값은 측정 전까지 빈칸으로 둡니다.
어떤 자료는 공유되고 어떤 자료는 세션마다 달라지는지, 취소 뒤 어떤 작업을 기다려야 하는지, CPU에서 거부되는 가중치 형식은 무엇인지 답할 수 있다면 주요 구조를 이해한 것입니다.
주당 3~5시간을 가정한 학습 순서입니다. 익숙한 주제는 건너뛰어도 되지만, 매주 작은 설명 자료 하나는 남기세요. 모델 수학 전체를 먼저 공부하지 않아도 작은 연산·상태·인터페이스부터 이해를 넓힐 수 있습니다.
| 주차 | 배울 개념 | uzu에서 읽을 곳 | 주간 산출물 |
|---|---|---|---|
| 1주 | 소유권, 참조, Result, 구조체·열거형 | crates/uzu-engine/src/engine/language_model/state.rs, 로딩 오류 열거형 | 모델·상태·스트림의 소유 관계 그림 |
| 2주 | 트레이트, 제네릭, 연관 타입, 동적 디스패치 | crates/uzu-engine/src/backends/common/backend.rs, 브리지의 선택 구현 | CPU·Metal 선택 경로와 장난감 백엔드 설계 |
| 3주 | async/await, 스트림, 취소, 잠금 | crates/uzu-engine/src/engine/language_model/stream/stream.rs, 공개 브리지 | 취소·한도·오류별 상태 전이 표 |
| 4주 | 행렬곱, 임베딩, logits, 토큰 샘플링 | crates/uzu-engine/src/encodable_block/decoder.rs, 작은 커널 테스트 | 입력 모양에서 출력 모양까지의 계산 표 |
| 5주 | Metal 버퍼, 명령 제출, KV 캐시, 양자화 | crates/uzu-engine/src/backends/metal/buffer/dense.rs, KV 저장 코드 | 문맥 길이에 따른 가중치·캐시 메모리 추정 표 |
| 6주 | FFI, 코드 생성, CI, 성능 측정 설계 | crates/uzu-engine/build/main.rs, 바인딩 설정, .github/workflows/tests.yml | 기능 하나를 여러 언어에 노출할 변경 목록 |
버퍼 크기, 자료형, 메모리 접근, 결과 오차를 기록하세요. 그다음 GEMM 계획 선택, attention, 추측 디코딩으로 넓히면 됩니다. 빠른 커널을 이해하려면 먼저 비교 기준이 되는 느리고 단순한 구현을 읽을 수 있어야 합니다.
GUI의 모델 목록·다운로드 이벤트·세션·저장 경계를 따라가세요. 사용자가 창을 닫거나 다운로드를 취소할 때 누가 상태를 소유하는지 적어 보면, 추론 코어를 모두 이해하기 전에도 제품 설계를 배울 수 있습니다.
학습의 목표는 코드를 전부 기억하는 것이 아니라, 새 기능을 넣을 때 바꿀 계층과 지켜야 할 계약을 찾는 능력입니다.
| 용어 | 쉬운 설명 |
|---|---|
| 추론 | 학습한 모델로 입력에 대한 출력을 계산하는 과정입니다. |
| 가중치 | 학습으로 정한 모델의 수치입니다. 실행할 때 읽는 큰 데이터의 중심입니다. |
| 텐서 | 수치를 여러 축으로 배열한 자료입니다. 모양과 자료형을 함께 알아야 해석할 수 있습니다. |
| 토크나이저 | 텍스트를 모델의 토큰 번호로 바꾸고 번호를 텍스트로 되돌리는 도구입니다. |
| 임베딩 | 토큰 번호를 계산에 사용할 수치 벡터로 바꾸는 단계입니다. |
| logits | 모델이 계산한 다음 토큰 후보의 점수입니다. 곧바로 확률을 뜻하지는 않습니다. |
| 샘플링 | 점수와 선택 규칙을 이용해 실제 다음 토큰을 고르는 과정입니다. |
| greedy | 가장 높은 점수의 토큰을 고르는 선택 방식입니다. |
| temperature | 토큰 선택 분포의 집중도를 조절하는 값입니다. 값의 효과는 샘플링 구현과 함께 봐야 합니다. |
| top-k / top-p | 상위 k개 또는 누적 확률 기준으로 후보를 줄이는 선택 규칙입니다. |
| 문맥 길이 | 모델이 계산 상태에 반영하는 토큰 범위입니다. 사용자 문장의 글자 수와 다릅니다. |
| prefill / decode | 입력을 읽어 상태를 채우는 구간과 출력을 이어 생성하는 구간입니다. |
| attention | 토큰 사이의 관련도를 바탕으로 정보를 섞는 연산입니다. |
| KV 캐시 | attention에서 이전 토큰의 Key·Value 계산을 재사용하기 위한 기록입니다. |
| BF16 / Int8 | 16비트 부동소수점 표현과 8비트 정수 표현입니다. 정밀도·범위·보조 정보가 다릅니다. |
| 양자화 | 수치를 더 적은 비트 등으로 표현해 저장·전송·연산 비용을 줄이는 기법입니다. |
| 통합 메모리 | CPU와 GPU가 같은 메모리 풀에 접근하는 구조입니다. 동기화까지 자동으로 없어지지는 않습니다. |
| 희소 버퍼 | 큰 논리적 공간 중 필요한 구간에 물리 메모리를 연결해 쓰는 버퍼입니다. |
| 커널 / GEMM | 커널은 장치에서 수행하는 계산 단위이고, GEMM은 일반 행렬곱 연산을 가리킵니다. |
| 명령 버퍼 | 장치에 수행할 작업을 기록해 제출하는 묶음입니다. 기록·제출·완료 시점이 다릅니다. |
| 트레이트 / 연관 타입 | 구현이 지킬 약속과, 그 구현에 연결되는 타입을 표현하는 Rust 기능입니다. |
| Arc / Mutex | 여러 곳에서 소유권을 공유하거나, 공유 자료의 접근을 조정하는 도구입니다. |
| 상태 머신 | 가능한 상태와 상태 사이의 이동을 정해 복잡한 진행을 관리하는 설계입니다. |
| 추측 디코딩 | 후보 토큰을 먼저 제안하고 본 모델의 확인을 거쳐 생성하는 최적화 방식입니다. |
| 문법 제약 | 허용할 다음 토큰을 제한해 출력의 구조를 유도합니다. 사실성 보장과는 다릅니다. |
| FFI / 바인딩 | 서로 다른 언어가 연결되는 경계와, 그 경계를 쓰기 편하게 만든 인터페이스입니다. |
| 절차적 매크로 | Rust 코드의 구조를 읽어 추가 코드를 생성하는 기능입니다. |
| Safetensors | 텐서의 모양·자료형·바이트 구간을 기록하는 저장 형식입니다. 확장자가 같아도 모델 내부 구조는 맞아야 합니다. |
| 첫 토큰 시간 | 요청 후 첫 출력 토큰을 얻을 때까지의 시간입니다. 모델 로딩을 포함하는지 정의해야 합니다. |
| OpenAI 호환 API | 기존 클라이언트가 쓰는 요청·응답 형태에 맞춘 API입니다. 전체 기능·처리 능력이 동일하다는 뜻은 아닙니다. |
| 항목 | 확인 내용 |
|---|---|
| 선정 | GitHub Trending Rust Weekly #6 |
| 스타 / 포크 | 2,546 / 146 · 2026-10-23 GitHub 기준 |
| 주 언어 | Rust |
| 생성일 | 2025-06-23 |
| 최신 릴리스 | 0.6.13 · 2026-10-22 |
| 최근 push | 2026-10-23 |
| 최근 커밋 | 637cf10 · 2026-10-23 · “Update nix metal toolchain (#1011)” |
| 코드 규모 | Rust 104,738줄, TS·TSX 합계 27,924줄, Swift 17,917줄, Python 9,245줄, Metal 7,096줄 |
| 집계 범위 | 확장자별 물리적 줄 수. 테스트·주석·생성 코드 포함, Git 이력 제외 |
| 핵심 백엔드 | CPU·Metal. 운영체제·가중치 형식별 지원 범위는 별도 확인 |
| 모델 형식 | 자체 설정·가중치 구조. 언어 모델 로더는 config.json·tokenizer.json·model.safetensors 사용 |
| 라이선스 | MIT · LICENSE의 저작권 표기는 2025 Mirai Tech Inc. 모델·종속 자원은 각 조건 별도 확인 |
이 글의 구현 설명은 커밋 637cf10 기준입니다. 이후 API·플랫폼·모델 지원이 바뀔 수 있으며, 현재 문서와 과거 커밋의 코드가 서로 다를 수 있습니다. 물리적 줄 수는 기능 수나 품질 점수가 아닙니다.
하드웨어 호환성, 속도 우위, 최대 문맥 길이를 실제 기기에서 보장하는 자료는 아닙니다. 메모리 계산은 단순 예시이고, README의 성능·비용·프라이버시 문구에는 로컬 모델 선택과 실행 설정이라는 조건이 붙습니다.
Safetensors 파일만 있다고 바로 호환되지는 않습니다. 설정·텐서 이름·자료형·양자화 형식까지 맞아야 합니다. 학습 순서는 작은 테스트와 CPU 기준 구현에서 시작하고, 제품 적용 전에는 사용할 버전과 기기의 조건을 다시 확인하세요.