Skip to content

Commit b24f78c

Browse files
author
Sanggyu Lee
committed
[ggma] Add gyu (ggma yielding utility) tool
Implement gyu CLI tool to automate GGMA model package creation: - Merge prefill.py and decode.py into unified export.py - Create modular gpm tool structure: - gyu/init.py: Setup venv, install deps (CPU-only torch), clone TICO, extract o2o tools - gyu/import.py: Download complete model from HuggingFace - gyu/export.py: Run conversion pipeline and create .ggma package - gyu/common.py: Shared utilities and constants - gyu/clean.py: Remove building directory - gyu/gyu: Bash wrapper to dispatch commands Documentation: - Rename README.md → DEVELOPER.md (technical guide) - Add USER.md (user-facing guide)
1 parent fd78f13 commit b24f78c

File tree

12 files changed

+401
-156
lines changed

12 files changed

+401
-156
lines changed

runtime/ggma/examples/generate_text/README.md renamed to runtime/ggma/examples/generate_text/DEVELOPER.md

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# TinyLlama Text Generation Example
1+
# TinyLlama Text Generation Developer Guide
22

3-
This document provides a step‑by‑step guide for generating and processing a TinyLlama textgeneration model.
3+
This document provides a detailed technical guide for generating, processing, and optimizing the TinyLlama text-generation model. For basic usage, see [USER.md](USER.md).
44

55
## Summary
66

@@ -22,14 +22,6 @@ source _/bin/activate
2222
pip install -r requirements.txt
2323
```
2424

25-
### 3. Install TICO (Torch IR to Circle ONE)
26-
```bash
27-
# Clone the repository
28-
git clone https://github.com/Samsung/TICO.git
29-
# Install it in editable mode
30-
pip install -e TICO
31-
```
32-
3325
### 4. Get [o2o](https://github.com/Samsung/ONE/pull/16233) in PATH
3426
*Requires the GitHub CLI (`gh`).*
3527
```bash
@@ -41,8 +33,8 @@ export PATH=../../../../tools/o2o:$PATH
4133

4234
### 1. Create the prefill and decode Circle model files
4335
```bash
44-
python prefill.py # Generates prefill.circle
45-
python decode.py # Generates decode_.circle
36+
python tinyllama.py --mode prefill # Generates prefill.circle
37+
python tinyllama.py --mode decode # Generates decode_.circle
4638
```
4739

4840
Verify the generated files:
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# TinyLlama Text Generation User Guide
2+
3+
This guide shows how to create a GGMA package for the TinyLlama model using the `gyu` (Generate Your Utility) tool.
4+
5+
## Quick Start
6+
7+
```bash
8+
cd runtime/ggma/examples/generate_text/
9+
10+
# 1. Initialize environment (one-time setup)
11+
gyu/gyu init
12+
13+
# 2. Import model from HuggingFace
14+
gyu/gyu import Maykeye/TinyLLama-v0
15+
16+
# 3. Export to GGMA package
17+
gyu/gyu export
18+
19+
# 4. Your package is ready at: build/tinyllama-v0.ggma/
20+
```
21+
22+
## Commands
23+
24+
- **`gyu init`**: Set up environment (venv, dependencies, tools)
25+
- **`gyu import <model_id>`**: Download model from HuggingFace to `build/`
26+
- **`gyu export`**: Convert model to Circle format and create `.ggma` package in `build/`
27+
- **`gyu clean`**: Remove `build/` directory
28+
29+
## Package Structure
30+
31+
The final package at `build/tinyllama-v0.ggma/` contains:
32+
- `model.circle` - Optimized model for GGMA runtime
33+
- `tokenizer.json` - Tokenizer configuration
34+
- `tokenizer.model` - Tokenizer model
35+
- `config.json` - Model configuration
36+
37+
## Running the Model
38+
39+
```bash
40+
# Build ggma_run (from ONE root)
41+
make -j$(nproc)
42+
make install
43+
44+
# Run the model
45+
Product/out/bin/ggma_run build/tinyllama-v0.ggma
46+
```
47+
48+
For detailed developer instructions, see [DEVELOPER.md](DEVELOPER.md).

runtime/ggma/examples/generate_text/decode.py

Lines changed: 0 additions & 68 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env python3
2+
import shutil
3+
import os
4+
5+
def main():
6+
build_dir = "build"
7+
if os.path.exists(build_dir):
8+
print(f"Removing {build_dir} directory...")
9+
shutil.rmtree(build_dir)
10+
print("Clean complete.")
11+
else:
12+
print(f"{build_dir} directory does not exist.")
13+
14+
if __name__ == "__main__":
15+
main()
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import subprocess
2+
3+
# Constants
4+
VENV_DIR = "venv"
5+
PR_WORKTREE = "_pr_16233"
6+
PR_BRANCH = "pr-16233"
7+
PR_REF = "refs/pull/16233/head"
8+
9+
def run_command(cmd, cwd=None, env=None, check=True):
10+
print(f"Running: {cmd}")
11+
subprocess.run(cmd, shell=True, cwd=cwd, env=env, check=check)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env python3
2+
import os
3+
import shutil
4+
import json
5+
from common import VENV_DIR, run_command
6+
7+
def main():
8+
# Change to build directory
9+
build_dir = "build"
10+
if not os.path.exists(build_dir):
11+
print(f"Error: {build_dir} directory does not exist. Run 'gyu import' first.")
12+
return
13+
14+
os.chdir(build_dir)
15+
16+
# Find model directory by config.json
17+
model_dir = None
18+
model_id = None
19+
for d in os.listdir("."):
20+
config_path = os.path.join(d, "config.json")
21+
if os.path.isdir(d) and os.path.exists(config_path):
22+
model_dir = d
23+
# Read model ID from config.json
24+
with open(config_path, "r") as f:
25+
config = json.load(f)
26+
model_id = config.get("_name_or_path", d)
27+
print(f"Using local model directory: {model_dir}")
28+
print(f"Model ID from config: {model_id}")
29+
break
30+
31+
if not model_dir:
32+
raise ValueError("No local model directory found (directory with config.json)")
33+
34+
# Add o2o tools to PATH
35+
env = os.environ.copy()
36+
o2o_path = os.path.abspath("../o2o")
37+
env["PATH"] = f"{o2o_path}:{env['PATH']}"
38+
39+
python_bin = os.path.join("..", VENV_DIR, "bin", "python3")
40+
export_script = os.path.join("..", "tinyllama.py")
41+
42+
# 1. Generate prefill and decode circles
43+
print("Running tinyllama.py (prefill)...")
44+
run_command(f"{python_bin} {export_script} --mode prefill --model {model_dir}", env=env)
45+
46+
print("Running tinyllama.py (decode)...")
47+
run_command(f"{python_bin} {export_script} --mode decode --model {model_dir}", env=env)
48+
49+
# 2. Pipeline
50+
pipeline_cmd = (
51+
f"fuse.attention.py < decode_.circle "
52+
f"| fuse.bmm_lhs_const.py "
53+
f"| reshape.io.py input --by_shape [1,16,30,4] [1,16,32,4] "
54+
f"| transpose.io.kvcache.py > decode.circle"
55+
)
56+
run_command(pipeline_cmd, env=env)
57+
58+
# 3. Merge
59+
merge_cmd = (
60+
f"merge.circles.py prefill.circle decode.circle "
61+
f"| downcast.input_ids.py "
62+
f"| gc.py > model.circle"
63+
)
64+
run_command(merge_cmd, env=env)
65+
66+
# 4. Create package directory and copy files
67+
# Find source directory with tokenizer.json
68+
source_dir = None
69+
for d in os.listdir("."):
70+
if os.path.isdir(d) and os.path.exists(os.path.join(d, "tokenizer.json")):
71+
source_dir = d
72+
break
73+
74+
if source_dir:
75+
package_dir = f"{source_dir}.ggma"
76+
print(f"Creating package directory {package_dir}...")
77+
os.makedirs(package_dir, exist_ok=True)
78+
79+
# Copy tokenizer and config files
80+
for filename in ["tokenizer.json", "tokenizer.model", "config.json"]:
81+
src = os.path.join(source_dir, filename)
82+
if os.path.exists(src):
83+
shutil.copy2(src, package_dir)
84+
85+
# Move model.circle
86+
print(f"Moving model.circle to {package_dir}...")
87+
shutil.move("model.circle", os.path.join(package_dir, "model.circle"))
88+
else:
89+
print("Warning: Could not find source directory (directory with tokenizer.json). Leaving model.circle in current dir.")
90+
91+
if __name__ == "__main__":
92+
main()
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
SCRIPT_DIR=$(dirname "$0")
3+
COMMAND="$1"
4+
shift # Remove command from arguments
5+
6+
if [ "$COMMAND" == "init" ]; then
7+
python3 "$SCRIPT_DIR/$COMMAND.py" "$@"
8+
else
9+
if [ ! -f "venv/bin/python3" ]; then
10+
echo "Error: Environment not initialized. Run 'gyu init' first."
11+
exit 1
12+
fi
13+
venv/bin/python3 "$SCRIPT_DIR/$COMMAND.py" "$@"
14+
fi
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env python3
2+
import sys
3+
import os
4+
from huggingface_hub import snapshot_download
5+
6+
def main():
7+
if len(sys.argv) < 2:
8+
print("Usage: gyu import <model_id>")
9+
sys.exit(1)
10+
11+
model_id = sys.argv[1]
12+
model_basename = model_id.split("/")[-1].lower()
13+
14+
# Create build directory
15+
build_dir = "build"
16+
os.makedirs(build_dir, exist_ok=True)
17+
18+
# Download into build directory
19+
target_dir = os.path.join(build_dir, model_basename)
20+
print(f"Downloading model files for {model_id} into {target_dir}...")
21+
22+
snapshot_download(repo_id=model_id, local_dir=target_dir)
23+
24+
if __name__ == "__main__":
25+
main()
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env python3
2+
import os
3+
import shutil
4+
import subprocess
5+
import venv
6+
from common import VENV_DIR, PR_WORKTREE, PR_BRANCH, PR_REF, run_command
7+
8+
def main():
9+
# 1. Create virtual environment
10+
if not os.path.exists(VENV_DIR):
11+
print(f"Creating virtual environment in {VENV_DIR}...")
12+
venv.create(VENV_DIR, with_pip=True)
13+
14+
# 2. Install torch cpu first to avoid nvidia packages
15+
pip_cmd = os.path.join(VENV_DIR, "bin", "pip")
16+
run_command(f"{pip_cmd} install torch --index-url https://download.pytorch.org/whl/cpu")
17+
18+
# 3. Install requirements
19+
if os.path.exists("requirements.txt"):
20+
run_command(f"{pip_cmd} install -r requirements.txt")
21+
22+
# 4. Clone and Install TICO (Shallow)
23+
if not os.path.exists("TICO"):
24+
run_command("git clone --depth 1 https://github.com/Samsung/TICO.git")
25+
26+
# Patch TICO to support None condition
27+
tico_record_input = os.path.join("TICO", "tico", "utils", "record_input.py")
28+
if os.path.exists(tico_record_input):
29+
with open(tico_record_input, "r") as f:
30+
content = f.read()
31+
if "self.condition = condition" in content and "self.condition = condition if condition" not in content:
32+
print("Patching TICO/tico/utils/record_input.py...")
33+
content = content.replace("self.condition = condition", "self.condition = condition if condition is not None else lambda args_dict: True")
34+
with open(tico_record_input, "w") as f:
35+
f.write(content)
36+
37+
run_command(f"{pip_cmd} install -e TICO --extra-index-url https://download.pytorch.org/whl/cpu")
38+
39+
# 5. Git worktree for PR and o2o extraction
40+
if not os.path.exists("o2o"):
41+
if not os.path.exists(PR_WORKTREE):
42+
# Fetch PR only if worktree doesn't exist
43+
try:
44+
run_command(f"git fetch https://github.com/Samsung/ONE.git {PR_REF}:{PR_BRANCH}")
45+
except subprocess.CalledProcessError:
46+
print("Fetch failed, possibly branch already exists. Continuing...")
47+
48+
# Create worktree with no checkout
49+
run_command(f"git worktree add --no-checkout -f {PR_WORKTREE} {PR_BRANCH}")
50+
51+
# Configure sparse checkout
52+
cwd = os.getcwd()
53+
try:
54+
os.chdir(PR_WORKTREE)
55+
run_command("git sparse-checkout init --cone")
56+
run_command("git sparse-checkout set tools/o2o")
57+
# Populate files
58+
run_command(f"git checkout {PR_BRANCH}")
59+
finally:
60+
os.chdir(cwd)
61+
62+
# Move o2o to top level
63+
print("Moving o2o tools to ./o2o...")
64+
if os.path.exists(os.path.join(PR_WORKTREE, "tools", "o2o")):
65+
shutil.move(os.path.join(PR_WORKTREE, "tools", "o2o"), "o2o")
66+
67+
# Remove worktree
68+
print("Removing temporary worktree...")
69+
run_command(f"git worktree remove --force {PR_WORKTREE}")
70+
71+
else:
72+
print("o2o tools already exist.")
73+
74+
# Make tools executable
75+
if os.path.exists("o2o"):
76+
run_command("chmod +x o2o/*.py")
77+
78+
if __name__ == "__main__":
79+
main()

0 commit comments

Comments
 (0)