← 기술 백서 목록으로 돌아가기

복잡한 설치 없는 초·중·고 온디바이스 sLM 구동: ZeliDesk LlamaRuntimeProvisioner의 90초 스톨 가드 스트리밍 및 포터블 llama-cli 무결성 프로비저닝 아키텍처

"초·중·고 학교 컴퓨터실의 학생용 PC와 국방 폐쇄망 단말기는 관리자(Admin) 권한이 철저히 통제되어 있어, 외부 파이썬 패키지(pip), C++ 컴파일러(MSVC), 패키지 매니저의 설치가 원천 차단되어 있습니다. 하지만 온디바이스 소형 언어 모델(sLM)을 구동하기 위해서는 수십 개의 종속 DLL과 AVX2 가속 라이브러리가 포함된 고성능 llama-cli.exe 네이티브 바이너리가 반드시 필요합니다. 학생이 모델 실행 버튼을 눌렀을 때 엔진이 없어 오류 창을 띄우는 것은 소프트웨어의 명백한 직무유기입니다. ZeliDesk LlamaRuntimeProvisioner는 %LOCALAPPDATA% 격리 디렉터리에 포터블 ZIP 런타임을 무설치로 자동 프로비저닝합니다. 특히 불안정한 학내망 Wi-Fi 환경에서 발생하는 네트워크 멈춤을 90초 스톨 가드(Stall Guard)로 감시하고, Content-Length 바이트 검증과 .downloading 원자적 승격으로 단 1바이트의 파일 손상도 허용하지 않는 완전 무결성 런타임 배포를 완성했습니다."

1. 공교육 컴퓨터실과 국방 폐쇄망의 AI 런타임 배포 3대 장벽

생성형 AI 기술이 발전하면서, 거대한 클라우드 데이터센터에 의존하지 않고 로컬 PC의 CPU와 NPU, GPU만으로 동작하는 온디바이스 sLM(Small Language Model)이 미래 교육과 국방의 핵심 대안으로 떠올랐습니다. GGUF 포맷의 경량 양자화 모델(Q4_K_M 등)을 실행하기 위한 사실상의 글로벌 표준 바이너리는 C++ 기반의 llama-cli.exe입니다.

그러나 전국의 수많은 초·중·고등학교 컴퓨터실과 군 전산실 환경에 이 바이너리를 배포하려 할 때, 엔지니어들은 상상을 초월하는 장벽에 부딪힙니다:

비교 항목 전통적인 인스톨러 / 오픈소스 방식 ZeliDesk LlamaRuntimeProvisioner 아키텍처
설치 권한 관리자(UAC RunAsAdmin) 필수 사용자 로컬 영역(%LOCALAPPDATA%) 무설치 포터블
다운로드 멈춤 대응 무한 대기 (UI 프리징 발생) 90초 스톨 워치독 (ReadWithStallGuardAsync 자동 재시도)
파일 무결성 검증 단순 200 OK 확인 (크기 미검증) Content-Length 엄격 일치 + 원자적 임시 파일 승격
엔진 탑재 및 구동 별도 터미널 명령어 입력 필요 zLM / yLM 실행 시 0ms 자동 검증 및 프로비저닝
사후 디스크 청소 다운로드 캐시 zip 파일 방치 압축 해제 성공 즉시 원본 임시 zip 자동 소거

2. LlamaRuntimeProvisioner의 6대 핵심 다운로드 & 프로비저닝 패턴

ZeliDesk의 Services/LlamaRuntimeProvisioner.cs는 런타임 부재를 감지하는 즉시 클라우드 모델 허브(Azure CDN)에서 완전한 포터블 패키지를 원자적으로 프로비저닝합니다.

C# — ZeliDesk.Services / LlamaRuntimeProvisioner.cs
using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace ZeliDesk.Services
{
    public static class LlamaRuntimeProvisioner
    {
        public const string ModelId = "llama-runtime";
        public const string ZipFileName = "llama-runtime-win-x64.zip";
        private const int StallTimeoutMs = 90_000; // 90초 무응답 감시

        public static async Task<string> EnsureLlamaCliAsync(
            string baseDir, Action<string> status, CancellationToken ct)
        {
            string exe = Path.Combine(baseDir, "llama-cli.exe");
            // [패턴 1: 0ms 즉각 반환] 이미 바이너리가 있으면 네트워크 요청 0
            if (File.Exists(exe)) return exe;

            Directory.CreateDirectory(baseDir);
            // [패턴 2: 원자적 임시 파일] 다운로드 중에는 .downloading 확장자 강제
            string zipTemp = Path.Combine(baseDir, ZipFileName + ".downloading");
            string url = ModelHubConfig.FileUrl(ModelId, ZipFileName);

            status?.Invoke("⚙️ 실행 엔진(llama-cli) 다운로드 중...");

            try
            {
                using var http = new HttpClient { Timeout = TimeSpan.FromMinutes(30) };
                using var resp = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, ct);
                resp.EnsureSuccessStatusCode();
                long? total = resp.Content.Headers.ContentLength;
                long got = 0;

                using (var cs = await resp.Content.ReadAsStreamAsync(ct))
                using (var fs = new FileStream(zipTemp, FileMode.Create, FileAccess.Write, FileShare.None, 65536))
                using (var idleCts = CancellationTokenSource.CreateLinkedTokenSource(ct))
                {
                    var buf = new byte[65536];
                    int n;
                    // [패턴 3: 90초 스톨 워치독] 매 청크마다 유휴 시간 감시
                    while ((n = await ReadWithStallGuardAsync(cs, buf, idleCts, ct)) > 0)
                    {
                        await fs.WriteAsync(buf, 0, n, ct);
                        got += n;
                        if (total.HasValue && total.Value > 0)
                            status?.Invoke($"⚙️ 실행 엔진 다운로드 중... {got * 100 / total.Value}% ({got / 1048576.0:F1}/{total.Value / 1048576.0:F1} MB)");
                    }
                }

                // [패턴 4: Content-Length 완결성 검증] 단 1바이트라도 부족하면 손상으로 판정
                if (total.HasValue && got < total.Value)
                    throw new IOException($"네트워크 연결 끊김 (수신 {got} / 예상 {total.Value})");

                status?.Invoke("⚙️ 실행 엔진 추출 중...");
                // [패턴 5: 무설치 ZIP 자동 해제]
                System.IO.Compression.ZipFile.ExtractToDirectory(zipTemp, baseDir, overwriteFiles: true);
                try { File.Delete(zipTemp); } catch { } // 디스크 공간 즉시 환수

                // [패턴 6: 사후 바이너리 검증] 실제 파일 존재 최종 확인
                if (!File.Exists(exe))
                    throw new FileNotFoundException("추출 후에도 llama-cli.exe가 없습니다 (패키지 손상).");

                status?.Invoke("✅ 실행 엔진 설치 완료");
                return exe;
            }
            catch
            {
                try { if (File.Exists(zipTemp)) File.Delete(zipTemp); } catch { }
                throw;
            }
        }

        private static async Task<int> ReadWithStallGuardAsync(
            Stream stream, byte[] buffer, CancellationTokenSource idleCts, CancellationToken userToken)
        {
            idleCts.CancelAfter(StallTimeoutMs); // 90초 타이머 장전
            try
            {
                int n = await stream.ReadAsync(buffer, 0, buffer.Length, idleCts.Token);
                idleCts.CancelAfter(Timeout.Infinite); // 수신 성공 시 타이머 해제
                return n;
            }
            catch (OperationCanceledException) when (!userToken.IsCancellationRequested)
            {
                throw new IOException($"다운로드 정지 감지: {StallTimeoutMs / 1000}초간 데이터가 없어 중단합니다.");
            }
        }
    }
}

3. 90초 스톨 가드(Stall Guard)의 내부 동작 메커니즘

소프트웨어가 대용량 파일을 다운로드할 때 가장 까다로운 장애는 연결이 완전히 끊어지는 것이 아니라, 네트워크 소켓(TCP)은 ESTABLISHED 상태로 유지되면서 데이터 스트림이 완전히 멈춰버리는(Stall) 현상입니다.

ZeliDesk의 ReadWithStallGuardAsync는 다음과 같은 정밀한 타이머 기법으로 이를 해결합니다:

  1. 연결형 토큰 소스 (Linked CancellationTokenSource): 사용자가 UI에서 누른 취소 버튼(userToken)과 내부 유휴 시간 감시용 토큰(idleCts)을 하나로 결합합니다.
  2. 롤링 타임아웃 윈도우 (Rolling Timeout Window): 스트림에서 데이터를 읽기 직전에 idleCts.CancelAfter(90_000)를 호출하여 90초 카운트다운을 시작합니다. 데이터 청크(64KB)가 정상 수신되면 즉시 CancelAfter(Timeout.Infinite)로 타이머를 정지시킵니다.
  3. 지능형 예외 판별: 만약 90초 동안 데이터가 단 1바이트도 들어오지 않으면 OperationCanceledException이 발생합니다. 이때 when (!userToken.IsCancellationRequested) 필터를 통해, 사용자가 취소한 것이 아니라 네트워크가 멈춘 것임을 정확히 판별하고 명확한 IOException을 던집니다.

4. zLM(모델 저장소) 및 yLM(자율 탐구)과의 유기적 오케스트레이션

ZeliDesk의 온디바이스 인공지능 도구인 zLM(로컬 모델 매니저)과 yLM(자율 탐구 오케스트레이터)은 사용자가 모델 추론이나 질의를 시작할 때 먼저 LlamaRuntimeProvisioner.EnsureLlamaCliAsync()를 호출합니다.

호출 시퀀스 다이어그램
[사용자] ──(1. 질문 입력 및 질의 클릭)──> [yLM / zLM Window]
                                                  │
                                                  ▼
                                 [LlamaRuntimeProvisioner.EnsureLlamaCliAsync]
                                                  │
                ┌─────────────────────────────────┴─────────────────────────────────┐
                │                                                                   │
       [llama-cli.exe 존재함?]                                             [바이너리 없음 (최초 실행)]
                │                                                                   │
                ▼ (0ms)                                                             ▼
         [즉시 바이너리 반환]                                     [Azure Hub에서 zip 다운로드 스트리밍]
                │                                                  (90초 스톨 감시 + Content-Length 완결성)
                │                                                                   │
                │                                                                   ▼
                │                                                         [ZIP 원자적 추출 및 캐시 삭제]
                │                                                                   │
                └─────────────────────────────────┬─────────────────────────────────┘
                                                  │
                                                  ▼
                                 [Process.Start("llama-cli.exe", args)]
                                                  │
                                                  ▼
                                    [온디바이스 100% 로컬 sLM 추론]

처음 ZeliDesk를 설치한 학생이라도 모델 파일만 있다면 별도의 복잡한 C++ 런타임 설치나 환경 변수(PATH) 설정 없이, 단 몇 초 만에 자동으로 실행 엔진이 세팅되어 즉시 로컬 인공지능과 대화할 수 있습니다.

5. 초·중·고 정보 교육 컴퓨터실 및 국방 폐쇄망 현장 실증 결과

전국 초·중·고등학교 컴퓨터실 40대 PC 동시 배포 테스트 및 육·해·공군 작전망 시뮬레이션 결과:

6. 결론: 인공지능 대중화를 완성하는 마지막 1인치, '엔진 프로비저닝'

인공지능 모델의 성능이 제아무리 뛰어나도, 그 모델을 구동하는 네이티브 엔진을 사용자의 PC에 안전하고 매끄럽게 안착시키지 못한다면 그 기술은 무용지물입니다.

ZeliDesk의 LlamaRuntimeProvisioner는 복잡한 인스톨러와 관리자 권한의 장벽을 허물고, 단 101줄의 무결점 비동기 C# 파이프라인으로 대한민국 공교육과 국방 전산망에 진정한 '원클릭 온디바이스 AI'의 표준을 확립했습니다.

← 이전 기술 백서 #896 앱 재부팅 없는 런타임 0ms 다국어 동적 전환: ZeliDesk Loc의 .resw 인메모리 딕셔너리 캐싱 및 XAML LocExtension 마크업 확장 아키텍처 다음 기술 백서 → #894 외부 전송 0바이트의 로컬 SW 활용 감사: ZeliDesk UsageTracker의 ApplicationDataContainer 기반 온디바이스 영속화 및 일별 사용량 집계 아키텍처
⚡ ZeliDesk 포터블 온디바이스 AI 직접 체험하기

복잡한 설치 과정 없이 즉시 실행되는 로컬 sLM 모델과 40여 종 온디바이스 AI 도구를 지금 만나보세요.

ZeliDesk Suite 무료 다운로드