Compiling LLMs Artifacts

This guide describes how to generate QPC (Qualcomm Program Container) artifacts for LLMs using Qualcomm’s Cloud AI100 SDK and how to compile them into binaries for the NR1 device or simulator.


🛠️ Step 1: Create QPC

Generate a QPC for the required model using the Qualcomm Cloud AI100 SDK.

📚 References:

🔧 Create the QPC Entrypoint

Save the following as create_qpc.py:

import os
import argparse

from transformers import AutoTokenizer
from transformers.models.llama.modeling_llama import LlamaForCausalLM

import QEfficient
from QEfficient.utils import hf_download
from QEfficient.utils.constants import Constants
from QEfficient import QEFFAutoModelForCausalLM

from QEfficient.utils import load_hf_tokenizer

CACHE = "/workspace/models/"

os.environ["TRANSFORMERS_CACHE"] = CACHE

# Parse input arguments
parser = argparse.ArgumentParser(
    description="Script for creating QPC (Qualcomm Program Container)"
)
parser.add_argument(
    "--model",
    type=str,
    required=True,
    help="Full name of the model, e.g. meta-llama/Meta-Llama-3.1-70B",
)
parser.add_argument("--pl", type=int, required=True, help="Prompt length")
parser.add_argument("--cl", type=int, required=True, help="Context length")
parser.add_argument("--tp", type=int, required=True, help="Tensor parallelism")
parser.add_argument(
    "--cores_per_soc", type=int, required=True, help="Number of cores per SOC"
)
parser.add_argument("--bs", type=int, help="Static batch size")
parser.add_argument(
    "--fbs", type=int, help="Full batch size"
)  # this is for continous batching
parser.add_argument(
    "--force_fp16_output",
    action="store_true",
    help="Force the specified output tensor (e.g., logits) to float16 precision in the custom IO configuration.",
)
parser.add_argument(
    "--fp16_output_name",
    type=str,
    default="logits",
    help="Name of the output tensor to set to float16 precision, if --force_fp16_output is enabled.",
)

args = parser.parse_args()

MODEL_NAME = args.model
model_name_without_slash = MODEL_NAME.replace("/", "_")
FULL_BATCH_SIZE = args.fbs
STATIC_BATCH_SIZE = args.bs
FORCE_FP16_OUTPUT = args.force_fp16_output
FP16_OUTPUT_NAME = args.fp16_output_name

if not FULL_BATCH_SIZE and not STATIC_BATCH_SIZE:
    print("must enter either fbs or bs")
    exit(1)

PROMPT_LENGTH = args.pl
CONTEXT_LENGTH = args.cl
TENSOR_PARALLELISM = args.tp
GENERATED_LENGTH = CONTEXT_LENGTH - PROMPT_LENGTH

# NUM_OF_CORES_PER_SOC: for each card, not in total  # You can use `/opt/qti-aic/tools/qaic-util | grep "Nsp Total"` from Apps SDK for this.
NUM_OF_CORES_PER_SOC = args.cores_per_soc

print(f"Model name: {MODEL_NAME}")
print(f"Full batch size: {FULL_BATCH_SIZE}")
print(f"Static batch size: {STATIC_BATCH_SIZE}")
print(f"Prompt length: {PROMPT_LENGTH}")
print(f"Context length: {CONTEXT_LENGTH}")
print(f"Tensor parallelism: {TENSOR_PARALLELISM}")
print(f"Generated length: {GENERATED_LENGTH}")
print(f"Number of cores per soc: {NUM_OF_CORES_PER_SOC}")
LOGITS_FP16 = ""
if FORCE_FP16_OUTPUT:
    print(f"Will force output tensor '{FP16_OUTPUT_NAME}' to float16 precision.")
    LOGITS_FP16 = "-logits_fp16"


ROOT_DIR = os.path.dirname(os.path.abspath(""))

BASE_MODEL_PATH = f"/data/work/hf_models/{model_name_without_slash}/"

DEVICE_GROUP = list(range(TENSOR_PARALLELISM))
print("DEVICE_GROUP:", DEVICE_GROUP)

FBS_MODE = f"{STATIC_BATCH_SIZE}bs" if STATIC_BATCH_SIZE else f"{FULL_BATCH_SIZE}fbs"

base_path = f"/qualcomm_compiled_models/{model_name_without_slash}/{model_name_without_slash}-{FBS_MODE}-{PROMPT_LENGTH}pl-{GENERATED_LENGTH}gl-{CONTEXT_LENGTH}cl-{TENSOR_PARALLELISM}tp{LOGITS_FP16}-{NUM_OF_CORES_PER_SOC}cores-per-soc-app/models"
os.makedirs(base_path, exist_ok=True)
print("Saving model to:", base_path)

model_hf_path = BASE_MODEL_PATH
model_exist_flag = os.path.exists(model_hf_path)
if not model_exist_flag:
    print("Model not found on device, downloading from huggingface")
    model_hf_path = MODEL_NAME
print("Model Path:", os.path.exists(BASE_MODEL_PATH))
print("BASE_MODEL_PATH:", BASE_MODEL_PATH)
model_hf = QEFFAutoModelForCausalLM.from_pretrained(
    model_hf_path, local_files_only=model_exist_flag, use_cache=True
)
print(f"########### {MODEL_NAME} from hugging-face \n", model_hf)


# Easy and minimal api to update the model
from QEfficient.exporter.export_hf_to_cloud_ai_100 import qualcomm_efficient_converter

model_transformed = QEfficient.transform(model_hf, form_factor="cloud")
print("############ Model after Optimized transformations \n", model_transformed)

tokenizer = load_hf_tokenizer(
    BASE_MODEL_PATH if model_exist_flag else MODEL_NAME, use_cache=True
)

base_path, onnx_path = qualcomm_efficient_converter(
    model_kv=model_transformed,
    model_name=MODEL_NAME,
    kv=True,
    tokenizer=tokenizer,
    onnx_dir_path=base_path,
    form_factor="cloud",
    full_batch_size=FULL_BATCH_SIZE,
    seq_length=PROMPT_LENGTH,
    # return_path=True
)

if FORCE_FP16_OUTPUT:

    def append_output_precision_yaml(base_path, output_name):
        file_list = ["custom_io_fp16.yaml", "custom_io_int8.yaml"]
        for file in file_list:
            io_file = os.path.join(base_path, file)
            print(f"Appending '{output_name}' with float16 precision to {io_file}")
            with open(io_file, "a") as f:
                f.write(f" - IOName: {output_name}\n   Precision: float16\n\n")

    append_output_precision_yaml(base_path, FP16_OUTPUT_NAME)
else:
    print("Not forcing any specific output tensor to float16.")

# Please use platform SDK to Check num_cores for your card.
from QEfficient import compile

generated_qpc_path = compile(
    onnx_path=onnx_path,
    num_cores=NUM_OF_CORES_PER_SOC,
    qpc_path=os.path.dirname(base_path),
    mxfp6=True,
    mxint8=True,
    aic_enable_depth_first=True,
    device_group=DEVICE_GROUP,
    full_batch_size=FULL_BATCH_SIZE,
    prompt_len=PROMPT_LENGTH,
    ctx_len=CONTEXT_LENGTH,
)
print("generated_qpc_path: ", generated_qpc_path)

🐳 Docker Setup

Save the following as Dockerfile:

FROM ubuntu:22.04

ARG QEFFICIENT_TRANSFORMERS_VERSION="v1.19"

# check https://quic.github.io/cloud-ai-sdk-pages/latest/Getting-Started/Installation/Pre-requisites/pre-requisites/#ubuntu-on-x86-64
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
    python3.10 \
    python3-pip \
    python3-dev \
    wget \
    unzip \
    git \
    sudo \
    locales \
    build-essential \
    libpci-dev \
    libudev-dev \
    libatomic1 \
    pciutils \
    libglib2.0-dev \
    libssl-dev \
    pkg-config \
    libpng-dev \
    libstdc++6 \
    libncurses5-dev \
    libtinfo5 \
    && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# required for pyproject.toml of QEfficientTransformers
RUN pip install -U pip

# from https://quic.github.io/cloud-ai-sdk-pages/latest/Getting-Started/Installation/Pre-requisites/pre-requisites/#addupdate-environment-variables
ENV LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/opt/qti-aic/dev/lib/x86_64"
ENV PATH="/usr/local/bin:${PATH}"
ENV PATH="${PATH}:/opt/qti-aic/tools:/opt/qti-aic/exec:/opt/qti-aic/scripts"
ENV QAIC_EXAMPLES="/opt/qti-aic/examples"
ENV QAIC_APPS="/opt/qti-aic/examples/apps"
ENV QAIC_LIB="/opt/qti-aic/dev/lib/x86_64/libQAic.so"
ENV QAIC_COMPILER_LIB="/opt/qti-aic/dev/lib/x86_64/libQAicCompiler.so"

WORKDIR /qaic
RUN git clone https://github.com/quic/efficient-transformers.git

WORKDIR "efficient-transformers"

RUN git fetch origin release/${QEFFICIENT_TRANSFORMERS_VERSION}
RUN git checkout release/${QEFFICIENT_TRANSFORMERS_VERSION}

RUN pip install .

WORKDIR /workspace

COPY create_qpc.py create_qpc.py

# do not buffer output, print them immediately
ENV PYTHONUNBUFFERED=1

ENTRYPOINT ["python3", "create_qpc.py"]

🧱 Build Docker Image

⚠️ Important: Run this command on your host machine (not inside a Docker container). Building Docker images inside another container can lead to unexpected volume or environment issues, especially with nested file systems.

# Navigate to the directory with your Dockerfile
export DOCKER_IMG_NAME=qpc_builder  # change if desired
docker build -t $DOCKER_IMG_NAME .

🚀 Run Docker Container to Create QPC

export TENSOR_PARALLELISM=1
export FULL_BATCH_SIZE=16
export CONTEXT_LENGTH=256
export PROMPT_LENGTH=128
export WHERE_TO_SAVE_QPCS=/qpcs # ensure this path exists, create if needed
export HF_TOKEN=<your_huggingface_token>
export MODEL_NAME=TinyLlama/TinyLlama-1.1B-Chat-v1.0
export CORES_PER_SOC=16

docker run -v $WHERE_TO_SAVE_QPCS:/qualcomm_compiled_models -v /opt:/opt -e HF_TOKEN=$HF_TOKEN $DOCKER_IMG_NAME \
    --model $MODEL_NAME \
    --fbs $FULL_BATCH_SIZE \
    --cl $CONTEXT_LENGTH \
    --tp $TENSOR_PARALLELISM \
    --pl $PROMPT_LENGTH \
    --cores_per_soc $CORES_PER_SOC

Your QPC will be saved under the directory specified by $WHERE_TO_SAVE_QPCS. Within it, you’ll find subdirectories based on the model name, configuration (e.g., batch size, prompt length), and other parameters. The actual QPC file is named programqpc.bin.


🧩 Step 2: Generate NR1 Deployment Binary

🏗️ Build and Compile with app.py

Create app.py:

from neureality.integrations.vllm import VLLModelCall
import numpy as np
import os
from neureality.common.nr_defines import NR_CONFIG_MODE_DEVICE, NR_CONFIG_MODE_SIM_TURBO_ON
from neureality import Tensor
import argparse
import json
from huggingface_hub import login


def nr_build_and_simulate(
    qpc_dir: str,
    batch_size: int,
    qpc_input_length: int,
    input_length: int,
    output_length: int,
    nof_activations: int,
    max_tokens: int,
    model_name_or_path: str,
    cg_path_device: str,
    cg_path_simulator: str,
    compile_to_nr1: bool = True,
    without_push_docker_image: bool = True,
):
    PROMPT_BUFFER_SIZE = 10 * input_length
    OUTPUT_BUFFER_SIZE = 10 * output_length
    SAMPLING_PARAMS_BUFFER_SIZE = 2048
    num_shards = int(
        os.getenv("TENSOR_PARALLELISM")
    )  # it should be the same as the TENSOR_PARALLELISM you compiled your qpc with

    # for tracing phase
    trace_prompt = np.zeros((1, PROMPT_BUFFER_SIZE), np.uint8)
    trace_sampling_params = np.zeros((1, SAMPLING_PARAMS_BUFFER_SIZE), np.uint8)

    tracing_sample = [Tensor(trace_prompt), Tensor(trace_sampling_params)]

    hf_token = os.getenv("HF_TOKEN")
    if hf_token is not None:
        login(hf_token)

    if compile_to_nr1:
        vllm_app_device = VLLModelCall(
            qpc_path=f"{qpc_dir}/programqpc.bin",
            max_output_size_b=OUTPUT_BUFFER_SIZE,
            model_name_or_path=model_name_or_path,
            num_activations=nof_activations,
            num_model_shards=num_shards,
            max_num_seqs=batch_size,
            max_model_len=input_length + output_length,  # context length (input length + output length)
            max_seq_len_to_capture=qpc_input_length,
            use_argmax_sampler=True,  # set to True to use an argmax sampler instead of the default vLLM sampler
        )

        vllm_app_device.build(cg_path_device, input_sample=tracing_sample, compilation_mode=NR_CONFIG_MODE_DEVICE, push_docker_image=not without_push_docker_image)
        print("Artifacts for NR1 are saved at:", cg_path_device)

    else:
        vllm_app_simulator = VLLModelCall(
            qpc_path=f"{qpc_dir}/programqpc.bin",
            max_output_size_b=OUTPUT_BUFFER_SIZE,
            model_name_or_path=model_name_or_path,
            # num_activations & num_model_shards is always 1 - even for multi or sharded instances, the simulation can be run as single instance (since we might not have enough cards to simulate on).
            num_activations=1,
            num_model_shards=1,
            max_num_seqs=batch_size,
            max_model_len=input_length + output_length,  # context length (input length + output length)
            max_seq_len_to_capture=qpc_input_length,
            use_argmax_sampler=True,  # for having vllm sampler of argmax, if what is needed is a regular sampler, just remove it
        )

        # Compile
        sim_exec = vllm_app_simulator.build(
            cg_path_simulator, input_sample=tracing_sample, compilation_mode=NR_CONFIG_MODE_SIM_TURBO_ON, push_docker_image=not without_push_docker_image
        )
        print("Artifacts for simulator are saved at:", cg_path_simulator)

        # Run - for running we nead real inputs (not as for tracing) for example:

        # initialize inputs
        prompt = "My name is"

        sampling_params = {
            "temperature": 0.0,  # make the model deterministic
            "ignore_eos": 1,
            "max_tokens": max_tokens,  # usually should be as the output_length, however you can define it <= output_length if you want the asnwer to be shorter for some reason
        }
        # Notice: use_beam_search=True is not supported since sampling_type == SamplingType.BEAM is not supported by qualcomm.

        params = json.dumps(sampling_params)  # Convert the dictionary to a JSON string

        prompt_np = np.frombuffer(prompt.encode("utf-8"), dtype=np.int8).reshape(1, -1)
        params_np = np.frombuffer(params.encode("utf-8"), dtype=np.int8).reshape(1, -1)

        # call the simulator
        sim_outputs = sim_exec([prompt_np, params_np])


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--qpc_dir",
        type=str,
        help="directory where the programqpc.bin is saved",
        default=os.getenv("QPC_PARENT_DIR_PATH"),
    )
    parser.add_argument(
        "--batch_size",
        type=int,
        help="batch size",
        default=64,
    )
    parser.add_argument(
        "--qpc_input_length",
        type=int,
        help="the prompt length the qpc was built with",
        default=128,
    )
    parser.add_argument(
        "--input_length",
        type=int,
        help="the actual input length of the prompt (in tokens)",
        default=1920,
    )
    parser.add_argument(
        "--output_length",
        type=int,
        help="the output length we get from the LLM (in tokens)",
        default=128,
    )
    parser.add_argument(
        "--nof_activations",
        type=int,
        help="number of instacnes",
        default=1,
    )
    parser.add_argument(
        "--max_tokens",
        type=int,
        help="max number of tokens to generate (can be <= output_length)",
        default=128,
    )
    parser.add_argument(
        "--model_name_or_path",  # read in the documnetation regarding this parameter
        type=str,
        help="either a name of a Hugging Face model such as meta-llama/Meta-Llama-3-8B or a path to a directory containing model/tokenizer configuration files",
        default="meta-llama/Meta-Llama-3-8B",
    )
    parser.add_argument(
        "--path_for_saving_artifacs_for_device",
        type=str,
        help="where to save nr1 artifacts",
        default="/neureality/sdk/toolchain/llama3_8b_device",
    )
    parser.add_argument(
        "--path_for_saving_artifacs_for_simualtor",
        type=str,
        help="where to save simualtor artifacts",
        default="/neureality/sdk/toolchain/llama3_8b_simulator",
    )
    
    parser.add_argument("--compile_to_nr1", action="store_true", help="compile to nr1 or to simulator")
    parser.add_argument("--without_push_docker_image", action="store_true", help="not pushing the Docker image to the registry")

    args = parser.parse_args()
    qpc_dir = args.qpc_dir
    batch_size = args.batch_size
    qpc_input_length = args.qpc_input_length
    input_length = args.input_length
    output_length = args.output_length
    nof_activations = args.nof_activations
    max_tokens = args.max_tokens
    model_name_or_path = args.model_name_or_path
    path_for_saving_artifacs_for_device = args.path_for_saving_artifacs_for_device
    path_for_saving_artifacs_for_simualtor = args.path_for_saving_artifacs_for_simualtor
    compile_to_nr1 = args.compile_to_nr1
    without_push_docker_image=args.without_push_docker_image

    nr_build_and_simulate(
        qpc_dir=qpc_dir,
        batch_size=batch_size,
        qpc_input_length=qpc_input_length,
        input_length=input_length,
        output_length=output_length,
        nof_activations=nof_activations,
        max_tokens=max_tokens,
        model_name_or_path=model_name_or_path,
        cg_path_device=path_for_saving_artifacs_for_device,
        cg_path_simulator=path_for_saving_artifacs_for_simualtor,
        compile_to_nr1=compile_to_nr1,
        without_push_docker_image=without_push_docker_image
    )


if __name__ == "__main__":
    main()

🏃 Run Script

To compile the model to NR1:

  1. Attach to the deployment toolchain image provided by the Neureality SDK. See here (the running part is under Running the Toolchain Container section): https://developer.neureality.ai/docs/_static/toolchain-api/setup.html

Notice to the next step before running the toolchain docker

  1. If your QPC was created outside the container (as described in the section above), ensure you mount the directory containing the QPC into the toolchain container using the -v flag when starting the container.

  2. Copy the app.py script (provided above) into the any path you want inside the container, for example, under /neureality/sdk/toolchain.

  3. Run the following command inside the container to compile the model to NR1.

    Please ensure that the following environment variables are defined before running:

    • TENSOR_PARALLELISM – the tensor parallelism value used when generating the QPC.

    • HF_TOKEN – your Hugging Face access token (if needed for model loading).

    • QPC_PARENT_DIR_PATH – the path to the generated QPC; this can be passed either via environment variable or using the --qpc_dir flag.

    • Make sure to pass the appropriate arguments that match the QPC you are using, including batch size, input length, output length, model name etc.

    • By default, the script push the runtime docker image to the container registry automatically. If you do not want to push the image after compilation, you can disable this behavior using the --without_push_docker_image flag.

    • If you’d like to retrieve and use the Docker image name manually (e.g., to push it to a different registry), you can use the following helper function:

    📄 get_docker_runtime_image_from_cg() (click to expand)
    import os
    import json
    
    def get_docker_runtime_image_from_cg(cg_path: str) -> str:
        descriptor_path = os.path.join(cg_path, "cg_descriptor.json")
        print(f"Looking for descriptor at: {descriptor_path}")
    
        if not os.path.exists(descriptor_path):
            print(f"cg_descriptor.json not found in {cg_path}. Cannot extract Docker image.")
            return
    
        print("Attempting to load descriptor data...")
        try:
            with open(descriptor_path, "r") as f:
                descriptor_data = json.load(f)
            print("Descriptor data loaded.")
    
            functions = descriptor_data.get("functions", [])
            dcr = None
            for function in functions:
                for impl in function.get("implementations", []):
                    binary_path = impl.get("binary_path")
                    if binary_path and binary_path.startswith("dcr."):
                        dcr = binary_path
                        break
                if dcr:
                    break
    
            if not dcr:
                print("Could not extract DCR (Docker image tag) from descriptor.")
                return
    
            print(f"Found DCR: {dcr}")
            return dcr
    
        except Exception as e:
            print(f"Error while reading descriptor: {e}")
    

    ✅ Usage example:

    dcr = get_docker_runtime_image_from_cg("/path/to/cg_output_dir")
    print(f"Docker image name: {dcr}")
    

    ℹ️ Replace "/path/to/cg_output_dir" with the actual path used in your compilation.
    This is typically the value passed to either:

    • --path_for_saving_artifacs_for_device (for NR1 compilation), or

    • --path_for_saving_artifacs_for_simulator (for simulator compilation).

export APP_PATH=app.py # change to the path where you put the app.py inside your contianer
python3 $APP_PATH --compile_to_nr1

If default paths are used, NR1 artifacts will be saved under:

/neureality/sdk/toolchain/llama3_8b_device

Export path for later usage:

export LLM_ARTIFACTS=/neureality/sdk/toolchain/llama3_8b_device

Available Flags

Flag

Type

Description

--qpc_dir

str

Directory of programqpc.bin (default: $QPC_PARENT_DIR_PATH)

--batch_size

int

Batch size (default: 64)

--qpc_input_length

int

Prompt length used when QPC was built (default: 128)

--input_length

int

Actual input prompt length (tokens) (default: 1920)

--output_length

int

Output length from LLM (tokens) (default: 128)

--nof_activations

int

Number of instances (default: 1)

--max_tokens

int

Maximum tokens to generate (≤ output_length) (default: 128)

--model_name_or_path

str

HF model name or path (default: meta-llama/Meta-Llama-3-8B)

--path_for_saving_artifacs_for_device

str

Path to save NR1 artifacts (default: /neureality/sdk/toolchain/llama3_8b_devicecg)

--path_for_saving_artifacs_for_simualtor

str

Path to save simulator artifacts (default: /neureality/sdk/toolchain/llama3_8b_simulator)

--compile_to_nr1

flag

Add this flag to compile to NR1

--without_push_docker_image

flag

Add this flag to prevent pushing the docker images to the registry


Note on parameter names (for clarity):

  • batch_size refers to FULL_BATCH_SIZE used during QPC creation.

  • qpc_input_length refers to the PROMPT_LENGTH used during QPC creation.

  • input_length is the actual input length (in tokens) you want to run inference on.
    It can be larger than qpc_input_length, in which case the prefill phase will automatically run in a loop behind the scenes.

  • output_length corresponds to the CONTEXT_LENGTH from the QPC creation step.

📌 For example, based on the QPC we created earlier:

batch_size = 16          # same as FULL_BATCH_SIZE
qpc_input_length = 128   # same as PROMPT_LENGTH
input_length = 128       # actual number of tokens used in prompt
output_length = 256      # same as CONTEXT_LENGTH

🧪 Optional: Compile to Simulator

python3 $APP_PATH

⚠️ Notice About model_name_or_path

There are two options for providing this parameter:

1. Hugging Face Model

model_name_or_path = "meta-llama/Meta-Llama-3-8B"

import os
os.environ["HF_TOKEN"] = "<your_hf_token>"

2. Local Path

Download and cache the model locally using Qualcomm Efficient Transformers:

touch load_model.py

Inside load_model.py:

model_name = "meta-llama/Meta-Llama-3-8B"

# Optional: export your HF token
# import os
# os.environ["HF_TOKEN"] = "<your_token>"

from QEfficient.utils import load_hf_tokenizer
tokenizer = load_hf_tokenizer(model_name, use_cache=True)

Run:

python3 /neureality/sdk/ai-pipelines/efficient-transformers/load_model.py

Locate your model snapshot:

/root/.cache/huggingface/hub/models--meta-llama--Meta-Llama-3-8B/snapshots/<hash>

Then use:

model_name_or_path = "/root/.cache/huggingface/hub/models--meta-llama--Meta-Llama-3-8B/snapshots/<hash>"

☁️ Upload to Local SSD or S3 (NR1 CG Store)

Artifacts compiled for NR1 must be uploaded to local SSD or an S3-compatible bucket (CG store).

You can upload using:

neureality.api.cg_store.upload_artifacts_to_s3

Example (from ResNet50 demo):

python3 pipelines/demos/resnet50/deploy/model_as_qpc/upload_to_cg_store.py \
    --build_path $LLM_ARTIFACTS \
    --bucket cg-store-dev \
    --bucket_obj_key example/key

This README provides a full lifecycle from QPC generation to deployment for Qualcomm-based LLM acceleration on Neureality NR1.