Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1bda929
docs: record resident delta results and SDK 2026.4 migration limits (…
gspark-etri Oct 30, 2026
53b5a0e
docs: clarify P2 scope and track SDK 2026.4 native loader
gspark-etri Oct 30, 2026
f8b6e61
docs: record SDK2026.4 native 8B resident validation scope
gspark-etri Oct 30, 2026
fcf2fb8
docs: freeze SDK2026.4 native evidence reference
gspark-etri Oct 30, 2026
1af19c9
test: pin single target locator read during diff
gspark-etri Oct 30, 2026
46b3e2b
perf(store): reuse target plan for diff extents and stored bytes
gspark-etri Oct 30, 2026
0e0da46
test: preserve diff parity for legacy and identical manifests
gspark-etri Oct 30, 2026
216df10
test: keep diff corruption checks and repack fallback
gspark-etri Oct 30, 2026
c7e7d9e
bench: compare interleaved diff builds with exact JSON parity
gspark-etri Oct 30, 2026
93fa367
style: sort diff regression test imports
gspark-etri Oct 30, 2026
91a0005
docs: publish controlled diff-plan speed and parity evidence
gspark-etri Oct 30, 2026
358c755
docs: close diff optimization ledger and record ownership checks
gspark-etri Oct 30, 2026
69033a1
docs: plan compatible artifact selection and preparation
gspark-etri Oct 30, 2026
56f6865
test: specify exact compatible artifact selection
gspark-etri Oct 30, 2026
c46041a
feat: bind exact compatibility profiles to artifact manifests
gspark-etri Oct 30, 2026
eb1862f
test: specify atomic compatible artifact preparation
gspark-etri Oct 30, 2026
1d050e5
feat: select and atomically prepare compatible artifacts
gspark-etri Oct 30, 2026
1f1ca21
docs: document compatible preparation and validate S3 workflow
gspark-etri Oct 30, 2026
ee6e457
test: reproduce preparation staging and locator boundary findings
gspark-etri Oct 30, 2026
c7e3910
fix: protect preparation staging and validate locator counts
gspark-etri Oct 30, 2026
db82b4c
docs: record review regressions and compatible preparation validation
gspark-etri Oct 30, 2026
1ecd4a2
Merge resident delta and SDK264 evidence
gspark-etri Oct 30, 2026
c22f5fe
Merge verified live delta comparison optimization
gspark-etri Oct 30, 2026
dafcfe1
Merge verified compatible artifact preparation
gspark-etri Oct 30, 2026
466ef91
docs: plan combined Onggi to native serving validation
gspark-etri Oct 30, 2026
8f0b4bc
docs: publish verified native preparation to HTTP serving evidence
gspark-etri Oct 30, 2026
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
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,33 @@ jobs:
./onggi pull demo out
diff -r src out
./onggi ls
- name: compatible profile preparation and warm cache
run: |
python3 - <<'PY'
import json
profile = dict(schema="onggi.profile.v1", model_source="sha256:"+"1"*64,
options="sha256:"+"2"*64, device="synthetic-ci", runtime="test-v1",
sdk="1", compiler="1", firmware="1", format="test", layout="test",
topology="single", tp=1, batch=4, sequence=256)
with open("profile.json", "w") as f:
json.dump(profile, f)
PY
export ONGGI_STORE="${ONGGI_TEST_S3/onggitest?/onggitest/cli?}"
./onggi push --profile profile.json --ref compatible src
./onggi prepare --request profile.json --cache node-cache --dir prepared compatible > prepared.json
diff -r src prepared
./onggi prepare --request profile.json --cache node-cache --dir prepared-again compatible > prepared-again.json
diff -r src prepared-again
python3 - <<'PY'
import json
with open("prepared.json") as f:
assert json.load(f)["ready"] is True
with open("prepared-again.json") as f:
result = json.load(f)
assert result["ready"] is True
assert result["stats"]["read_bytes"] == 0
assert result["stats"]["cache_bytes"] > 0
PY
- name: server log
if: failure()
run: tail -100 "$RUNNER_TEMP/srv/server.log"
24 changes: 23 additions & 1 deletion README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
> Kubernetes에서 결과물을 읽기 전용 볼륨으로 제공합니다. 컨트롤러(컴파일 팜, 프리페치)는 P3에서 이어집니다. 기존 방법과의
> 비교는 [docs/evaluation.md](docs/evaluation.md)에 있습니다.

실시간 델타 비교는 [대상 메타데이터 계획 재사용](docs/diff-plan-2026-10-30.md)으로
8B 부분 갱신 fixture의 CLI 시간을 8~14% 줄였으며, 변경 범위 출력은 동일합니다.

NPU는 모델을 미리 컴파일하며 모델 × SDK × 장치 × 길이 구간 × 배치 × 옵션 조합마다 결과물이 하나씩 생깁니다. 연구실
RNGD + ATOM 노드에서 Rebellions ATOM용 Llama-3.1-8B 빌드 묶음 하나가 **262 GB**를 차지합니다. 그래프마다 가중치가 통째로
들어 있기 때문입니다. FuriosaAI RNGD는 HF 체크포인트와 바이트가 같은 가중치를 세 벌 보관하고 있었습니다. 내용 기반
Expand All @@ -25,6 +28,18 @@ Onggi(옹기)는 서로 다른 바이트를 한 번만 저장하고, 복원한

## 하는 일

P3의 첫 단계로 [호환 프로필 선택·파일 사전 준비](docs/ko/compatible-prepare.md)를 지원합니다.
`push --profile FILE`로 게시하고 `prepare --request FILE --dir DIR <ref>...`로
호환 결과물을 골라 노드 캐시를 통해 검증된 파일을 완성한 뒤 게시합니다.
파일 준비 단계이며, 런타임은 별도로 NPU 예열을 완료한 뒤 readiness를 표시할 수 있습니다.
배치·확장 신호에 따른 자동 연동은 후속 작업입니다.

[실제 RNGD 서빙 검증](docs/ko/native-serving-2026-10-30.md)은 SDK 2026.4에서
init container의 파일 준비부터 예열·HTTP 요청 12건까지 연결합니다. 반환 토큰 96개가
새 전체 로딩의 기준 결과와 일치하고 반복 배치에서 상주 가중치·그래프를 재컴파일 없이
유지했습니다. B4/S256 한 프로필과 외부 체크포인트를 사용하는 그래프·설정 전달 검증이며,
새 속도 향상 수치를 주장하지 않습니다.

| | |
|---|---|
| **한 번만 저장** | 내용 기반 청크, 결과물별 매니페스트, 가중치는 배열 방식별로 한 벌 |
Expand All @@ -41,9 +56,16 @@ Onggi는 미리 컴파일한 변형과 가중치를 기존 청크와 함께 일

빌드마다 원본 체크포인트 식별자, 컴파일러·SDK, 장치, 가중치 배열 방식, 지원 배치·시퀀스 길이를 기록해야 합니다.
배치나 길이가 바뀌어도 호환되는 사전 컴파일 그래프가 필요합니다. RNGD 외부 가중치와 ATOM 장치 입력 가중치는 런타임 지원이
다르며, 가중치를 내장한 ATOM 그래프에는 해당 컴파일 결과물이 필요합니다. 자동 호환성 선택·예열·배치 연동은 후속 작업입니다.
다르며, 가중치를 내장한 ATOM 그래프에는 해당 컴파일 결과물이 필요합니다.
[prepare CLI](docs/ko/compatible-prepare.md)는 지정한 후보에서 프로필이 일치하는 결과물을 선택합니다.
후보 자동 탐색·NPU 예열 제어·배치 연동은 후속 작업입니다.
[연계 설계](docs/ko/integration.md)를 참고하세요.

[상주 델타 갱신 기록](docs/ko/resident-delta.md)은 실제 Onggi 범위 조회와 RNGD 행 단위 갱신을 연결합니다.
Projection 64행은 논리 전송량 117MB → 1.84MB, LM 8행은 1.05GB → 64KiB로 줄었고,
768개 토큰이 새 전체 로딩과 일치했습니다. 이 실측은 SDK 2026.2 기준이며, SDK 2026.4 점검 결과와
별도 [실행 파일·캐시 로더 이식 작업](https://github.com/ai-computing/rngd-pd/pull/31)도 함께 기록했습니다.

## 빠른 시작

```bash
Expand Down
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ English · [한국어](README.ko.md)
> ([deploy/csi](deploy/csi/README.md)) serves them as read-only volumes on Kubernetes. The controller (compile farm,
> prefetch) follows in P3. Measurements against common methods: [docs/evaluation.md](docs/evaluation.md).

Live delta comparison: [reuse target metadata planning](docs/diff-plan-2026-10-30.md)
reduced measured CLI time by 8–14% on the 8B sparse-update fixtures, with identical range output.

NPUs compile models ahead of time, one artifact per model × SDK × device × sequence bucket × batch × options. On our
RNGD + ATOM node, one Llama-3.1-8B build set for Rebellions ATOM takes **262 GB** because every graph embeds the
weights, and FuriosaAI RNGD keeps three copies of weights that are byte-identical to the HF checkpoint. Content-defined
Expand All @@ -27,6 +30,18 @@ The Kubernetes Ref API, compile controller and fetches triggered by ai-rm/llm-d

## What it does

The first P3 integration is [exact-profile artifact selection and preparation](docs/compatible-prepare.md):
publish with `push --profile FILE`, then `prepare --request FILE --dir DIR <ref>...`
chooses a compatible build and atomically prepares verified files through the node cache.
This establishes file readiness. The runtime can separately prewarm before readiness;
automatic placement/autoscaler integration remains planned.

The [native RNGD serving proof](docs/native-serving-2026-10-30.md) connects a real
init-container preparation to startup warmup and 12 HTTP requests on SDK 2026.4.
All 96 returned tokens match fresh references; repeated batches retain resident
weights and graphs without recompilation. This covers one B4/S256 profile and
graph/config delivery with an external checkpoint, with no new speedup claim.

| | |
|---|---|
| **Store once** | content-defined chunks, manifests per artifact, weights stored once per weight layout |
Expand All @@ -45,9 +60,17 @@ delta does not itself update NPU memory or establish that two compiled graphs ca
Record the source checkpoint identity, compiler/SDK, device, weight layout and supported batch/sequence shapes
with each build. Changing batch or sequence length still requires a compatible precompiled graph. RNGD external
weights and ATOM device-input weights have different runtime capabilities; ATOM graphs with embedded weights
require their corresponding compiled artifacts. Automated compatibility selection, prewarm and placement hooks
require their corresponding compiled artifacts. The [prepare CLI](docs/compatible-prepare.md) now selects
exact-profile matches from supplied candidates. Discovery, native prewarm orchestration and placement hooks
remain integration work; see [integration](docs/integration.md).

The [resident-delta integration record](docs/resident-delta.md) connects actual
Onggi ranges to RNGD row updates: 64 projection rows reduced logical upload
117 MB → 1.84 MB, and eight LM rows reduced 1.05 GB → 64 KiB, with 768 generated
tokens equal to fresh loads. Those measurements use SDK 2026.2; the record also
documents the SDK 2026.4 preflight and the separate
[native cache-loader migration](https://github.com/ai-computing/rngd-pd/pull/31).

## Quick start

```bash
Expand Down
50 changes: 50 additions & 0 deletions bench/diff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""Interleave two Onggi builds on one immutable store/ref pair; require equal JSON."""
import argparse
import hashlib
import json
import os
from pathlib import Path
import statistics
import subprocess
import time


def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("baseline")
p.add_argument("candidate")
p.add_argument("store")
p.add_argument("base_ref")
p.add_argument("new_ref")
a = p.parse_args()
env = dict(os.environ, GOMAXPROCS="4")
samples, expected = [], None
for i in range(6):
order = ("baseline", "candidate") if i % 2 == 0 else ("candidate", "baseline")
for name in order:
t = time.perf_counter()
result = subprocess.run(
[getattr(a, name), "diff", "--store", a.store, "--ranges", "--json",
a.base_ref, a.new_ref], env=env, capture_output=True, check=True,
timeout=120)
elapsed = time.perf_counter() - t
data = json.loads(result.stdout)
if expected is None:
expected = data
if data != expected:
raise RuntimeError("diff JSON changed between builds or repetitions")
samples.append(dict(round=i, build=name, seconds=elapsed,
stdout_sha256=hashlib.sha256(result.stdout).hexdigest()))
print(json.dumps(dict(
schema=1, scope="CLI wall time including process start; no NPU execution",
gomaxprocs=4, refs=[a.base_ref, a.new_ref],
binary_sha256={n: hashlib.sha256(Path(getattr(a, n)).read_bytes()).hexdigest()
for n in ("baseline", "candidate")},
medians={n: statistics.median(x["seconds"] for x in samples if x["build"] == n)
for n in ("baseline", "candidate")},
exact_diff_json=True, samples=samples, diff=expected), indent=2))


if __name__ == "__main__":
main()
3 changes: 3 additions & 0 deletions cmd/onggi/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const usage = `onggi — content-addressed store for model weights and compiled
usage:
onggi push --store S [--ref NAME] <dir> store a directory; only missing chunks are uploaded
onggi pull --store S <ref|sha256:...> <dir> restore an artifact, every chunk hash-checked
onggi prepare --store S --request FILE --dir DIR <ref>... select compatible files and prepare atomically
onggi ls --store S refs and store totals
onggi inspect --store S <ref|sha256:...> files of an artifact
onggi diff --store S <base> <ref> what turning base into ref keeps, copies and fetches
Expand Down Expand Up @@ -55,6 +56,8 @@ func main() {
os.Exit(push(os.Args[2:], os.Stdout, os.Stderr))
case "pull":
os.Exit(pull(os.Args[2:], os.Stdout, os.Stderr))
case "prepare":
os.Exit(prepare(os.Args[2:], os.Stdout, os.Stderr))
case "ls":
os.Exit(ls(os.Args[2:], os.Stdout, os.Stderr))
case "inspect":
Expand Down
164 changes: 164 additions & 0 deletions cmd/onggi/prepare.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package main

import (
"context"
"errors"
"flag"
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
"syscall"

"github.com/ai-computing/onggi/pkg/backend"
bfs "github.com/ai-computing/onggi/pkg/backend/fs"
"github.com/ai-computing/onggi/pkg/digest"
"github.com/ai-computing/onggi/pkg/store"
)

func readProfileFile(name string) (store.Profile, error) {
f, err := os.Open(name)
if err != nil {
return store.Profile{}, err
}
defer f.Close()
return store.DecodeProfile(f)
}

type prepareReport struct {
Selection *store.Selection `json:"selection,omitempty"`
Ready bool `json:"ready"` // verified files on disk, not NPU readiness
Directory string `json:"directory,omitempty"`
Stats *store.PullStats `json:"stats,omitempty"`
Error string `json:"error,omitempty"`
}

func prepare(args []string, stdout, stderr io.Writer) int {
var report prepareReport
fail := func(code int, err error) int {
report.Error = err.Error()
fmt.Fprintln(stderr, "onggi:", err)
if writeJSON(stdout, stderr, report) != 0 {
return 1
}
return code
}
fs := flag.NewFlagSet("prepare", flag.ContinueOnError)
fs.SetOutput(stderr)
sf := addStoreFlags(fs)
request := fs.String("request", "", "exact compatibility profile JSON")
dest := fs.String("dir", "", "previously absent destination directory")
cacheDir := fs.String("cache", "", "node chunk cache directory")
peers := fs.String("peers", "", "comma-separated peer URLs")
workers := fs.Int("workers", 8, "ranges read at once")
planOnly := fs.Bool("plan-only", false, "select using metadata; do not fetch model chunks or create destination/cache")
if err := fs.Parse(args); err != nil {
return fail(2, err)
}
if fs.NArg() == 0 || *request == "" || (!*planOnly && *dest == "") || *workers < 1 {
return fail(2, errors.New("usage: onggi prepare --store S --request FILE [--plan-only | --dir DEST] <ref|digest>...; workers must be positive"))
}
want, err := readProfileFile(*request)
if err != nil {
return fail(2, err)
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
s, refs, err := sf.open(ctx)
if err != nil {
return fail(1, err)
}
release, err := s.Lease(ctx, "compatible prepare")
if err != nil {
return fail(1, err)
}
defer release()
candidates := make([]digest.Digest, 0, fs.NArg())
for _, input := range fs.Args() {
d, err := store.Resolve(ctx, refs, input)
if err != nil {
return fail(1, fmt.Errorf("candidate %q: %w", input, err))
}
candidates = append(candidates, d)
}
selected, err := s.Select(ctx, candidates, want)
report.Selection = &selected
if err != nil {
return fail(1, err)
}
if *planOnly {
return writeJSON(stdout, stderr, report)
}
d, err := digest.Parse(selected.Manifest)
if err != nil {
return fail(1, err)
}
o := store.PullOptions{Workers: *workers, Peers: peerClients(*peers)}
if *cacheDir != "" {
cb, err := bfs.Open(*cacheDir, backend.DurabilityNone)
if err == nil {
o.Cache, err = store.OpenWith(ctx, cb, store.Create{Chunker: s.Config.Chunker, Sub: s.Config.Sub, SubKinds: s.Config.SubKinds, PackSize: s.Config.PackSize})
}
if err != nil {
return fail(1, fmt.Errorf("node cache: %w", err))
}
}
st, err := prepareFiles(ctx, s, d, *dest, o)
if err != nil {
return fail(1, err)
}
report.Ready = true
report.Directory = *dest
report.Stats = &st
return writeJSON(stdout, stderr, report)
}

// prepareFiles publishes only a complete tree. It never re-resolves the chosen digest.
func prepareFiles(ctx context.Context, s *store.Store, d digest.Digest, dest string, o store.PullOptions) (store.PullStats, error) {
var st store.PullStats
if err := ctx.Err(); err != nil {
return st, err
}
dest = filepath.Clean(dest)
parent := filepath.Dir(dest)
if err := os.MkdirAll(parent, 0755); err != nil {
return st, err
}
lock := dest + ".onggi-lock"
if err := os.Mkdir(lock, 0700); err != nil {
return st, fmt.Errorf("destination busy: %w", err)
}
defer os.Remove(lock) // never remove a lock this call did not create
if _, err := os.Lstat(dest); !errors.Is(err, os.ErrNotExist) {
if err == nil {
err = errors.New("destination already exists")
}
return st, err
}
tmp, err := os.MkdirTemp(parent, "."+filepath.Base(dest)+".onggi-")
if err != nil {
return st, err
}
defer os.RemoveAll(tmp)
st, err = s.Pull(ctx, d, tmp, o)
if err != nil {
return st, err
}
if err = ctx.Err(); err != nil {
return st, err
}
if err = os.Chmod(tmp, 0755); err != nil {
return st, err
}
if _, err = os.Lstat(dest); !errors.Is(err, os.ErrNotExist) {
if err == nil {
err = errors.New("destination appeared during preparation")
}
return st, err
}
if err = os.Rename(tmp, dest); err != nil {
return st, err
}
return st, nil
}
Loading
Loading