Spaces:
Running on Zero
Running on Zero
Upload folder using huggingface_hub (part 3)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- backend/llama.cpp/examples/model-conversion/scripts/causal/compare-embeddings-logits.sh +46 -0
- backend/llama.cpp/examples/model-conversion/scripts/causal/compare-logits.py +87 -0
- backend/llama.cpp/examples/model-conversion/scripts/causal/convert-model.sh +57 -0
- backend/llama.cpp/examples/model-conversion/scripts/causal/modelcard.template +13 -0
- backend/llama.cpp/examples/model-conversion/scripts/causal/run-casual-gen-embeddings-org.py +114 -0
- backend/llama.cpp/examples/model-conversion/scripts/causal/run-converted-model-embeddings-logits.sh +23 -0
- backend/llama.cpp/examples/model-conversion/scripts/causal/run-converted-model.sh +31 -0
- backend/llama.cpp/examples/model-conversion/scripts/causal/run-org-model.py +172 -0
- backend/llama.cpp/examples/model-conversion/scripts/embedding/compare-embeddings-logits.sh +84 -0
- backend/llama.cpp/examples/model-conversion/scripts/embedding/convert-model.sh +38 -0
- backend/llama.cpp/examples/model-conversion/scripts/embedding/modelcard.template +48 -0
- backend/llama.cpp/examples/model-conversion/scripts/embedding/run-converted-model.sh +55 -0
- backend/llama.cpp/examples/model-conversion/scripts/embedding/run-original-model.py +243 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/__init__.py +0 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/check-nmse.py +177 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/common.py +299 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/compare_tokens.py +76 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/create-collection-add-model.sh +8 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/curl-embedding-server.sh +6 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/hf-add-model-to-collection.py +80 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/hf-create-collection.py +106 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/hf-create-model.py +78 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/hf-upload-gguf-model.py +58 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/inspect-converted-model.sh +14 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/inspect-org-model.py +290 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/perplexity-gen.sh +40 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/perplexity-run-simple.sh +32 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/perplexity-run.sh +33 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/quantize.sh +53 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/run-embedding-server.sh +27 -0
- backend/llama.cpp/examples/model-conversion/scripts/utils/semantic_check.py +242 -0
- backend/llama.cpp/examples/parallel/CMakeLists.txt +5 -0
- backend/llama.cpp/examples/parallel/README.md +14 -0
- backend/llama.cpp/examples/parallel/parallel.cpp +520 -0
- backend/llama.cpp/examples/passkey/CMakeLists.txt +5 -0
- backend/llama.cpp/examples/passkey/README.md +15 -0
- backend/llama.cpp/examples/passkey/passkey.cpp +277 -0
- backend/llama.cpp/examples/pydantic_models_to_grammar.py +1322 -0
- backend/llama.cpp/examples/pydantic_models_to_grammar_examples.py +312 -0
- backend/llama.cpp/examples/reason-act.sh +16 -0
- backend/llama.cpp/examples/regex_to_grammar.py +20 -0
- backend/llama.cpp/examples/retrieval/CMakeLists.txt +5 -0
- backend/llama.cpp/examples/retrieval/README.md +69 -0
- backend/llama.cpp/examples/retrieval/retrieval.cpp +307 -0
- backend/llama.cpp/examples/server-llama2-13B.sh +26 -0
- backend/llama.cpp/examples/server_embd.py +35 -0
- backend/llama.cpp/examples/simple-chat/CMakeLists.txt +5 -0
- backend/llama.cpp/examples/simple-chat/README.md +7 -0
- backend/llama.cpp/examples/simple-chat/simple-chat.cpp +210 -0
- backend/llama.cpp/examples/simple-cmake-pkg/.gitignore +50 -0
backend/llama.cpp/examples/model-conversion/scripts/causal/compare-embeddings-logits.sh
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -e
|
| 4 |
+
|
| 5 |
+
MODEL_PATH="${1:-"$MODEL_PATH"}"
|
| 6 |
+
MODEL_NAME="${2:-$(basename "$MODEL_PATH")}"
|
| 7 |
+
|
| 8 |
+
CONVERTED_MODEL_PATH="${1:-"$CONVERTED_MODEL"}"
|
| 9 |
+
CONVERTED_MODEL_NAME="${2:-$(basename "$CONVERTED_MODEL_PATH" ".gguf")}"
|
| 10 |
+
|
| 11 |
+
if [ -t 0 ]; then
|
| 12 |
+
CPP_EMBEDDINGS="data/llamacpp-${CONVERTED_MODEL_NAME}-embeddings.bin"
|
| 13 |
+
else
|
| 14 |
+
# Process piped JSON data and convert to binary (matching logits.cpp format)
|
| 15 |
+
TEMP_FILE=$(mktemp /tmp/tmp.XXXXXX.binn)
|
| 16 |
+
python3 -c "
|
| 17 |
+
import json
|
| 18 |
+
import sys
|
| 19 |
+
import struct
|
| 20 |
+
|
| 21 |
+
data = json.load(sys.stdin)
|
| 22 |
+
|
| 23 |
+
# Flatten all embeddings completely
|
| 24 |
+
flattened = []
|
| 25 |
+
for item in data:
|
| 26 |
+
embedding = item['embedding']
|
| 27 |
+
for token_embedding in embedding:
|
| 28 |
+
flattened.extend(token_embedding)
|
| 29 |
+
|
| 30 |
+
print(f'Total embedding values: {len(flattened)}', file=sys.stderr)
|
| 31 |
+
|
| 32 |
+
# Write as binary floats - matches logitc.cpp fwrite format
|
| 33 |
+
with open('$TEMP_FILE', 'wb') as f:
|
| 34 |
+
for value in flattened:
|
| 35 |
+
f.write(struct.pack('f', value))
|
| 36 |
+
"
|
| 37 |
+
CPP_EMBEDDINGS="$TEMP_FILE"
|
| 38 |
+
trap "rm -f $TEMP_FILE" EXIT
|
| 39 |
+
fi
|
| 40 |
+
|
| 41 |
+
python scripts/utils/semantic_check.py --model-path $MODEL_PATH \
|
| 42 |
+
--python-embeddings data/pytorch-${MODEL_NAME}-embeddings.bin \
|
| 43 |
+
--cpp-embeddings $CPP_EMBEDDINGS \
|
| 44 |
+
--prompt "Hello world today" \
|
| 45 |
+
--causal
|
| 46 |
+
|
backend/llama.cpp/examples/model-conversion/scripts/causal/compare-logits.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
import sys
|
| 4 |
+
import numpy as np
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
# Add utils directory to path for direct script execution
|
| 9 |
+
sys.path.insert(0, str(Path(__file__).parent.parent / "utils"))
|
| 10 |
+
from common import get_model_name_from_env_path, compare_tokens, exit_with_warning # type: ignore[import-not-found, ty:unresolved-import]
|
| 11 |
+
|
| 12 |
+
def quick_logits_check(pytorch_file, llamacpp_file):
|
| 13 |
+
"""Lightweight sanity check before NMSE"""
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
pytorch_logits = np.fromfile(pytorch_file, dtype=np.float32)
|
| 17 |
+
llamacpp_logits = np.fromfile(llamacpp_file, dtype=np.float32)
|
| 18 |
+
except Exception as e:
|
| 19 |
+
print(f"❌ NOK: Failed to load files - {e}")
|
| 20 |
+
return False
|
| 21 |
+
|
| 22 |
+
# Check shapes match
|
| 23 |
+
if pytorch_logits.shape != llamacpp_logits.shape:
|
| 24 |
+
print(f"❌ NOK: Shape mismatch - PyTorch: {pytorch_logits.shape}, llama.cpp: {llamacpp_logits.shape}")
|
| 25 |
+
return False
|
| 26 |
+
|
| 27 |
+
# Calculate key metrics
|
| 28 |
+
diff = pytorch_logits - llamacpp_logits
|
| 29 |
+
abs_diff = np.abs(diff)
|
| 30 |
+
max_diff = np.max(abs_diff)
|
| 31 |
+
|
| 32 |
+
# Get top 10 predictions from both models
|
| 33 |
+
pytorch_top10 = np.argsort(pytorch_logits)[-10:][::-1]
|
| 34 |
+
llamacpp_top10 = np.argsort(llamacpp_logits)[-10:][::-1]
|
| 35 |
+
print(f"Top 10 PyTorch logits: {pytorch_logits[pytorch_top10]}")
|
| 36 |
+
print(f"Top 10 llama.cpp logits: {llamacpp_logits[llamacpp_top10]}")
|
| 37 |
+
print(f"Max absolute difference: {max_diff:.4f}")
|
| 38 |
+
|
| 39 |
+
return True
|
| 40 |
+
|
| 41 |
+
def main():
|
| 42 |
+
model_path = os.environ.get('MODEL_PATH')
|
| 43 |
+
model_name = get_model_name_from_env_path('MODEL_PATH')
|
| 44 |
+
data_dir = Path("data")
|
| 45 |
+
pytorch_file = data_dir / f"pytorch-{model_name}.bin"
|
| 46 |
+
|
| 47 |
+
llamacpp_model_name = get_model_name_from_env_path('CONVERTED_MODEL')
|
| 48 |
+
print(f"Using converted model: {llamacpp_model_name}")
|
| 49 |
+
llamacpp_file = data_dir / f"llamacpp-{llamacpp_model_name}.bin"
|
| 50 |
+
|
| 51 |
+
if not pytorch_file.exists():
|
| 52 |
+
print(f"Error: PyTorch logits file not found: {pytorch_file}")
|
| 53 |
+
print("Please run scripts/run-org-model.sh first to generate this file.")
|
| 54 |
+
sys.exit(1)
|
| 55 |
+
|
| 56 |
+
if not llamacpp_file.exists():
|
| 57 |
+
print(f"Error: llama.cpp logits file not found: {llamacpp_file}")
|
| 58 |
+
print("Please run scripts/run-converted-model.sh first to generate this file.")
|
| 59 |
+
sys.exit(1)
|
| 60 |
+
|
| 61 |
+
print("Checked all required files were found. Proceeding...\n")
|
| 62 |
+
|
| 63 |
+
# Verify tokens as they are a prerequisite for logits comparison.
|
| 64 |
+
print("🔍 Token Comparison Check")
|
| 65 |
+
print("=" * 40)
|
| 66 |
+
if not compare_tokens(f"pytorch-{model_name}", f"llamacpp-{llamacpp_model_name}"):
|
| 67 |
+
exit_with_warning("\n❌ Token mismatch detected", model_path)
|
| 68 |
+
print()
|
| 69 |
+
|
| 70 |
+
print("🔍 GGML Model Validation for model ", model_name)
|
| 71 |
+
print("=" * 40)
|
| 72 |
+
print(f"PyTorch logits : {pytorch_file}")
|
| 73 |
+
print(f"llama.cpp logits: {llamacpp_file}")
|
| 74 |
+
print()
|
| 75 |
+
|
| 76 |
+
success = quick_logits_check(pytorch_file, llamacpp_file)
|
| 77 |
+
|
| 78 |
+
# Exit with appropriate code
|
| 79 |
+
if success:
|
| 80 |
+
print("✅ OK: Lightweight model check successful!")
|
| 81 |
+
print(" Ok to proceed with NMSE check...")
|
| 82 |
+
sys.exit(0)
|
| 83 |
+
else:
|
| 84 |
+
exit_with_warning(f"❌ NOK: Top 10 predictions don't match - generation will differ", model_path)
|
| 85 |
+
|
| 86 |
+
if __name__ == "__main__":
|
| 87 |
+
main()
|
backend/llama.cpp/examples/model-conversion/scripts/causal/convert-model.sh
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -e
|
| 4 |
+
|
| 5 |
+
# Parse command line arguments
|
| 6 |
+
MMPROJ=""
|
| 7 |
+
DEBUG=""
|
| 8 |
+
while [[ $# -gt 0 ]]; do
|
| 9 |
+
case $1 in
|
| 10 |
+
--mmproj)
|
| 11 |
+
MMPROJ="--mmproj"
|
| 12 |
+
shift
|
| 13 |
+
;;
|
| 14 |
+
--debug)
|
| 15 |
+
DEBUG="1"
|
| 16 |
+
shift
|
| 17 |
+
;;
|
| 18 |
+
*)
|
| 19 |
+
shift
|
| 20 |
+
;;
|
| 21 |
+
esac
|
| 22 |
+
done
|
| 23 |
+
|
| 24 |
+
MODEL_NAME="${MODEL_NAME:-$(basename "$MODEL_PATH")}"
|
| 25 |
+
OUTPUT_DIR="${OUTPUT_DIR:-../../models}"
|
| 26 |
+
TYPE="${OUTTYPE:-f16}"
|
| 27 |
+
METADATA_OVERRIDE="${METADATA_OVERRIDE:-}"
|
| 28 |
+
if [[ -n "$MMPROJ" ]]; then
|
| 29 |
+
CONVERTED_MODEL="${OUTPUT_DIR}/mmproj-${MODEL_NAME}.gguf"
|
| 30 |
+
else
|
| 31 |
+
CONVERTED_MODEL="${OUTPUT_DIR}/${MODEL_NAME}.gguf"
|
| 32 |
+
fi
|
| 33 |
+
|
| 34 |
+
echo "Model path: ${MODEL_PATH}"
|
| 35 |
+
echo "Model name: ${MODEL_NAME}"
|
| 36 |
+
echo "Data type: ${TYPE}"
|
| 37 |
+
echo "Converted model path:: ${CONVERTED_MODEL}"
|
| 38 |
+
echo "Metadata override: ${METADATA_OVERRIDE}"
|
| 39 |
+
|
| 40 |
+
if [[ -n "$DEBUG" ]]; then
|
| 41 |
+
CMD_ARGS=("python" "-m" "pdb")
|
| 42 |
+
else
|
| 43 |
+
CMD_ARGS=("python")
|
| 44 |
+
fi
|
| 45 |
+
|
| 46 |
+
CMD_ARGS+=("../../convert_hf_to_gguf.py" "--verbose")
|
| 47 |
+
CMD_ARGS+=("${MODEL_PATH}")
|
| 48 |
+
CMD_ARGS+=("--outfile" "${CONVERTED_MODEL}")
|
| 49 |
+
CMD_ARGS+=("--outtype" "${TYPE}")
|
| 50 |
+
[[ -n "$METADATA_OVERRIDE" ]] && CMD_ARGS+=("--metadata" "${METADATA_OVERRIDE}")
|
| 51 |
+
[[ -n "$MMPROJ" ]] && CMD_ARGS+=("${MMPROJ}")
|
| 52 |
+
|
| 53 |
+
"${CMD_ARGS[@]}"
|
| 54 |
+
|
| 55 |
+
echo ""
|
| 56 |
+
echo "The environment variable CONVERTED_MODEL can be set to this path using:"
|
| 57 |
+
echo "export CONVERTED_MODEL=$(realpath ${CONVERTED_MODEL})"
|
backend/llama.cpp/examples/model-conversion/scripts/causal/modelcard.template
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
base_model:
|
| 3 |
+
- {base_model}
|
| 4 |
+
---
|
| 5 |
+
# {model_name} GGUF
|
| 6 |
+
|
| 7 |
+
Recommended way to run this model:
|
| 8 |
+
|
| 9 |
+
```sh
|
| 10 |
+
llama-server -hf {namespace}/{model_name}-GGUF
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
Then, access http://localhost:8080
|
backend/llama.cpp/examples/model-conversion/scripts/causal/run-casual-gen-embeddings-org.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import os
|
| 5 |
+
import importlib
|
| 6 |
+
import torch
|
| 7 |
+
import numpy as np
|
| 8 |
+
|
| 9 |
+
from transformers import AutoTokenizer, AutoConfig, AutoModelForCausalLM
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
unreleased_model_name = os.getenv('UNRELEASED_MODEL_NAME')
|
| 13 |
+
|
| 14 |
+
parser = argparse.ArgumentParser(description='Process model with specified path')
|
| 15 |
+
parser.add_argument('--model-path', '-m', help='Path to the model')
|
| 16 |
+
args = parser.parse_args()
|
| 17 |
+
|
| 18 |
+
model_path = os.environ.get('MODEL_PATH', args.model_path)
|
| 19 |
+
if model_path is None:
|
| 20 |
+
parser.error("Model path must be specified either via --model-path argument or MODEL_PATH environment variable")
|
| 21 |
+
|
| 22 |
+
config = AutoConfig.from_pretrained(model_path)
|
| 23 |
+
|
| 24 |
+
print("Model type: ", config.model_type)
|
| 25 |
+
print("Vocab size: ", config.vocab_size)
|
| 26 |
+
print("Hidden size: ", config.hidden_size)
|
| 27 |
+
print("Number of layers: ", config.num_hidden_layers)
|
| 28 |
+
print("BOS token id: ", config.bos_token_id)
|
| 29 |
+
print("EOS token id: ", config.eos_token_id)
|
| 30 |
+
|
| 31 |
+
print("Loading model and tokenizer using AutoTokenizer:", model_path)
|
| 32 |
+
tokenizer = AutoTokenizer.from_pretrained(model_path)
|
| 33 |
+
|
| 34 |
+
if unreleased_model_name:
|
| 35 |
+
model_name_lower = unreleased_model_name.lower()
|
| 36 |
+
unreleased_module_path = f"transformers.models.{model_name_lower}.modular_{model_name_lower}"
|
| 37 |
+
class_name = f"{unreleased_model_name}ForCausalLM"
|
| 38 |
+
print(f"Importing unreleased model module: {unreleased_module_path}")
|
| 39 |
+
|
| 40 |
+
try:
|
| 41 |
+
model_class = getattr(importlib.import_module(unreleased_module_path), class_name)
|
| 42 |
+
model = model_class.from_pretrained(model_path)
|
| 43 |
+
except (ImportError, AttributeError) as e:
|
| 44 |
+
print(f"Failed to import or load model: {e}")
|
| 45 |
+
print("Falling back to AutoModelForCausalLM")
|
| 46 |
+
model = AutoModelForCausalLM.from_pretrained(model_path)
|
| 47 |
+
else:
|
| 48 |
+
model = AutoModelForCausalLM.from_pretrained(model_path)
|
| 49 |
+
print(f"Model class: {type(model)}")
|
| 50 |
+
#print(f"Model file: {type(model).__module__}")
|
| 51 |
+
|
| 52 |
+
model_name = os.path.basename(model_path)
|
| 53 |
+
print(f"Model name: {model_name}")
|
| 54 |
+
|
| 55 |
+
prompt = "Hello world today"
|
| 56 |
+
input_ids = tokenizer(prompt, return_tensors="pt").input_ids # ty: ignore[call-non-callable]
|
| 57 |
+
print(f"Input tokens: {input_ids}")
|
| 58 |
+
print(f"Input text: {repr(prompt)}")
|
| 59 |
+
print(f"Tokenized: {tokenizer.convert_ids_to_tokens(input_ids[0])}") # ty: ignore[unresolved-attribute]
|
| 60 |
+
|
| 61 |
+
with torch.no_grad():
|
| 62 |
+
outputs = model(input_ids, output_hidden_states=True)
|
| 63 |
+
|
| 64 |
+
# Extract hidden states from the last layer
|
| 65 |
+
# outputs.hidden_states is a tuple of (num_layers + 1) tensors
|
| 66 |
+
# Index -1 gets the last layer, shape: [batch_size, seq_len, hidden_size]
|
| 67 |
+
last_hidden_states = outputs.hidden_states[-1]
|
| 68 |
+
|
| 69 |
+
# Get embeddings for all tokens
|
| 70 |
+
token_embeddings = last_hidden_states[0].float().cpu().numpy() # Remove batch dimension
|
| 71 |
+
|
| 72 |
+
print(f"Hidden states shape: {last_hidden_states.shape}")
|
| 73 |
+
print(f"Token embeddings shape: {token_embeddings.shape}")
|
| 74 |
+
print(f"Hidden dimension: {token_embeddings.shape[-1]}")
|
| 75 |
+
print(f"Number of tokens: {token_embeddings.shape[0]}")
|
| 76 |
+
|
| 77 |
+
# Save raw token embeddings
|
| 78 |
+
data_dir = Path("data")
|
| 79 |
+
data_dir.mkdir(exist_ok=True)
|
| 80 |
+
bin_filename = data_dir / f"pytorch-{model_name}-embeddings.bin"
|
| 81 |
+
txt_filename = data_dir / f"pytorch-{model_name}-embeddings.txt"
|
| 82 |
+
|
| 83 |
+
# Save all token embeddings as binary
|
| 84 |
+
print(token_embeddings)
|
| 85 |
+
token_embeddings.astype(np.float32).tofile(bin_filename)
|
| 86 |
+
|
| 87 |
+
# Save as text for inspection
|
| 88 |
+
with open(txt_filename, "w") as f:
|
| 89 |
+
for i, embedding in enumerate(token_embeddings):
|
| 90 |
+
for j, val in enumerate(embedding):
|
| 91 |
+
f.write(f"{i} {j} {val:.6f}\n")
|
| 92 |
+
|
| 93 |
+
# Print embeddings per token in the requested format
|
| 94 |
+
print("\nToken embeddings:")
|
| 95 |
+
tokens = tokenizer.convert_ids_to_tokens(input_ids[0]) # ty: ignore[unresolved-attribute]
|
| 96 |
+
for i, embedding in enumerate(token_embeddings):
|
| 97 |
+
# Format: show first few values, ..., then last few values
|
| 98 |
+
if len(embedding) > 10:
|
| 99 |
+
# Show first 3 and last 3 values with ... in between
|
| 100 |
+
first_vals = " ".join(f"{val:8.6f}" for val in embedding[:3])
|
| 101 |
+
last_vals = " ".join(f"{val:8.6f}" for val in embedding[-3:])
|
| 102 |
+
print(f"embedding {i}: {first_vals} ... {last_vals}")
|
| 103 |
+
else:
|
| 104 |
+
# If embedding is short, show all values
|
| 105 |
+
vals = " ".join(f"{val:8.6f}" for val in embedding)
|
| 106 |
+
print(f"embedding {i}: {vals}")
|
| 107 |
+
|
| 108 |
+
# Also show token info for reference
|
| 109 |
+
print(f"\nToken reference:")
|
| 110 |
+
for i, token in enumerate(tokens):
|
| 111 |
+
print(f" Token {i}: {repr(token)}")
|
| 112 |
+
|
| 113 |
+
print(f"Saved bin logits to: {bin_filename}")
|
| 114 |
+
print(f"Saved txt logist to: {txt_filename}")
|
backend/llama.cpp/examples/model-conversion/scripts/causal/run-converted-model-embeddings-logits.sh
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -e
|
| 4 |
+
|
| 5 |
+
# First try command line argument, then environment variable, then file
|
| 6 |
+
CONVERTED_MODEL="${1:-"$CONVERTED_MODEL"}"
|
| 7 |
+
BUILD_DIR="${2:-"$BUILD_DIR"}"
|
| 8 |
+
|
| 9 |
+
# Final check if we have a model path
|
| 10 |
+
if [ -z "$CONVERTED_MODEL" ]; then
|
| 11 |
+
echo "Error: Model path must be provided either as:" >&2
|
| 12 |
+
echo " 1. Command line argument" >&2
|
| 13 |
+
echo " 2. CONVERTED_MODEL environment variable" >&2
|
| 14 |
+
exit 1
|
| 15 |
+
fi
|
| 16 |
+
|
| 17 |
+
if [ -z "$BUILD_DIR" ]; then
|
| 18 |
+
BUILD_DIR="../../build"
|
| 19 |
+
fi
|
| 20 |
+
|
| 21 |
+
cmake --build ${BUILD_DIR} --target llama-debug -j8
|
| 22 |
+
|
| 23 |
+
${BUILD_DIR}/bin/llama-debug -m $CONVERTED_MODEL --embedding -p "Hello world today" --save-logits
|
backend/llama.cpp/examples/model-conversion/scripts/causal/run-converted-model.sh
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -e
|
| 4 |
+
|
| 5 |
+
# First try command line argument, then environment variable, then file
|
| 6 |
+
CONVERTED_MODEL="${1:-"$CONVERTED_MODEL"}"
|
| 7 |
+
MODEL_TESTING_PROMPT="${2:-"$MODEL_TESTING_PROMPT"}"
|
| 8 |
+
BUILD_DIR="${3:-"$BUILD_DIR"}"
|
| 9 |
+
|
| 10 |
+
if [ -z "$MODEL_TESTING_PROMPT" ]; then
|
| 11 |
+
MODEL_TESTING_PROMPT="Hello, my name is"
|
| 12 |
+
fi
|
| 13 |
+
|
| 14 |
+
if [ -z "$BUILD_DIR" ]; then
|
| 15 |
+
BUILD_DIR="../../build"
|
| 16 |
+
fi
|
| 17 |
+
|
| 18 |
+
# Final check if we have a model path
|
| 19 |
+
if [ -z "$CONVERTED_MODEL" ]; then
|
| 20 |
+
echo "Error: Model path must be provided either as:" >&2
|
| 21 |
+
echo " 1. Command line argument" >&2
|
| 22 |
+
echo " 2. CONVERTED_MODEL environment variable" >&2
|
| 23 |
+
exit 1
|
| 24 |
+
fi
|
| 25 |
+
|
| 26 |
+
echo $CONVERTED_MODEL
|
| 27 |
+
echo $MODEL_TESTING_PROMPT
|
| 28 |
+
|
| 29 |
+
cmake --build ${BUILD_DIR} --target llama-debug -j8
|
| 30 |
+
|
| 31 |
+
${BUILD_DIR}/bin/llama-debug -m "$CONVERTED_MODEL" -p "$MODEL_TESTING_PROMPT" --save-logits
|
backend/llama.cpp/examples/model-conversion/scripts/causal/run-org-model.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import os
|
| 5 |
+
import sys
|
| 6 |
+
import importlib
|
| 7 |
+
import torch
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM, AutoModelForImageTextToText, AutoConfig
|
| 11 |
+
|
| 12 |
+
# Add parent directory to path for imports
|
| 13 |
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
| 14 |
+
from utils.common import debug_hook, save_output_data
|
| 15 |
+
|
| 16 |
+
def parse_arguments():
|
| 17 |
+
parser = argparse.ArgumentParser(description="Process model with specified path")
|
| 18 |
+
parser.add_argument("--model-path", "-m", help="Path to the model")
|
| 19 |
+
parser.add_argument("--prompt-file", "-f", help="Optional prompt file", required=False)
|
| 20 |
+
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose debug output")
|
| 21 |
+
parser.add_argument("--device", "-d", help="Device to use (cpu, cuda, mps, auto)", default="auto")
|
| 22 |
+
return parser.parse_args()
|
| 23 |
+
|
| 24 |
+
def load_model_and_tokenizer(model_path, device="auto"):
|
| 25 |
+
print("Loading model and tokenizer using AutoTokenizer:", model_path)
|
| 26 |
+
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
|
| 27 |
+
config = AutoConfig.from_pretrained(model_path, trust_remote_code=True)
|
| 28 |
+
multimodal = False
|
| 29 |
+
full_config = config
|
| 30 |
+
|
| 31 |
+
# Determine device_map based on device argument
|
| 32 |
+
if device == "cpu":
|
| 33 |
+
device_map = {"": "cpu"}
|
| 34 |
+
print("Forcing CPU usage")
|
| 35 |
+
elif device == "auto":
|
| 36 |
+
device_map = "auto"
|
| 37 |
+
else:
|
| 38 |
+
device_map = {"": device}
|
| 39 |
+
|
| 40 |
+
print("Model type: ", config.model_type)
|
| 41 |
+
if "vocab_size" not in config and "text_config" in config:
|
| 42 |
+
config = config.text_config
|
| 43 |
+
multimodal = True
|
| 44 |
+
|
| 45 |
+
def print_if_exists(label, obj, attr, default="N/A"):
|
| 46 |
+
val = getattr(obj, attr) if hasattr(obj, attr) else default
|
| 47 |
+
print(f"{label}", val)
|
| 48 |
+
|
| 49 |
+
print_if_exists("Vocab size: ", config, "vocab_size")
|
| 50 |
+
print_if_exists("Hidden size: ", config, "hidden_size")
|
| 51 |
+
print_if_exists("Number of layers: ", config, "num_hidden_layers")
|
| 52 |
+
print_if_exists("BOS token id: ", config, "bos_token_id")
|
| 53 |
+
print_if_exists("EOS token id: ", config, "eos_token_id")
|
| 54 |
+
|
| 55 |
+
unreleased_model_name = os.getenv("UNRELEASED_MODEL_NAME")
|
| 56 |
+
if unreleased_model_name:
|
| 57 |
+
model_name_lower = unreleased_model_name.lower()
|
| 58 |
+
unreleased_module_path = (
|
| 59 |
+
f"transformers.models.{model_name_lower}.modular_{model_name_lower}"
|
| 60 |
+
)
|
| 61 |
+
class_name = f"{unreleased_model_name}ForCausalLM"
|
| 62 |
+
print(f"Importing unreleased model module: {unreleased_module_path}")
|
| 63 |
+
|
| 64 |
+
try:
|
| 65 |
+
model_class = getattr(importlib.import_module(unreleased_module_path), class_name)
|
| 66 |
+
model = model_class.from_pretrained(
|
| 67 |
+
model_path,
|
| 68 |
+
device_map=device_map,
|
| 69 |
+
offload_folder="offload",
|
| 70 |
+
trust_remote_code=True,
|
| 71 |
+
config=config
|
| 72 |
+
)
|
| 73 |
+
except (ImportError, AttributeError) as e:
|
| 74 |
+
print(f"Failed to import or load model: {e}")
|
| 75 |
+
exit(1)
|
| 76 |
+
else:
|
| 77 |
+
if multimodal:
|
| 78 |
+
model = AutoModelForImageTextToText.from_pretrained(
|
| 79 |
+
model_path,
|
| 80 |
+
device_map=device_map,
|
| 81 |
+
offload_folder="offload",
|
| 82 |
+
trust_remote_code=True,
|
| 83 |
+
config=full_config
|
| 84 |
+
)
|
| 85 |
+
else:
|
| 86 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 87 |
+
model_path,
|
| 88 |
+
device_map=device_map,
|
| 89 |
+
offload_folder="offload",
|
| 90 |
+
trust_remote_code=True,
|
| 91 |
+
config=config
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
print(f"Model class: {model.__class__.__name__}")
|
| 95 |
+
|
| 96 |
+
return model, tokenizer, config
|
| 97 |
+
|
| 98 |
+
def enable_torch_debugging(model):
|
| 99 |
+
for name, module in model.named_modules():
|
| 100 |
+
if len(list(module.children())) == 0: # only leaf modules
|
| 101 |
+
module.register_forward_hook(debug_hook(name))
|
| 102 |
+
|
| 103 |
+
def get_prompt(args):
|
| 104 |
+
if args.prompt_file:
|
| 105 |
+
with open(args.prompt_file, encoding='utf-8') as f:
|
| 106 |
+
return f.read()
|
| 107 |
+
elif os.getenv("MODEL_TESTING_PROMPT"):
|
| 108 |
+
return os.getenv("MODEL_TESTING_PROMPT")
|
| 109 |
+
else:
|
| 110 |
+
return "Hello, my name is"
|
| 111 |
+
|
| 112 |
+
def main():
|
| 113 |
+
args = parse_arguments()
|
| 114 |
+
model_path = os.environ.get("MODEL_PATH", args.model_path)
|
| 115 |
+
if model_path is None:
|
| 116 |
+
print("Error: Model path must be specified either via --model-path argument or MODEL_PATH environment variable")
|
| 117 |
+
sys.exit(1)
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
model, tokenizer, config = load_model_and_tokenizer(model_path, args.device)
|
| 121 |
+
|
| 122 |
+
if args.verbose:
|
| 123 |
+
enable_torch_debugging(model)
|
| 124 |
+
|
| 125 |
+
model_name = os.path.basename(model_path)
|
| 126 |
+
|
| 127 |
+
# Iterate over the model parameters (the tensors) and get the first one
|
| 128 |
+
# and use it to get the device the model is on.
|
| 129 |
+
device = next(model.parameters()).device
|
| 130 |
+
prompt = get_prompt(args)
|
| 131 |
+
input_ids = tokenizer(prompt, return_tensors="pt").input_ids.to(device)
|
| 132 |
+
token_ids = input_ids[0].cpu().tolist()
|
| 133 |
+
|
| 134 |
+
print(f"Input tokens: {input_ids}")
|
| 135 |
+
print(f"Input text: {repr(prompt)}")
|
| 136 |
+
print(f"Tokenized: {tokenizer.convert_ids_to_tokens(input_ids[0])}")
|
| 137 |
+
|
| 138 |
+
batch_size = 512
|
| 139 |
+
|
| 140 |
+
with torch.no_grad():
|
| 141 |
+
past = None
|
| 142 |
+
outputs = None
|
| 143 |
+
for i in range(0, input_ids.size(1), batch_size):
|
| 144 |
+
print(f"Processing chunk with tokens {i} to {i + batch_size}")
|
| 145 |
+
chunk = input_ids[:, i:i + batch_size]
|
| 146 |
+
outputs = model(chunk.to(model.device), past_key_values=past, use_cache=True)
|
| 147 |
+
past = outputs.past_key_values
|
| 148 |
+
|
| 149 |
+
logits = outputs.logits # type: ignore
|
| 150 |
+
|
| 151 |
+
# Extract logits for the last token (next token prediction)
|
| 152 |
+
last_logits = logits[0, -1, :].float().cpu().numpy()
|
| 153 |
+
|
| 154 |
+
print(f"Logits shape: {logits.shape}")
|
| 155 |
+
print(f"Last token logits shape: {last_logits.shape}")
|
| 156 |
+
print(f"Vocab size: {len(last_logits)}")
|
| 157 |
+
|
| 158 |
+
# Print some sample logits for quick verification
|
| 159 |
+
print(f"First 10 logits: {last_logits[:10]}")
|
| 160 |
+
print(f"Last 10 logits: {last_logits[-10:]}")
|
| 161 |
+
|
| 162 |
+
# Show top 5 predicted tokens
|
| 163 |
+
top_indices = np.argsort(last_logits)[-5:][::-1]
|
| 164 |
+
print("Top 5 predictions:")
|
| 165 |
+
for idx in top_indices:
|
| 166 |
+
token = tokenizer.decode([idx])
|
| 167 |
+
print(f" Token {idx} ({repr(token)}): {last_logits[idx]:.6f}")
|
| 168 |
+
|
| 169 |
+
save_output_data(last_logits, token_ids, prompt, model_name)
|
| 170 |
+
|
| 171 |
+
if __name__ == "__main__":
|
| 172 |
+
main()
|
backend/llama.cpp/examples/model-conversion/scripts/embedding/compare-embeddings-logits.sh
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -e
|
| 4 |
+
|
| 5 |
+
# Parse command line arguments
|
| 6 |
+
MODEL_PATH=""
|
| 7 |
+
MODEL_NAME=""
|
| 8 |
+
PROMPTS_FILE=""
|
| 9 |
+
|
| 10 |
+
# First argument is always model path
|
| 11 |
+
if [ $# -gt 0 ] && [[ "$1" != --* ]]; then
|
| 12 |
+
MODEL_PATH="$1"
|
| 13 |
+
shift
|
| 14 |
+
fi
|
| 15 |
+
|
| 16 |
+
# Parse remaining arguments
|
| 17 |
+
while [[ $# -gt 0 ]]; do
|
| 18 |
+
case $1 in
|
| 19 |
+
--prompts-file|-pf)
|
| 20 |
+
PROMPTS_FILE="$2"
|
| 21 |
+
shift 2
|
| 22 |
+
;;
|
| 23 |
+
*)
|
| 24 |
+
# If MODEL_NAME not set and this isn't a flag, use as model name
|
| 25 |
+
if [ -z "$MODEL_NAME" ] && [[ "$1" != --* ]]; then
|
| 26 |
+
MODEL_NAME="$1"
|
| 27 |
+
fi
|
| 28 |
+
shift
|
| 29 |
+
;;
|
| 30 |
+
esac
|
| 31 |
+
done
|
| 32 |
+
|
| 33 |
+
# Set defaults
|
| 34 |
+
MODEL_PATH="${MODEL_PATH:-"$EMBEDDING_MODEL_PATH"}"
|
| 35 |
+
MODEL_NAME="${MODEL_NAME:-$(basename "$MODEL_PATH")}"
|
| 36 |
+
|
| 37 |
+
CONVERTED_MODEL_PATH="${CONVERTED_EMBEDDING_PATH:-"$CONVERTED_EMBEDDING_MODEL"}"
|
| 38 |
+
CONVERTED_MODEL_NAME="${CONVERTED_MODEL_NAME:-$(basename "$CONVERTED_MODEL_PATH" .gguf)}"
|
| 39 |
+
|
| 40 |
+
if [ -t 0 ]; then
|
| 41 |
+
CPP_EMBEDDINGS="data/llamacpp-${CONVERTED_MODEL_NAME}-embeddings.bin"
|
| 42 |
+
else
|
| 43 |
+
# Process piped JSON data and convert to binary (matching logits.cpp format)
|
| 44 |
+
TEMP_FILE=$(mktemp /tmp/tmp.XXXXXX.binn)
|
| 45 |
+
python3 -c "
|
| 46 |
+
import json
|
| 47 |
+
import sys
|
| 48 |
+
import struct
|
| 49 |
+
|
| 50 |
+
data = json.load(sys.stdin)
|
| 51 |
+
|
| 52 |
+
# Flatten all embeddings completely
|
| 53 |
+
flattened = []
|
| 54 |
+
for item in data:
|
| 55 |
+
embedding = item['embedding']
|
| 56 |
+
for token_embedding in embedding:
|
| 57 |
+
flattened.extend(token_embedding)
|
| 58 |
+
|
| 59 |
+
print(f'Total embedding values: {len(flattened)}', file=sys.stderr)
|
| 60 |
+
|
| 61 |
+
# Write as binary floats - matches logitc.cpp fwrite format
|
| 62 |
+
with open('$TEMP_FILE', 'wb') as f:
|
| 63 |
+
for value in flattened:
|
| 64 |
+
f.write(struct.pack('f', value))
|
| 65 |
+
"
|
| 66 |
+
CPP_EMBEDDINGS="$TEMP_FILE"
|
| 67 |
+
trap "rm -f $TEMP_FILE" EXIT
|
| 68 |
+
fi
|
| 69 |
+
|
| 70 |
+
# Build the semantic_check.py command
|
| 71 |
+
SEMANTIC_CMD="python scripts/utils/semantic_check.py --model-path $MODEL_PATH \
|
| 72 |
+
--python-embeddings data/pytorch-${MODEL_NAME}-embeddings.bin \
|
| 73 |
+
--cpp-embeddings $CPP_EMBEDDINGS"
|
| 74 |
+
|
| 75 |
+
# Add prompts file if specified, otherwise use default prompt
|
| 76 |
+
if [ -n "$PROMPTS_FILE" ]; then
|
| 77 |
+
SEMANTIC_CMD="$SEMANTIC_CMD --prompts-file \"$PROMPTS_FILE\""
|
| 78 |
+
else
|
| 79 |
+
SEMANTIC_CMD="$SEMANTIC_CMD --prompt \"Hello world today\""
|
| 80 |
+
fi
|
| 81 |
+
|
| 82 |
+
# Execute the command
|
| 83 |
+
eval $SEMANTIC_CMD
|
| 84 |
+
|
backend/llama.cpp/examples/model-conversion/scripts/embedding/convert-model.sh
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -e
|
| 4 |
+
|
| 5 |
+
# Parse command line arguments
|
| 6 |
+
SENTENCE_TRANSFORMERS=""
|
| 7 |
+
while [[ $# -gt 0 ]]; do
|
| 8 |
+
case $1 in
|
| 9 |
+
-st|--sentence-transformers)
|
| 10 |
+
SENTENCE_TRANSFORMERS="--sentence-transformers-dense-modules"
|
| 11 |
+
shift
|
| 12 |
+
;;
|
| 13 |
+
*)
|
| 14 |
+
echo "Unknown option: $1"
|
| 15 |
+
exit 1
|
| 16 |
+
;;
|
| 17 |
+
esac
|
| 18 |
+
done
|
| 19 |
+
|
| 20 |
+
MODEL_NAME="${MODEL_NAME:-$(basename "$EMBEDDING_MODEL_PATH")}"
|
| 21 |
+
OUTPUT_DIR="${OUTPUT_DIR:-../../models}"
|
| 22 |
+
TYPE="${OUTTYPE:-f16}"
|
| 23 |
+
METADATA_OVERRIDE="${METADATA_OVERRIDE:-}"
|
| 24 |
+
CONVERTED_MODEL="${OUTPUT_DIR}/${MODEL_NAME}.gguf"
|
| 25 |
+
|
| 26 |
+
echo "Model path: ${EMBEDDING_MODEL_PATH}"
|
| 27 |
+
echo "Model name: ${MODEL_NAME}"
|
| 28 |
+
echo "Data type: ${TYPE}"
|
| 29 |
+
echo "Converted model path:: ${CONVERTED_MODEL}"
|
| 30 |
+
python ../../convert_hf_to_gguf.py --verbose \
|
| 31 |
+
${EMBEDDING_MODEL_PATH} \
|
| 32 |
+
--outfile ${CONVERTED_MODEL} \
|
| 33 |
+
--outtype ${TYPE} \
|
| 34 |
+
${SENTENCE_TRANSFORMERS}
|
| 35 |
+
|
| 36 |
+
echo ""
|
| 37 |
+
echo "The environment variable CONVERTED_EMBEDDING MODEL can be set to this path using:"
|
| 38 |
+
echo "export CONVERTED_EMBEDDING_MODEL=$(realpath ${CONVERTED_MODEL})"
|
backend/llama.cpp/examples/model-conversion/scripts/embedding/modelcard.template
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
base_model:
|
| 3 |
+
- {base_model}
|
| 4 |
+
---
|
| 5 |
+
# {model_name} GGUF
|
| 6 |
+
|
| 7 |
+
Recommended way to run this model:
|
| 8 |
+
|
| 9 |
+
```sh
|
| 10 |
+
llama-server -hf {namespace}/{model_name}-GGUF --embeddings
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
Then the endpoint can be accessed at http://localhost:8080/embedding, for
|
| 14 |
+
example using `curl`:
|
| 15 |
+
```console
|
| 16 |
+
curl --request POST \
|
| 17 |
+
--url http://localhost:8080/embedding \
|
| 18 |
+
--header "Content-Type: application/json" \
|
| 19 |
+
--data '{{"input": "Hello embeddings"}}' \
|
| 20 |
+
--silent
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
Alternatively, the `llama-embedding` command line tool can be used:
|
| 24 |
+
```sh
|
| 25 |
+
llama-embedding -hf {namespace}/{model_name}-GGUF --verbose-prompt -p "Hello embeddings"
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
#### embd_normalize
|
| 29 |
+
When a model uses pooling, or the pooling method is specified using `--pooling`,
|
| 30 |
+
the normalization can be controlled by the `embd_normalize` parameter.
|
| 31 |
+
|
| 32 |
+
The default value is `2` which means that the embeddings are normalized using
|
| 33 |
+
the Euclidean norm (L2). Other options are:
|
| 34 |
+
* -1 No normalization
|
| 35 |
+
* 0 Max absolute
|
| 36 |
+
* 1 Taxicab
|
| 37 |
+
* 2 Euclidean/L2
|
| 38 |
+
* \>2 P-Norm
|
| 39 |
+
|
| 40 |
+
This can be passed in the request body to `llama-server`, for example:
|
| 41 |
+
```sh
|
| 42 |
+
--data '{{"input": "Hello embeddings", "embd_normalize": -1}}' \
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
And for `llama-embedding`, by passing `--embd-normalize <value>`, for example:
|
| 46 |
+
```sh
|
| 47 |
+
llama-embedding -hf {namespace}/{model_name}-GGUF --embd-normalize -1 -p "Hello embeddings"
|
| 48 |
+
```
|
backend/llama.cpp/examples/model-conversion/scripts/embedding/run-converted-model.sh
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -e
|
| 4 |
+
|
| 5 |
+
# Parse command line arguments
|
| 6 |
+
CONVERTED_MODEL=""
|
| 7 |
+
PROMPTS_FILE=""
|
| 8 |
+
EMBD_NORMALIZE="2"
|
| 9 |
+
|
| 10 |
+
while [[ $# -gt 0 ]]; do
|
| 11 |
+
case $1 in
|
| 12 |
+
-p|--prompts-file)
|
| 13 |
+
PROMPTS_FILE="$2"
|
| 14 |
+
shift 2
|
| 15 |
+
;;
|
| 16 |
+
--embd-normalize)
|
| 17 |
+
EMBD_NORMALIZE="$2"
|
| 18 |
+
shift 2
|
| 19 |
+
;;
|
| 20 |
+
*)
|
| 21 |
+
if [ -z "$CONVERTED_MODEL" ]; then
|
| 22 |
+
CONVERTED_MODEL="$1"
|
| 23 |
+
fi
|
| 24 |
+
shift
|
| 25 |
+
;;
|
| 26 |
+
esac
|
| 27 |
+
done
|
| 28 |
+
|
| 29 |
+
# First try command line argument, then environment variable
|
| 30 |
+
CONVERTED_MODEL="${CONVERTED_MODEL:-"$CONVERTED_EMBEDDING_MODEL"}"
|
| 31 |
+
BUILD_DIR="${BUILD_DIR:-"../../build"}"
|
| 32 |
+
|
| 33 |
+
# Final check if we have a model path
|
| 34 |
+
if [ -z "$CONVERTED_MODEL" ]; then
|
| 35 |
+
echo "Error: Model path must be provided either as:" >&2
|
| 36 |
+
echo " 1. Command line argument" >&2
|
| 37 |
+
echo " 2. CONVERTED_EMBEDDING_MODEL environment variable" >&2
|
| 38 |
+
exit 1
|
| 39 |
+
fi
|
| 40 |
+
|
| 41 |
+
# Read prompt from file or use default
|
| 42 |
+
if [ -n "$PROMPTS_FILE" ]; then
|
| 43 |
+
if [ ! -f "$PROMPTS_FILE" ]; then
|
| 44 |
+
echo "Error: Prompts file '$PROMPTS_FILE' not found" >&2
|
| 45 |
+
exit 1
|
| 46 |
+
fi
|
| 47 |
+
PROMPT=$(cat "$PROMPTS_FILE")
|
| 48 |
+
else
|
| 49 |
+
PROMPT="Hello world today"
|
| 50 |
+
fi
|
| 51 |
+
|
| 52 |
+
echo $CONVERTED_MODEL
|
| 53 |
+
|
| 54 |
+
cmake --build ${BUILD_DIR} --target llama-debug -j8
|
| 55 |
+
${BUILD_DIR}/bin/llama-debug -m "$CONVERTED_MODEL" --embedding -p "$PROMPT" --save-logits --embd-normalize $EMBD_NORMALIZE
|
backend/llama.cpp/examples/model-conversion/scripts/embedding/run-original-model.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import os
|
| 5 |
+
import sys
|
| 6 |
+
import importlib
|
| 7 |
+
|
| 8 |
+
from transformers import AutoTokenizer, AutoConfig, AutoModel
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
# Add parent directory to path for imports
|
| 12 |
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
| 13 |
+
from utils.common import save_output_data
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def parse_arguments():
|
| 17 |
+
parser = argparse.ArgumentParser(description='Run original embedding model')
|
| 18 |
+
parser.add_argument(
|
| 19 |
+
'--model-path',
|
| 20 |
+
'-m',
|
| 21 |
+
help='Path to the model'
|
| 22 |
+
)
|
| 23 |
+
parser.add_argument(
|
| 24 |
+
'--prompts-file',
|
| 25 |
+
'-p',
|
| 26 |
+
help='Path to file containing prompts (one per line)'
|
| 27 |
+
)
|
| 28 |
+
parser.add_argument(
|
| 29 |
+
'--use-sentence-transformers',
|
| 30 |
+
action='store_true',
|
| 31 |
+
help=('Use SentenceTransformer to apply all numbered layers '
|
| 32 |
+
'(01_Pooling, 02_Dense, 03_Dense, 04_Normalize)')
|
| 33 |
+
)
|
| 34 |
+
parser.add_argument(
|
| 35 |
+
'--device',
|
| 36 |
+
'-d',
|
| 37 |
+
help='Device to use (cpu, cuda, mps, auto)',
|
| 38 |
+
default='auto'
|
| 39 |
+
)
|
| 40 |
+
return parser.parse_args()
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def load_model_and_tokenizer(model_path, use_sentence_transformers=False, device="auto"):
|
| 44 |
+
if device == "cpu":
|
| 45 |
+
device_map = {"": "cpu"}
|
| 46 |
+
print("Forcing CPU usage")
|
| 47 |
+
elif device == "auto":
|
| 48 |
+
# On Mac, "auto" device_map can cause issues with accelerate
|
| 49 |
+
# So we detect the best device manually
|
| 50 |
+
if torch.cuda.is_available():
|
| 51 |
+
device_map = {"": "cuda"}
|
| 52 |
+
print("Using CUDA")
|
| 53 |
+
elif torch.backends.mps.is_available():
|
| 54 |
+
device_map = {"": "mps"}
|
| 55 |
+
print("Using MPS (Apple Metal)")
|
| 56 |
+
else:
|
| 57 |
+
device_map = {"": "cpu"}
|
| 58 |
+
print("Using CPU")
|
| 59 |
+
else:
|
| 60 |
+
device_map = {"": device}
|
| 61 |
+
|
| 62 |
+
if use_sentence_transformers:
|
| 63 |
+
from sentence_transformers import SentenceTransformer
|
| 64 |
+
print("Using SentenceTransformer to apply all numbered layers")
|
| 65 |
+
model = SentenceTransformer(model_path)
|
| 66 |
+
tokenizer = model.tokenizer
|
| 67 |
+
config = model[0].auto_model.config # ty: ignore[unresolved-attribute]
|
| 68 |
+
else:
|
| 69 |
+
tokenizer = AutoTokenizer.from_pretrained(model_path)
|
| 70 |
+
config = AutoConfig.from_pretrained(model_path, trust_remote_code=True)
|
| 71 |
+
|
| 72 |
+
# This can be used to override the sliding window size for manual testing. This
|
| 73 |
+
# can be useful to verify the sliding window attention mask in the original model
|
| 74 |
+
# and compare it with the converted .gguf model.
|
| 75 |
+
if hasattr(config, 'sliding_window'):
|
| 76 |
+
original_sliding_window = config.sliding_window
|
| 77 |
+
print(f"Modified sliding window: {original_sliding_window} -> {config.sliding_window}")
|
| 78 |
+
|
| 79 |
+
unreleased_model_name = os.getenv('UNRELEASED_MODEL_NAME')
|
| 80 |
+
print(f"Using unreleased model: {unreleased_model_name}")
|
| 81 |
+
if unreleased_model_name:
|
| 82 |
+
model_name_lower = unreleased_model_name.lower()
|
| 83 |
+
unreleased_module_path = f"transformers.models.{model_name_lower}.modular_{model_name_lower}"
|
| 84 |
+
class_name = f"{unreleased_model_name}Model"
|
| 85 |
+
print(f"Importing unreleased model module: {unreleased_module_path}")
|
| 86 |
+
|
| 87 |
+
try:
|
| 88 |
+
model_class = getattr(importlib.import_module(unreleased_module_path), class_name)
|
| 89 |
+
model = model_class.from_pretrained(
|
| 90 |
+
model_path,
|
| 91 |
+
device_map=device_map,
|
| 92 |
+
offload_folder="offload",
|
| 93 |
+
trust_remote_code=True,
|
| 94 |
+
config=config
|
| 95 |
+
)
|
| 96 |
+
except (ImportError, AttributeError) as e:
|
| 97 |
+
print(f"Failed to import or load model: {e}")
|
| 98 |
+
sys.exit(1)
|
| 99 |
+
else:
|
| 100 |
+
model = AutoModel.from_pretrained(
|
| 101 |
+
model_path,
|
| 102 |
+
device_map=device_map,
|
| 103 |
+
offload_folder="offload",
|
| 104 |
+
trust_remote_code=True,
|
| 105 |
+
config=config
|
| 106 |
+
)
|
| 107 |
+
print(f"Model class: {type(model)}")
|
| 108 |
+
print(f"Model file: {type(model).__module__}")
|
| 109 |
+
|
| 110 |
+
# Verify the model is using the correct sliding window
|
| 111 |
+
if hasattr(model.config, 'sliding_window'):
|
| 112 |
+
print(f"Model's sliding_window: {model.config.sliding_window}")
|
| 113 |
+
else:
|
| 114 |
+
print("Model config does not have sliding_window attribute")
|
| 115 |
+
|
| 116 |
+
return model, tokenizer, config
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def get_prompt(args):
|
| 120 |
+
if args.prompts_file:
|
| 121 |
+
try:
|
| 122 |
+
with open(args.prompts_file, 'r', encoding='utf-8') as f:
|
| 123 |
+
return f.read().strip()
|
| 124 |
+
except FileNotFoundError:
|
| 125 |
+
print(f"Error: Prompts file '{args.prompts_file}' not found")
|
| 126 |
+
sys.exit(1)
|
| 127 |
+
except Exception as e:
|
| 128 |
+
print(f"Error reading prompts file: {e}")
|
| 129 |
+
sys.exit(1)
|
| 130 |
+
else:
|
| 131 |
+
return "Hello world today"
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def main():
|
| 135 |
+
args = parse_arguments()
|
| 136 |
+
|
| 137 |
+
model_path = os.environ.get('EMBEDDING_MODEL_PATH', args.model_path)
|
| 138 |
+
if model_path is None:
|
| 139 |
+
print("Error: Model path must be specified either via --model-path argument "
|
| 140 |
+
"or EMBEDDING_MODEL_PATH environment variable")
|
| 141 |
+
sys.exit(1)
|
| 142 |
+
|
| 143 |
+
# Determine if we should use SentenceTransformer
|
| 144 |
+
use_st = (
|
| 145 |
+
args.use_sentence_transformers or os.environ.get('USE_SENTENCE_TRANSFORMERS', '').lower() in ('1', 'true', 'yes')
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
model, tokenizer, config = load_model_and_tokenizer(model_path, use_st, args.device)
|
| 149 |
+
|
| 150 |
+
# Get the device the model is on
|
| 151 |
+
if not use_st:
|
| 152 |
+
device = next(model.parameters()).device
|
| 153 |
+
else:
|
| 154 |
+
# For SentenceTransformer, get device from the underlying model
|
| 155 |
+
device = next(model[0].auto_model.parameters()).device
|
| 156 |
+
|
| 157 |
+
model_name = os.path.basename(model_path)
|
| 158 |
+
|
| 159 |
+
prompt_text = get_prompt(args)
|
| 160 |
+
texts = [prompt_text]
|
| 161 |
+
|
| 162 |
+
with torch.no_grad():
|
| 163 |
+
if use_st:
|
| 164 |
+
embeddings = model.encode(texts, convert_to_numpy=True)
|
| 165 |
+
all_embeddings = embeddings # Shape: [batch_size, hidden_size]
|
| 166 |
+
|
| 167 |
+
encoded = tokenizer(
|
| 168 |
+
texts,
|
| 169 |
+
padding=True,
|
| 170 |
+
truncation=True,
|
| 171 |
+
return_tensors="pt"
|
| 172 |
+
)
|
| 173 |
+
tokens = encoded['input_ids'][0]
|
| 174 |
+
token_ids = tokens.cpu().tolist()
|
| 175 |
+
token_strings = tokenizer.convert_ids_to_tokens(tokens)
|
| 176 |
+
for i, (token_id, token_str) in enumerate(zip(tokens, token_strings)):
|
| 177 |
+
print(f"{token_id:6d} -> '{token_str}'")
|
| 178 |
+
|
| 179 |
+
print(f"Embeddings shape (after all SentenceTransformer layers): {all_embeddings.shape}")
|
| 180 |
+
print(f"Embedding dimension: {all_embeddings.shape[1] if len(all_embeddings.shape) > 1 else all_embeddings.shape[0]}")
|
| 181 |
+
else:
|
| 182 |
+
# Standard approach: use base model output only
|
| 183 |
+
encoded = tokenizer(
|
| 184 |
+
texts,
|
| 185 |
+
padding=True,
|
| 186 |
+
truncation=True,
|
| 187 |
+
return_tensors="pt"
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
tokens = encoded['input_ids'][0]
|
| 191 |
+
token_ids = tokens.cpu().tolist()
|
| 192 |
+
token_strings = tokenizer.convert_ids_to_tokens(tokens)
|
| 193 |
+
for i, (token_id, token_str) in enumerate(zip(tokens, token_strings)):
|
| 194 |
+
print(f"{token_id:6d} -> '{token_str}'")
|
| 195 |
+
|
| 196 |
+
# Move inputs to the same device as the model
|
| 197 |
+
encoded = {k: v.to(device) for k, v in encoded.items()}
|
| 198 |
+
outputs = model(**encoded)
|
| 199 |
+
hidden_states = outputs.last_hidden_state # Shape: [batch_size, seq_len, hidden_size]
|
| 200 |
+
|
| 201 |
+
all_embeddings = hidden_states[0].float().cpu().numpy() # Shape: [seq_len, hidden_size]
|
| 202 |
+
|
| 203 |
+
print(f"Hidden states shape: {hidden_states.shape}")
|
| 204 |
+
print(f"All embeddings shape: {all_embeddings.shape}")
|
| 205 |
+
print(f"Embedding dimension: {all_embeddings.shape[1]}")
|
| 206 |
+
|
| 207 |
+
if len(all_embeddings.shape) == 1:
|
| 208 |
+
n_embd = all_embeddings.shape[0]
|
| 209 |
+
n_embd_count = 1
|
| 210 |
+
all_embeddings = all_embeddings.reshape(1, -1)
|
| 211 |
+
else:
|
| 212 |
+
n_embd = all_embeddings.shape[1]
|
| 213 |
+
n_embd_count = all_embeddings.shape[0]
|
| 214 |
+
|
| 215 |
+
print()
|
| 216 |
+
|
| 217 |
+
for j in range(n_embd_count):
|
| 218 |
+
embedding = all_embeddings[j]
|
| 219 |
+
print(f"embedding {j}: ", end="")
|
| 220 |
+
|
| 221 |
+
# Print first 3 values
|
| 222 |
+
for i in range(min(3, n_embd)):
|
| 223 |
+
print(f"{embedding[i]:9.6f} ", end="")
|
| 224 |
+
|
| 225 |
+
print(" ... ", end="")
|
| 226 |
+
|
| 227 |
+
# Print last 3 values
|
| 228 |
+
for i in range(n_embd - 3, n_embd):
|
| 229 |
+
print(f"{embedding[i]:9.6f} ", end="")
|
| 230 |
+
|
| 231 |
+
print() # New line
|
| 232 |
+
|
| 233 |
+
print()
|
| 234 |
+
|
| 235 |
+
flattened_embeddings = all_embeddings.flatten()
|
| 236 |
+
print(f"Total values: {len(flattened_embeddings)} ({n_embd_count} embeddings × {n_embd} dimensions)")
|
| 237 |
+
print("")
|
| 238 |
+
|
| 239 |
+
save_output_data(flattened_embeddings, token_ids, prompt_text, model_name, type_suffix="-embeddings")
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
if __name__ == "__main__":
|
| 243 |
+
main()
|
backend/llama.cpp/examples/model-conversion/scripts/utils/__init__.py
ADDED
|
File without changes
|
backend/llama.cpp/examples/model-conversion/scripts/utils/check-nmse.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import sys
|
| 5 |
+
import os
|
| 6 |
+
import argparse
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from common import get_model_name_from_env_path # type: ignore[import-not-found, ty:unresolved-import]
|
| 9 |
+
|
| 10 |
+
def calculate_nmse(reference, test):
|
| 11 |
+
mse = np.mean((test - reference) ** 2)
|
| 12 |
+
ref_var = np.var(reference)
|
| 13 |
+
if ref_var == 0:
|
| 14 |
+
nmse = float('inf') if mse > 0 else 0.0
|
| 15 |
+
return mse, mse, ref_var
|
| 16 |
+
|
| 17 |
+
nmse = mse / ref_var
|
| 18 |
+
|
| 19 |
+
return nmse, mse, ref_var
|
| 20 |
+
|
| 21 |
+
def load_logits(file_path):
|
| 22 |
+
if not os.path.exists(file_path):
|
| 23 |
+
raise FileNotFoundError(f"File not found: {file_path}")
|
| 24 |
+
|
| 25 |
+
if file_path.suffix == '.npy':
|
| 26 |
+
return np.load(file_path)
|
| 27 |
+
elif file_path.suffix == '.bin':
|
| 28 |
+
return np.fromfile(file_path, dtype=np.float32)
|
| 29 |
+
else:
|
| 30 |
+
# Try to load as text file
|
| 31 |
+
try:
|
| 32 |
+
# If it has index format "0: value", extract just values
|
| 33 |
+
data = []
|
| 34 |
+
with open(file_path, 'r') as f:
|
| 35 |
+
for line in f:
|
| 36 |
+
if ':' in line:
|
| 37 |
+
# Format: "index: value"
|
| 38 |
+
value = float(line.split(':')[1].strip())
|
| 39 |
+
else:
|
| 40 |
+
# Just the value
|
| 41 |
+
value = float(line.strip())
|
| 42 |
+
data.append(value)
|
| 43 |
+
return np.array(data, dtype=np.float32)
|
| 44 |
+
except:
|
| 45 |
+
return np.loadtxt(file_path, dtype=np.float32)
|
| 46 |
+
|
| 47 |
+
def interpret_nmse(nmse):
|
| 48 |
+
"""Provide interpretation of NMSE value"""
|
| 49 |
+
if nmse == 0:
|
| 50 |
+
return "Perfect match", "🎉"
|
| 51 |
+
elif nmse < 1e-6:
|
| 52 |
+
return "Essentially identical", "✅"
|
| 53 |
+
elif nmse < 1e-4:
|
| 54 |
+
return "Excellent match", "✅"
|
| 55 |
+
elif nmse < 1e-3:
|
| 56 |
+
return "Very good match", "👍"
|
| 57 |
+
elif nmse < 1e-2:
|
| 58 |
+
return "Good match", "👍"
|
| 59 |
+
elif nmse < 0.1:
|
| 60 |
+
return "Acceptable match", "⚠️"
|
| 61 |
+
elif nmse < 1.0:
|
| 62 |
+
return "Poor match", "❌"
|
| 63 |
+
else:
|
| 64 |
+
return "Very poor match (worse than noise)", "❌"
|
| 65 |
+
|
| 66 |
+
def main():
|
| 67 |
+
parser = argparse.ArgumentParser(description='Validate model logits')
|
| 68 |
+
parser.add_argument('-m', '--model-path', required=True, help='Path to the model directory')
|
| 69 |
+
args = parser.parse_args()
|
| 70 |
+
|
| 71 |
+
model_name = get_model_name_from_env_path('MODEL_PATH')
|
| 72 |
+
data_dir = Path("data")
|
| 73 |
+
|
| 74 |
+
pytorch_file = data_dir / f"pytorch-{model_name}.bin"
|
| 75 |
+
|
| 76 |
+
llamacpp_model_name = get_model_name_from_env_path('CONVERTED_MODEL')
|
| 77 |
+
llamacpp_file = data_dir / f"llamacpp-{llamacpp_model_name}.bin"
|
| 78 |
+
|
| 79 |
+
print(f"Model name: {model_name}")
|
| 80 |
+
print(f"PyTorch logits file: {pytorch_file}")
|
| 81 |
+
print(f"llama.cpp logits file: {llamacpp_file}")
|
| 82 |
+
|
| 83 |
+
reference_file = pytorch_file
|
| 84 |
+
test_file = llamacpp_file
|
| 85 |
+
|
| 86 |
+
print("📊 NMSE Check for Model Comparison")
|
| 87 |
+
print("=" * 50)
|
| 88 |
+
print(f"Reference (ground truth): {reference_file}")
|
| 89 |
+
print(f"Test (to evaluate): {test_file}")
|
| 90 |
+
print()
|
| 91 |
+
|
| 92 |
+
try:
|
| 93 |
+
print("Loading reference logits...")
|
| 94 |
+
reference = load_logits(reference_file)
|
| 95 |
+
print(f" Shape: {reference.shape}, Type: {reference.dtype}")
|
| 96 |
+
|
| 97 |
+
print("Loading test logits...")
|
| 98 |
+
test = load_logits(test_file)
|
| 99 |
+
print(f" Shape: {test.shape}, Type: {test.dtype}")
|
| 100 |
+
|
| 101 |
+
# Check shapes match
|
| 102 |
+
if reference.shape != test.shape:
|
| 103 |
+
print(f"\n❌ Error: Shape mismatch!")
|
| 104 |
+
print(f" Reference: {reference.shape}")
|
| 105 |
+
print(f" Test: {test.shape}")
|
| 106 |
+
sys.exit(1)
|
| 107 |
+
|
| 108 |
+
print(f"\n✅ Shapes match: {reference.shape}")
|
| 109 |
+
|
| 110 |
+
nmse, mse, ref_var = calculate_nmse(reference, test)
|
| 111 |
+
|
| 112 |
+
# Additional metrics
|
| 113 |
+
max_abs_error = np.max(np.abs(test - reference))
|
| 114 |
+
mean_abs_error = np.mean(np.abs(test - reference))
|
| 115 |
+
|
| 116 |
+
# Results
|
| 117 |
+
print(f"\n📈 METRICS")
|
| 118 |
+
print("=" * 30)
|
| 119 |
+
print(f"MSE (Mean Squared Error): {mse:.6e}")
|
| 120 |
+
print(f"Reference Variance: {ref_var:.6e}")
|
| 121 |
+
print(f"NMSE: {nmse:.6e}")
|
| 122 |
+
print(f"Max Absolute Error: {max_abs_error:.6f}")
|
| 123 |
+
print(f"Mean Absolute Error: {mean_abs_error:.6f}")
|
| 124 |
+
|
| 125 |
+
# NMSE in dB (common in signal processing)
|
| 126 |
+
if nmse > 0:
|
| 127 |
+
nmse_db = 10 * np.log10(nmse)
|
| 128 |
+
print(f"NMSE (dB): {nmse_db:.2f} dB")
|
| 129 |
+
|
| 130 |
+
# Interpretation
|
| 131 |
+
interpretation, emoji = interpret_nmse(nmse)
|
| 132 |
+
print(f"\n🎯 INTERPRETATION")
|
| 133 |
+
print("=" * 30)
|
| 134 |
+
print(f"{emoji} {interpretation}")
|
| 135 |
+
|
| 136 |
+
# Detailed guidance
|
| 137 |
+
print(f"\n📋 GUIDANCE")
|
| 138 |
+
print("=" * 30)
|
| 139 |
+
if nmse < 1e-3:
|
| 140 |
+
print("✅ EXCELLENT: Your GGML conversion is working very well!")
|
| 141 |
+
print(" The differences are negligible for practical use.")
|
| 142 |
+
elif nmse < 1e-2:
|
| 143 |
+
print("👍 GOOD: Your GGML conversion is working well.")
|
| 144 |
+
print(" Small differences are likely due to precision/quantization.")
|
| 145 |
+
elif nmse < 0.1:
|
| 146 |
+
print("⚠️ ACCEPTABLE: Conversion is working but with some differences.")
|
| 147 |
+
print(" Check if you're using quantization (Q4, Q8, etc.)")
|
| 148 |
+
print(" Test generation quality to see if it's acceptable.")
|
| 149 |
+
else:
|
| 150 |
+
print("❌ PROBLEMATIC: Large differences detected.")
|
| 151 |
+
print(" Check your conversion process for potential issues.")
|
| 152 |
+
print(" Verify you're using the same model weights.")
|
| 153 |
+
|
| 154 |
+
# NMSE benchmarks
|
| 155 |
+
print(f"\n📚 NMSE BENCHMARKS")
|
| 156 |
+
print("=" * 30)
|
| 157 |
+
print("< 1e-6: Essentially identical")
|
| 158 |
+
print("< 1e-4: Excellent (typical for good conversions)")
|
| 159 |
+
print("< 1e-3: Very good")
|
| 160 |
+
print("< 1e-2: Good (acceptable for most use cases)")
|
| 161 |
+
print("< 0.1: Acceptable (may need verification)")
|
| 162 |
+
print("> 1.0: Poor (worse than random)")
|
| 163 |
+
|
| 164 |
+
# Exit code based on NMSE
|
| 165 |
+
if nmse < 1e-2:
|
| 166 |
+
print(f"\n✅ RESULT: PASS (NMSE = {nmse:.2e})")
|
| 167 |
+
sys.exit(0)
|
| 168 |
+
else:
|
| 169 |
+
print(f"\n❌ RESULT: NEEDS REVIEW (NMSE = {nmse:.2e})")
|
| 170 |
+
sys.exit(1)
|
| 171 |
+
|
| 172 |
+
except Exception as e:
|
| 173 |
+
print(f"❌ Error: {e}")
|
| 174 |
+
sys.exit(1)
|
| 175 |
+
|
| 176 |
+
if __name__ == "__main__":
|
| 177 |
+
main()
|
backend/llama.cpp/examples/model-conversion/scripts/utils/common.py
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
import torch
|
| 6 |
+
import transformers
|
| 7 |
+
import json
|
| 8 |
+
import textwrap
|
| 9 |
+
import numpy as np
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def get_model_name_from_env_path(env_path_name):
|
| 14 |
+
model_path = os.getenv(env_path_name)
|
| 15 |
+
if not model_path:
|
| 16 |
+
print(f"Error: {env_path_name} environment variable not set")
|
| 17 |
+
sys.exit(1)
|
| 18 |
+
|
| 19 |
+
if not os.path.exists(model_path):
|
| 20 |
+
print(f"Error: Model file not found: {model_path}")
|
| 21 |
+
sys.exit(1)
|
| 22 |
+
|
| 23 |
+
name = os.path.basename(os.path.normpath(model_path))
|
| 24 |
+
if name.endswith(".gguf"):
|
| 25 |
+
name = name[:-5]
|
| 26 |
+
|
| 27 |
+
return name
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def summarize(tensor: torch.Tensor, name: str, max_seq: int = 3, max_vals: int = 3):
|
| 31 |
+
"""
|
| 32 |
+
Print a tensor in llama.cpp debug style.
|
| 33 |
+
|
| 34 |
+
Supports:
|
| 35 |
+
- 2D tensors (seq, hidden)
|
| 36 |
+
- 3D tensors (batch, seq, hidden)
|
| 37 |
+
- 4D tensors (batch, seq, heads, dim_per_head) via flattening heads × dim_per_head
|
| 38 |
+
|
| 39 |
+
Shows first and last max_vals of each vector per sequence position.
|
| 40 |
+
"""
|
| 41 |
+
t = tensor.detach().to(torch.float32).cpu()
|
| 42 |
+
|
| 43 |
+
# Determine dimensions
|
| 44 |
+
if t.ndim == 3:
|
| 45 |
+
_, s, _ = t.shape
|
| 46 |
+
elif t.ndim == 2:
|
| 47 |
+
_, s = 1, t.shape[0]
|
| 48 |
+
t = t.unsqueeze(0)
|
| 49 |
+
elif t.ndim == 4:
|
| 50 |
+
_, s, _, _ = t.shape
|
| 51 |
+
else:
|
| 52 |
+
print(f"Skipping tensor due to unsupported dimensions: {t.ndim}")
|
| 53 |
+
return
|
| 54 |
+
|
| 55 |
+
ten_shape = t.shape
|
| 56 |
+
|
| 57 |
+
print(f"ggml_debug: {name} = (f32) ... = {{{ten_shape}}}")
|
| 58 |
+
print(" [")
|
| 59 |
+
print(" [")
|
| 60 |
+
|
| 61 |
+
# Determine indices for first and last sequences
|
| 62 |
+
first_indices = list(range(min(s, max_seq)))
|
| 63 |
+
last_indices = list(range(max(0, s - max_seq), s))
|
| 64 |
+
|
| 65 |
+
# Check if there's an overlap between first and last indices or if we're at the edge case of s = 2 * max_seq
|
| 66 |
+
has_overlap = bool(set(first_indices) & set(last_indices)) or (max_seq * 2 == s)
|
| 67 |
+
|
| 68 |
+
# Combine indices
|
| 69 |
+
if has_overlap:
|
| 70 |
+
# If there's overlap, just use the combined unique indices
|
| 71 |
+
indices = sorted(list(set(first_indices + last_indices)))
|
| 72 |
+
separator_index = None
|
| 73 |
+
else:
|
| 74 |
+
# If no overlap, we'll add a separator between first and last sequences
|
| 75 |
+
indices = first_indices + last_indices
|
| 76 |
+
separator_index = len(first_indices)
|
| 77 |
+
|
| 78 |
+
for i, si in enumerate(indices):
|
| 79 |
+
# Add separator if needed
|
| 80 |
+
if separator_index is not None and i == separator_index:
|
| 81 |
+
print(" ...")
|
| 82 |
+
|
| 83 |
+
# Extract appropriate slice
|
| 84 |
+
vec = t[0, si]
|
| 85 |
+
if vec.ndim == 2: # 4D case: flatten heads × dim_per_head
|
| 86 |
+
flat = vec.flatten().tolist()
|
| 87 |
+
else: # 2D or 3D case
|
| 88 |
+
flat = vec.tolist()
|
| 89 |
+
|
| 90 |
+
# First and last slices
|
| 91 |
+
first = flat[:max_vals]
|
| 92 |
+
last = flat[-max_vals:] if len(flat) >= max_vals else flat
|
| 93 |
+
first_str = ", ".join(f"{v:12.4f}" for v in first)
|
| 94 |
+
last_str = ", ".join(f"{v:12.4f}" for v in last)
|
| 95 |
+
|
| 96 |
+
print(f" [{first_str}, ..., {last_str}]")
|
| 97 |
+
|
| 98 |
+
print(" ],")
|
| 99 |
+
print(" ]")
|
| 100 |
+
print(f" sum = {t.sum().item():.6f}\n")
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def debug_hook(name):
|
| 104 |
+
def fn(_m, input, output):
|
| 105 |
+
if isinstance(input, torch.Tensor):
|
| 106 |
+
summarize(input, name + "_in")
|
| 107 |
+
elif isinstance(input, (tuple, list)) and len(input) > 0 and isinstance(input[0], torch.Tensor):
|
| 108 |
+
summarize(input[0], name + "_in")
|
| 109 |
+
if isinstance(output, torch.Tensor):
|
| 110 |
+
summarize(output, name + "_out")
|
| 111 |
+
elif isinstance(output, (tuple, list)) and len(output) > 0 and isinstance(output[0], torch.Tensor):
|
| 112 |
+
summarize(output[0], name + "_out")
|
| 113 |
+
|
| 114 |
+
return fn
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def setup_rope_debug(model_module_path: str, function_name: str = "apply_rotary_pos_emb"):
|
| 118 |
+
"""
|
| 119 |
+
Apply monkey patch to dump RoPE activations for debugging.
|
| 120 |
+
|
| 121 |
+
Args:
|
| 122 |
+
model_module_path: Path to the model module (e.g., "transformers.models.apertus.modeling_apertus")
|
| 123 |
+
function_name: Name of the RoPE function to patch (default: "apply_rotary_pos_emb")
|
| 124 |
+
|
| 125 |
+
Example:
|
| 126 |
+
from utils.common import setup_rope_debug
|
| 127 |
+
setup_rope_debug("transformers.models.apertus.modeling_apertus")
|
| 128 |
+
"""
|
| 129 |
+
import importlib
|
| 130 |
+
|
| 131 |
+
# Import the module and get the original function
|
| 132 |
+
module = importlib.import_module(model_module_path)
|
| 133 |
+
orig_rope = getattr(module, function_name)
|
| 134 |
+
|
| 135 |
+
# Set torch print options for better debugging
|
| 136 |
+
torch.set_printoptions(threshold=float('inf'))
|
| 137 |
+
torch.set_printoptions(precision=6, sci_mode=False)
|
| 138 |
+
|
| 139 |
+
def debug_rope(q, k, cos, sin, position_ids=None, unsqueeze_dim=1):
|
| 140 |
+
# log inputs
|
| 141 |
+
summarize(q, "RoPE.q_in")
|
| 142 |
+
summarize(k, "RoPE.k_in")
|
| 143 |
+
|
| 144 |
+
# call original
|
| 145 |
+
q_out, k_out = orig_rope(q, k, cos, sin, position_ids, unsqueeze_dim)
|
| 146 |
+
|
| 147 |
+
# log outputs
|
| 148 |
+
summarize(q_out, "RoPE.q_out")
|
| 149 |
+
summarize(k_out, "RoPE.k_out")
|
| 150 |
+
|
| 151 |
+
return q_out, k_out
|
| 152 |
+
|
| 153 |
+
# Patch it
|
| 154 |
+
setattr(module, function_name, debug_rope)
|
| 155 |
+
print(f"RoPE debug patching applied to {model_module_path}.{function_name}")
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def save_output_data(data, tokens, prompt, model_name, type_suffix="", output_dir="data"):
|
| 159 |
+
"""
|
| 160 |
+
Save output data (logits/embeddings), tokens, and prompt to files.
|
| 161 |
+
|
| 162 |
+
Args:
|
| 163 |
+
data: numpy array of floats (logits or embeddings)
|
| 164 |
+
tokens: list or array of token IDs
|
| 165 |
+
prompt: string containing the input prompt
|
| 166 |
+
model_name: name of the model
|
| 167 |
+
type_suffix: optional suffix like "-embeddings" (default: "")
|
| 168 |
+
output_dir: directory to save files (default: "data")
|
| 169 |
+
|
| 170 |
+
Creates the following files in output_dir:
|
| 171 |
+
- pytorch-{model_name}{type_suffix}.bin
|
| 172 |
+
- pytorch-{model_name}{type_suffix}.txt
|
| 173 |
+
- pytorch-{model_name}{type_suffix}-prompt.txt
|
| 174 |
+
- pytorch-{model_name}{type_suffix}-tokens.bin
|
| 175 |
+
"""
|
| 176 |
+
data_dir = Path(output_dir)
|
| 177 |
+
data_dir.mkdir(exist_ok=True)
|
| 178 |
+
base_path = data_dir / f"pytorch-{model_name}{type_suffix}"
|
| 179 |
+
|
| 180 |
+
# Convert and flatten logits/embeddings
|
| 181 |
+
data = data.cpu().numpy() if isinstance(data, torch.Tensor) else np.asarray(data)
|
| 182 |
+
data = data.flatten() if data.ndim > 1 else data
|
| 183 |
+
|
| 184 |
+
# Save logits/embedding files
|
| 185 |
+
data.astype(np.float32).tofile(f"{base_path}.bin")
|
| 186 |
+
print(f"Data saved to {base_path}.bin")
|
| 187 |
+
|
| 188 |
+
with open(f"{base_path}.txt", "w") as f:
|
| 189 |
+
f.writelines(f"{i}: {value:.6f}\n" for i, value in enumerate(data))
|
| 190 |
+
print(f"Data saved to {base_path}.txt")
|
| 191 |
+
|
| 192 |
+
# Convert and flatten tokens
|
| 193 |
+
tokens = tokens.cpu().numpy() if isinstance(tokens, torch.Tensor) else np.asarray(tokens)
|
| 194 |
+
tokens = tokens.flatten() if tokens.ndim > 1 else tokens
|
| 195 |
+
|
| 196 |
+
# Save token binary file
|
| 197 |
+
tokens.astype(np.int32).tofile(f"{base_path}-tokens.bin")
|
| 198 |
+
print(f"Tokens saved to {base_path}-tokens.bin")
|
| 199 |
+
|
| 200 |
+
# Save prompt file
|
| 201 |
+
with open(f"{base_path}-prompt.txt", "w") as f:
|
| 202 |
+
f.write(f"prompt: {prompt}\n")
|
| 203 |
+
f.write(f"n_tokens: {len(tokens)}\n")
|
| 204 |
+
f.write(f"token ids: {', '.join(str(int(tid)) for tid in tokens)}\n")
|
| 205 |
+
print(f"Prompt saved to {base_path}-prompt.txt")
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def compare_tokens(original, converted, type_suffix="", output_dir="data"):
|
| 209 |
+
data_dir = Path(output_dir)
|
| 210 |
+
|
| 211 |
+
# Read tokens from both models
|
| 212 |
+
tokens1_file = data_dir / f"{original}{type_suffix}-tokens.bin"
|
| 213 |
+
tokens2_file = data_dir / f"{converted}{type_suffix}-tokens.bin"
|
| 214 |
+
|
| 215 |
+
if not tokens1_file.exists():
|
| 216 |
+
print(f"Error: Token file not found: {tokens1_file}")
|
| 217 |
+
return False
|
| 218 |
+
|
| 219 |
+
if not tokens2_file.exists():
|
| 220 |
+
print(f"Error: Token file not found: {tokens2_file}")
|
| 221 |
+
return False
|
| 222 |
+
|
| 223 |
+
tokens1 = np.fromfile(tokens1_file, dtype=np.int32)
|
| 224 |
+
tokens2 = np.fromfile(tokens2_file, dtype=np.int32)
|
| 225 |
+
|
| 226 |
+
print(f"\nComparing tokens between:")
|
| 227 |
+
print(f" Original : {original} ({len(tokens1)} tokens)")
|
| 228 |
+
print(f" Converted: {converted} ({len(tokens2)} tokens)")
|
| 229 |
+
|
| 230 |
+
if len(tokens1) != len(tokens2):
|
| 231 |
+
print(f"\n❌ Token count mismatch: {len(tokens1)} vs {len(tokens2)}")
|
| 232 |
+
return False
|
| 233 |
+
|
| 234 |
+
if np.array_equal(tokens1, tokens2):
|
| 235 |
+
print(f"\n✅ All {len(tokens1)} tokens match!")
|
| 236 |
+
return True
|
| 237 |
+
|
| 238 |
+
mismatches = np.where(tokens1 != tokens2)[0]
|
| 239 |
+
print(f"\n❌ Found {len(mismatches)} mismatched tokens:")
|
| 240 |
+
|
| 241 |
+
num_to_show = min(len(mismatches), 10)
|
| 242 |
+
for idx in mismatches[:num_to_show]:
|
| 243 |
+
print(f" Position {idx}: {tokens1[idx]} vs {tokens2[idx]}")
|
| 244 |
+
|
| 245 |
+
if len(mismatches) > num_to_show:
|
| 246 |
+
print(f" ... and {len(mismatches) - num_to_show} more mismatches")
|
| 247 |
+
|
| 248 |
+
return False
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def show_version_warning(current_version, model_version):
|
| 252 |
+
if not model_version:
|
| 253 |
+
return False
|
| 254 |
+
|
| 255 |
+
try:
|
| 256 |
+
from packaging.version import parse, InvalidVersion
|
| 257 |
+
try:
|
| 258 |
+
return parse(current_version) < parse(model_version)
|
| 259 |
+
except InvalidVersion:
|
| 260 |
+
return current_version != model_version
|
| 261 |
+
except ImportError:
|
| 262 |
+
return current_version != model_version
|
| 263 |
+
|
| 264 |
+
def get_model_transformers_version(model_path):
|
| 265 |
+
if not model_path:
|
| 266 |
+
return None
|
| 267 |
+
|
| 268 |
+
config_path = Path(model_path) / "config.json"
|
| 269 |
+
if not config_path.is_file():
|
| 270 |
+
return None
|
| 271 |
+
|
| 272 |
+
try:
|
| 273 |
+
with open(config_path, "r", encoding="utf-8") as f:
|
| 274 |
+
config = json.load(f)
|
| 275 |
+
return config.get("transformers_version")
|
| 276 |
+
except (IOError, json.JSONDecodeError) as e:
|
| 277 |
+
print(f"Warning: Could not read or parse {config_path}: {e}", file=sys.stderr)
|
| 278 |
+
return None
|
| 279 |
+
|
| 280 |
+
def exit_with_warning(message, model_path):
|
| 281 |
+
print(message)
|
| 282 |
+
|
| 283 |
+
if model_path and transformers is not None:
|
| 284 |
+
model_transformers_version = get_model_transformers_version(model_path)
|
| 285 |
+
transformers_version = transformers.__version__
|
| 286 |
+
if show_version_warning(transformers_version, model_transformers_version):
|
| 287 |
+
warning_message = f"""
|
| 288 |
+
=====================================================================
|
| 289 |
+
Verification failure might be due to a transformers version mismatch:
|
| 290 |
+
|
| 291 |
+
Current transformers version: {transformers_version}
|
| 292 |
+
Model's required version : {model_transformers_version}
|
| 293 |
+
|
| 294 |
+
Consider installing the version specified by the model's config:
|
| 295 |
+
pip install transformers=={model_transformers_version}
|
| 296 |
+
=====================================================================
|
| 297 |
+
"""
|
| 298 |
+
print(textwrap.dedent(warning_message))
|
| 299 |
+
sys.exit(1)
|
backend/llama.cpp/examples/model-conversion/scripts/utils/compare_tokens.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import sys
|
| 5 |
+
from common import compare_tokens # type: ignore[import-not-found, ty:unresolved-import]
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def parse_arguments():
|
| 9 |
+
parser = argparse.ArgumentParser(
|
| 10 |
+
description='Compare tokens between two models',
|
| 11 |
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
| 12 |
+
epilog="""
|
| 13 |
+
Examples:
|
| 14 |
+
%(prog)s pytorch-gemma-3-270m-it llamacpp-gemma-3-270m-it-bf16
|
| 15 |
+
"""
|
| 16 |
+
)
|
| 17 |
+
parser.add_argument(
|
| 18 |
+
'original',
|
| 19 |
+
help='Original model name'
|
| 20 |
+
)
|
| 21 |
+
parser.add_argument(
|
| 22 |
+
'converted',
|
| 23 |
+
help='Converted model name'
|
| 24 |
+
)
|
| 25 |
+
parser.add_argument(
|
| 26 |
+
'-s', '--suffix',
|
| 27 |
+
default='',
|
| 28 |
+
help='Type suffix (e.g., "-embeddings")'
|
| 29 |
+
)
|
| 30 |
+
parser.add_argument(
|
| 31 |
+
'-d', '--data-dir',
|
| 32 |
+
default='data',
|
| 33 |
+
help='Directory containing token files (default: data)'
|
| 34 |
+
)
|
| 35 |
+
parser.add_argument(
|
| 36 |
+
'-v', '--verbose',
|
| 37 |
+
action='store_true',
|
| 38 |
+
help='Print prompts from both models'
|
| 39 |
+
)
|
| 40 |
+
return parser.parse_args()
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def main():
|
| 44 |
+
args = parse_arguments()
|
| 45 |
+
|
| 46 |
+
if args.verbose:
|
| 47 |
+
from pathlib import Path
|
| 48 |
+
data_dir = Path(args.data_dir)
|
| 49 |
+
|
| 50 |
+
prompt1_file = data_dir / f"{args.original}{args.suffix}-prompt.txt"
|
| 51 |
+
prompt2_file = data_dir / f"{args.converted}{args.suffix}-prompt.txt"
|
| 52 |
+
|
| 53 |
+
if prompt1_file.exists():
|
| 54 |
+
print(f"\nOriginal model prompt ({args.original}):")
|
| 55 |
+
print(f" {prompt1_file.read_text().strip()}")
|
| 56 |
+
|
| 57 |
+
if prompt2_file.exists():
|
| 58 |
+
print(f"\nConverted model prompt ({args.converted}):")
|
| 59 |
+
print(f" {prompt2_file.read_text().strip()}")
|
| 60 |
+
|
| 61 |
+
print()
|
| 62 |
+
|
| 63 |
+
result = compare_tokens(
|
| 64 |
+
args.original,
|
| 65 |
+
args.converted,
|
| 66 |
+
type_suffix=args.suffix,
|
| 67 |
+
output_dir=args.data_dir
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
# Enable the script to be used in shell scripts so that they can check
|
| 71 |
+
# the exit code for success/failure.
|
| 72 |
+
sys.exit(0 if result else 1)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
if __name__ == "__main__":
|
| 76 |
+
main()
|
backend/llama.cpp/examples/model-conversion/scripts/utils/create-collection-add-model.sh
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
#!/usr/bin/env bash
|
| 3 |
+
|
| 4 |
+
COLLECTION_SLUG=$(python ./create_collection.py --return-slug)
|
| 5 |
+
echo "Created collection: $COLLECTION_SLUG"
|
| 6 |
+
|
| 7 |
+
# Use it in the next command
|
| 8 |
+
python add_model_to_collection.py "$COLLECTION_SLUG" "username/my-model"
|
backend/llama.cpp/examples/model-conversion/scripts/utils/curl-embedding-server.sh
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
curl --request POST \
|
| 3 |
+
--url http://localhost:8080/embedding \
|
| 4 |
+
--header "Content-Type: application/json" \
|
| 5 |
+
--data '{"input": "Hello world today"}' \
|
| 6 |
+
--silent
|
backend/llama.cpp/examples/model-conversion/scripts/utils/hf-add-model-to-collection.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
from huggingface_hub import HfApi
|
| 4 |
+
import argparse
|
| 5 |
+
import sys
|
| 6 |
+
|
| 7 |
+
def add_model_to_collection(collection_slug, model_id, note=""):
|
| 8 |
+
"""
|
| 9 |
+
Add a model to an existing collection
|
| 10 |
+
|
| 11 |
+
Args:
|
| 12 |
+
collection_slug: The slug of the collection (e.g., "username/collection-name-12345")
|
| 13 |
+
model_id: The model repository ID (e.g., "username/model-name")
|
| 14 |
+
note: Optional note about the model
|
| 15 |
+
|
| 16 |
+
Returns:
|
| 17 |
+
True if successful, False if failed
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
# Initialize API
|
| 21 |
+
api = HfApi()
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
user_info = api.whoami()
|
| 25 |
+
print(f"✅ Authenticated as: {user_info['name']}")
|
| 26 |
+
|
| 27 |
+
# Verify the model exists
|
| 28 |
+
print(f"🔍 Checking if model exists: {model_id}")
|
| 29 |
+
try:
|
| 30 |
+
model_info = api.model_info(model_id)
|
| 31 |
+
except Exception as e:
|
| 32 |
+
print(f"❌ Model not found or not accessible: {model_id}")
|
| 33 |
+
print(f"Error: {e}")
|
| 34 |
+
return False
|
| 35 |
+
|
| 36 |
+
print(f"📚 Adding model to collection...")
|
| 37 |
+
api.add_collection_item(
|
| 38 |
+
collection_slug=collection_slug,
|
| 39 |
+
item_id=model_id,
|
| 40 |
+
item_type="model",
|
| 41 |
+
note=note
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
print(f"✅ Model added to collection successfully!")
|
| 45 |
+
print(f"🔗 Collection URL: https://huggingface.co/collections/{collection_slug}")
|
| 46 |
+
|
| 47 |
+
return True
|
| 48 |
+
|
| 49 |
+
except Exception as e:
|
| 50 |
+
print(f"❌ Error adding model to collection: {e}")
|
| 51 |
+
return False
|
| 52 |
+
|
| 53 |
+
def main():
|
| 54 |
+
# This script requires that the environment variable HF_TOKEN is set with your
|
| 55 |
+
# Hugging Face API token.
|
| 56 |
+
api = HfApi()
|
| 57 |
+
|
| 58 |
+
parser = argparse.ArgumentParser(description='Add model to a Huggingface Collection')
|
| 59 |
+
parser.add_argument('--collection', '-c', help='The collection slug username/collection-hash', required=True)
|
| 60 |
+
parser.add_argument('--model', '-m', help='The model to add to the Collection', required=True)
|
| 61 |
+
parser.add_argument('--note', '-n', help='An optional note/description', required=False)
|
| 62 |
+
args = parser.parse_args()
|
| 63 |
+
|
| 64 |
+
collection = args.collection
|
| 65 |
+
model = args.model
|
| 66 |
+
note = args.note
|
| 67 |
+
|
| 68 |
+
success = add_model_to_collection(
|
| 69 |
+
collection_slug=collection,
|
| 70 |
+
model_id=model,
|
| 71 |
+
note=note
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
if success:
|
| 75 |
+
print("\n🎉 Model added successfully!")
|
| 76 |
+
else:
|
| 77 |
+
print("\n❌ Failed to add model to collection")
|
| 78 |
+
sys.exit(1)
|
| 79 |
+
if __name__ == "__main__":
|
| 80 |
+
main()
|
backend/llama.cpp/examples/model-conversion/scripts/utils/hf-create-collection.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
from huggingface_hub import HfApi
|
| 4 |
+
import argparse
|
| 5 |
+
import os
|
| 6 |
+
import sys
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def create_collection(title, description, private=False, namespace=None, return_slug=False):
|
| 10 |
+
"""
|
| 11 |
+
Create a new collection on Hugging Face
|
| 12 |
+
|
| 13 |
+
Args:
|
| 14 |
+
title: Collection title
|
| 15 |
+
description: Collection description
|
| 16 |
+
private: Whether the collection should be private (default: False)
|
| 17 |
+
namespace: Optional namespace (defaults to your username)
|
| 18 |
+
|
| 19 |
+
Returns:
|
| 20 |
+
Collection object if successful, None if failed
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
# Check if HF_TOKEN is available
|
| 24 |
+
token = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_HUB_TOKEN")
|
| 25 |
+
if not token:
|
| 26 |
+
print("❌ No HF_TOKEN or HUGGINGFACE_HUB_TOKEN found in environment variables")
|
| 27 |
+
print("Please set your Hugging Face token as an environment variable")
|
| 28 |
+
return None
|
| 29 |
+
|
| 30 |
+
# Initialize API
|
| 31 |
+
api = HfApi()
|
| 32 |
+
|
| 33 |
+
try:
|
| 34 |
+
# Test authentication first
|
| 35 |
+
user_info = api.whoami()
|
| 36 |
+
if not return_slug:
|
| 37 |
+
print(f"✅ Authenticated as: {user_info['name']}")
|
| 38 |
+
|
| 39 |
+
# Create the collection
|
| 40 |
+
if not return_slug:
|
| 41 |
+
print(f"📚 Creating collection: '{title}'...")
|
| 42 |
+
collection = api.create_collection(
|
| 43 |
+
title=title,
|
| 44 |
+
description=description,
|
| 45 |
+
private=private,
|
| 46 |
+
namespace=namespace
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
if not return_slug:
|
| 50 |
+
print(f"✅ Collection created successfully!")
|
| 51 |
+
print(f"📋 Collection slug: {collection.slug}")
|
| 52 |
+
print(f"🔗 Collection URL: https://huggingface.co/collections/{collection.slug}")
|
| 53 |
+
|
| 54 |
+
return collection
|
| 55 |
+
|
| 56 |
+
except Exception as e:
|
| 57 |
+
print(f"❌ Error creating collection: {e}")
|
| 58 |
+
return None
|
| 59 |
+
|
| 60 |
+
def main():
|
| 61 |
+
# This script requires that the environment variable HF_TOKEN is set with your
|
| 62 |
+
# Hugging Face API token.
|
| 63 |
+
api = HfApi()
|
| 64 |
+
|
| 65 |
+
parser = argparse.ArgumentParser(description='Create a Huggingface Collection')
|
| 66 |
+
parser.add_argument('--name', '-n', help='The name/title of the Collection', required=True)
|
| 67 |
+
parser.add_argument('--description', '-d', help='The description for the Collection', required=True)
|
| 68 |
+
parser.add_argument('--namespace', '-ns', help='The namespace to add the Collection to', required=True)
|
| 69 |
+
parser.add_argument('--private', '-p', help='Create a private Collection', action='store_true') # Fixed
|
| 70 |
+
parser.add_argument('--return-slug', '-s', help='Only output the collection slug', action='store_true') # Fixed
|
| 71 |
+
|
| 72 |
+
args = parser.parse_args()
|
| 73 |
+
|
| 74 |
+
name = args.name
|
| 75 |
+
description = args.description
|
| 76 |
+
private = args.private
|
| 77 |
+
namespace = args.namespace
|
| 78 |
+
return_slug = args.return_slug
|
| 79 |
+
|
| 80 |
+
if not return_slug:
|
| 81 |
+
print("🚀 Creating Hugging Face Collection")
|
| 82 |
+
print(f"Title: {name}")
|
| 83 |
+
print(f"Description: {description}")
|
| 84 |
+
print(f"Namespace: {namespace}")
|
| 85 |
+
print(f"Private: {private}")
|
| 86 |
+
|
| 87 |
+
collection = create_collection(
|
| 88 |
+
title=name,
|
| 89 |
+
description=description,
|
| 90 |
+
private=private,
|
| 91 |
+
namespace=namespace,
|
| 92 |
+
return_slug=return_slug
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
if collection:
|
| 96 |
+
if return_slug:
|
| 97 |
+
print(collection.slug)
|
| 98 |
+
else:
|
| 99 |
+
print("\n🎉 Collection created successfully!")
|
| 100 |
+
print(f"Use this slug to add models: {collection.slug}")
|
| 101 |
+
else:
|
| 102 |
+
print("\n❌ Failed to create collection")
|
| 103 |
+
sys.exit(1)
|
| 104 |
+
|
| 105 |
+
if __name__ == "__main__":
|
| 106 |
+
main()
|
backend/llama.cpp/examples/model-conversion/scripts/utils/hf-create-model.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
from huggingface_hub import HfApi
|
| 4 |
+
import argparse
|
| 5 |
+
|
| 6 |
+
# This script requires that the environment variable HF_TOKEN is set with your
|
| 7 |
+
# Hugging Face API token.
|
| 8 |
+
api = HfApi()
|
| 9 |
+
|
| 10 |
+
def load_template_and_substitute(template_path, **kwargs):
|
| 11 |
+
try:
|
| 12 |
+
with open(template_path, 'r', encoding='utf-8') as f:
|
| 13 |
+
template_content = f.read()
|
| 14 |
+
|
| 15 |
+
return template_content.format(**kwargs)
|
| 16 |
+
except FileNotFoundError:
|
| 17 |
+
print(f"Template file '{template_path}' not found!")
|
| 18 |
+
return None
|
| 19 |
+
except KeyError as e:
|
| 20 |
+
print(f"Missing template variable: {e}")
|
| 21 |
+
return None
|
| 22 |
+
|
| 23 |
+
parser = argparse.ArgumentParser(description='Create a new Hugging Face model repository')
|
| 24 |
+
parser.add_argument('--model-name', '-m', help='Name for the model', required=True)
|
| 25 |
+
parser.add_argument('--namespace', '-ns', help='Namespace to add the model to', required=True)
|
| 26 |
+
parser.add_argument('--org-base-model', '-b', help='Original Base model name', default="")
|
| 27 |
+
parser.add_argument('--no-card', action='store_true', help='Skip creating model card')
|
| 28 |
+
parser.add_argument('--private', '-p', action='store_true', help='Create private model')
|
| 29 |
+
parser.add_argument('--embedding', '-e', action='store_true', help='Use embedding model card template')
|
| 30 |
+
parser.add_argument('--dry-run', '-d', action='store_true', help='Print repository info and template without creating repository')
|
| 31 |
+
|
| 32 |
+
args = parser.parse_args()
|
| 33 |
+
|
| 34 |
+
repo_id = f"{args.namespace}/{args.model_name}-GGUF"
|
| 35 |
+
print("Repository ID: ", repo_id)
|
| 36 |
+
|
| 37 |
+
repo_url = None
|
| 38 |
+
if not args.dry_run:
|
| 39 |
+
repo_url = api.create_repo(
|
| 40 |
+
repo_id=repo_id,
|
| 41 |
+
repo_type="model",
|
| 42 |
+
private=args.private,
|
| 43 |
+
exist_ok=False
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
if not args.no_card:
|
| 47 |
+
if args.embedding:
|
| 48 |
+
template_path = "scripts/embedding/modelcard.template"
|
| 49 |
+
else:
|
| 50 |
+
template_path = "scripts/causal/modelcard.template"
|
| 51 |
+
|
| 52 |
+
print("Template path: ", template_path)
|
| 53 |
+
|
| 54 |
+
model_card_content = load_template_and_substitute(
|
| 55 |
+
template_path,
|
| 56 |
+
model_name=args.model_name,
|
| 57 |
+
namespace=args.namespace,
|
| 58 |
+
base_model=args.org_base_model,
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
if args.dry_run:
|
| 62 |
+
print("\nTemplate Content:\n")
|
| 63 |
+
print(model_card_content)
|
| 64 |
+
else:
|
| 65 |
+
if model_card_content:
|
| 66 |
+
api.upload_file(
|
| 67 |
+
path_or_fileobj=model_card_content.encode('utf-8'),
|
| 68 |
+
path_in_repo="README.md",
|
| 69 |
+
repo_id=repo_id
|
| 70 |
+
)
|
| 71 |
+
print("Model card created successfully.")
|
| 72 |
+
else:
|
| 73 |
+
print("Failed to create model card.")
|
| 74 |
+
|
| 75 |
+
if not args.dry_run and repo_url:
|
| 76 |
+
print(f"Repository created: {repo_url}")
|
| 77 |
+
|
| 78 |
+
|
backend/llama.cpp/examples/model-conversion/scripts/utils/hf-upload-gguf-model.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
from huggingface_hub import HfApi
|
| 4 |
+
import argparse
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
def upload_gguf_file(local_file_path, repo_id, filename_in_repo=None):
|
| 8 |
+
"""
|
| 9 |
+
Upload a GGUF file to a Hugging Face model repository
|
| 10 |
+
|
| 11 |
+
Args:
|
| 12 |
+
local_file_path: Path to your local GGUF file
|
| 13 |
+
repo_id: Your repository ID (e.g., "username/model-name")
|
| 14 |
+
filename_in_repo: Optional custom name for the file in the repo
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
if not os.path.exists(local_file_path):
|
| 18 |
+
print(f"❌ File not found: {local_file_path}")
|
| 19 |
+
return False
|
| 20 |
+
|
| 21 |
+
if filename_in_repo is None:
|
| 22 |
+
filename_in_repo = os.path.basename(local_file_path)
|
| 23 |
+
|
| 24 |
+
if filename_in_repo is None or filename_in_repo == "":
|
| 25 |
+
filename_in_repo = os.path.basename(local_file_path)
|
| 26 |
+
|
| 27 |
+
print(f"📤 Uploading {local_file_path} to {repo_id}/{filename_in_repo}")
|
| 28 |
+
|
| 29 |
+
api = HfApi()
|
| 30 |
+
|
| 31 |
+
try:
|
| 32 |
+
api.upload_file(
|
| 33 |
+
path_or_fileobj=local_file_path,
|
| 34 |
+
path_in_repo=filename_in_repo,
|
| 35 |
+
repo_id=repo_id,
|
| 36 |
+
repo_type="model",
|
| 37 |
+
commit_message=f"Upload {filename_in_repo}"
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
print("✅ Upload successful!")
|
| 41 |
+
print(f"🔗 File available at: https://huggingface.co/{repo_id}/blob/main/{filename_in_repo}")
|
| 42 |
+
return True
|
| 43 |
+
|
| 44 |
+
except Exception as e:
|
| 45 |
+
print(f"❌ Upload failed: {e}")
|
| 46 |
+
return False
|
| 47 |
+
|
| 48 |
+
# This script requires that the environment variable HF_TOKEN is set with your
|
| 49 |
+
# Hugging Face API token.
|
| 50 |
+
api = HfApi()
|
| 51 |
+
|
| 52 |
+
parser = argparse.ArgumentParser(description='Upload a GGUF model to a Huggingface model repository')
|
| 53 |
+
parser.add_argument('--gguf-model-path', '-m', help='The GGUF model file to upload', required=True)
|
| 54 |
+
parser.add_argument('--repo-id', '-r', help='The repository to upload to', required=True)
|
| 55 |
+
parser.add_argument('--name', '-o', help='The name in the model repository', required=False)
|
| 56 |
+
args = parser.parse_args()
|
| 57 |
+
|
| 58 |
+
upload_gguf_file(args.gguf_model_path, args.repo_id, args.name)
|
backend/llama.cpp/examples/model-conversion/scripts/utils/inspect-converted-model.sh
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
# First try command line argument, then environment variable, then file
|
| 4 |
+
CONVERTED_MODEL="${1:-"$CONVERTED_MODEL"}"
|
| 5 |
+
|
| 6 |
+
# Final check if we have a model path
|
| 7 |
+
if [ -z "$CONVERTED_MODEL" ]; then
|
| 8 |
+
echo "Error: Model path must be provided either as:" >&2
|
| 9 |
+
echo " 1. Command line argument" >&2
|
| 10 |
+
echo " 2. CONVERTED_MODEL environment variable" >&2
|
| 11 |
+
exit 1
|
| 12 |
+
fi
|
| 13 |
+
|
| 14 |
+
../../gguf-py/gguf/scripts/gguf_dump.py $CONVERTED_MODEL
|
backend/llama.cpp/examples/model-conversion/scripts/utils/inspect-org-model.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
import re
|
| 7 |
+
import struct
|
| 8 |
+
import sys
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from typing import Optional
|
| 11 |
+
from safetensors import safe_open
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
MODEL_SAFETENSORS_FILE = "model.safetensors"
|
| 15 |
+
MODEL_SAFETENSORS_INDEX = "model.safetensors.index.json"
|
| 16 |
+
|
| 17 |
+
DTYPE_SIZES = {
|
| 18 |
+
"F64": 8, "I64": 8, "U64": 8,
|
| 19 |
+
"F32": 4, "I32": 4, "U32": 4,
|
| 20 |
+
"F16": 2, "BF16": 2, "I16": 2, "U16": 2,
|
| 21 |
+
"I8": 1, "U8": 1, "BOOL": 1,
|
| 22 |
+
"F8_E4M3": 1, "F8_E5M2": 1,
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB']
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def get_weight_map(model_path: Path) -> Optional[dict[str, str]]:
|
| 29 |
+
index_file = model_path / MODEL_SAFETENSORS_INDEX
|
| 30 |
+
|
| 31 |
+
if index_file.exists():
|
| 32 |
+
with open(index_file, 'r') as f:
|
| 33 |
+
index = json.load(f)
|
| 34 |
+
return index.get("weight_map", {})
|
| 35 |
+
|
| 36 |
+
return None
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def get_all_tensor_names(model_path: Path) -> list[str]:
|
| 40 |
+
weight_map = get_weight_map(model_path)
|
| 41 |
+
|
| 42 |
+
if weight_map is not None:
|
| 43 |
+
return list(weight_map.keys())
|
| 44 |
+
|
| 45 |
+
single_file = model_path / MODEL_SAFETENSORS_FILE
|
| 46 |
+
if single_file.exists():
|
| 47 |
+
try:
|
| 48 |
+
with safe_open(single_file, framework="pt", device="cpu") as f:
|
| 49 |
+
return list(f.keys())
|
| 50 |
+
except Exception as e:
|
| 51 |
+
print(f"Error reading {single_file}: {e}")
|
| 52 |
+
sys.exit(1)
|
| 53 |
+
|
| 54 |
+
print(f"Error: No safetensors files found in {model_path}")
|
| 55 |
+
sys.exit(1)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def find_tensor_file(model_path: Path, tensor_name: str) -> Optional[str]:
|
| 59 |
+
weight_map = get_weight_map(model_path)
|
| 60 |
+
|
| 61 |
+
if weight_map is not None:
|
| 62 |
+
return weight_map.get(tensor_name)
|
| 63 |
+
|
| 64 |
+
single_file = model_path / MODEL_SAFETENSORS_FILE
|
| 65 |
+
if single_file.exists():
|
| 66 |
+
return single_file.name
|
| 67 |
+
|
| 68 |
+
return None
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def read_safetensors_header(file_path: Path) -> dict:
|
| 72 |
+
with open(file_path, 'rb') as f:
|
| 73 |
+
header_size = struct.unpack('<Q', f.read(8))[0]
|
| 74 |
+
return json.loads(f.read(header_size))
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def get_tensor_size_bytes(tensor_meta: dict) -> int:
|
| 78 |
+
offsets = tensor_meta.get("data_offsets")
|
| 79 |
+
if offsets and len(offsets) == 2:
|
| 80 |
+
return offsets[1] - offsets[0]
|
| 81 |
+
n_elements = 1
|
| 82 |
+
for d in tensor_meta.get("shape", []):
|
| 83 |
+
n_elements *= d
|
| 84 |
+
return n_elements * DTYPE_SIZES.get(tensor_meta.get("dtype", "F32"), 4)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def format_size(size_bytes: int) -> str:
|
| 88 |
+
val = float(size_bytes)
|
| 89 |
+
for unit in SIZE_UNITS[:-1]:
|
| 90 |
+
if val < 1024.0:
|
| 91 |
+
return f"{val:.2f} {unit}"
|
| 92 |
+
val /= 1024.0
|
| 93 |
+
return f"{val:.2f} {SIZE_UNITS[-1]}"
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def get_all_tensor_metadata(model_path: Path) -> dict[str, dict]:
|
| 97 |
+
weight_map = get_weight_map(model_path)
|
| 98 |
+
|
| 99 |
+
if weight_map is not None:
|
| 100 |
+
file_to_tensors: dict[str, list[str]] = {}
|
| 101 |
+
for tensor_name, file_name in weight_map.items():
|
| 102 |
+
file_to_tensors.setdefault(file_name, []).append(tensor_name)
|
| 103 |
+
|
| 104 |
+
all_metadata: dict[str, dict] = {}
|
| 105 |
+
for file_name, tensor_names in file_to_tensors.items():
|
| 106 |
+
try:
|
| 107 |
+
header = read_safetensors_header(model_path / file_name)
|
| 108 |
+
for tensor_name in tensor_names:
|
| 109 |
+
if tensor_name in header:
|
| 110 |
+
all_metadata[tensor_name] = header[tensor_name]
|
| 111 |
+
except Exception as e:
|
| 112 |
+
print(f"Warning: Could not read header from {file_name}: {e}", file=sys.stderr)
|
| 113 |
+
return all_metadata
|
| 114 |
+
|
| 115 |
+
single_file = model_path / MODEL_SAFETENSORS_FILE
|
| 116 |
+
if single_file.exists():
|
| 117 |
+
try:
|
| 118 |
+
header = read_safetensors_header(single_file)
|
| 119 |
+
return {k: v for k, v in header.items() if k != "__metadata__"}
|
| 120 |
+
except Exception as e:
|
| 121 |
+
print(f"Error reading {single_file}: {e}")
|
| 122 |
+
sys.exit(1)
|
| 123 |
+
|
| 124 |
+
print(f"Error: No safetensors files found in {model_path}")
|
| 125 |
+
sys.exit(1)
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def normalize_tensor_name(tensor_name: str) -> str:
|
| 129 |
+
normalized = re.sub(r'\.\d+\.', '.#.', tensor_name)
|
| 130 |
+
normalized = re.sub(r'\.\d+$', '.#', normalized)
|
| 131 |
+
return normalized
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def list_all_tensors(
|
| 135 |
+
model_path: Path,
|
| 136 |
+
short: bool = False,
|
| 137 |
+
show_sizes: bool = False,
|
| 138 |
+
):
|
| 139 |
+
tensor_names = get_all_tensor_names(model_path)
|
| 140 |
+
|
| 141 |
+
metadata: Optional[dict[str, dict]] = None
|
| 142 |
+
if show_sizes:
|
| 143 |
+
metadata = get_all_tensor_metadata(model_path)
|
| 144 |
+
|
| 145 |
+
total_bytes = 0
|
| 146 |
+
|
| 147 |
+
if short:
|
| 148 |
+
seen: dict[str, str] = {}
|
| 149 |
+
for tensor_name in sorted(tensor_names):
|
| 150 |
+
normalized = normalize_tensor_name(tensor_name)
|
| 151 |
+
if normalized not in seen:
|
| 152 |
+
seen[normalized] = tensor_name
|
| 153 |
+
display_pairs = list(sorted(seen.items()))
|
| 154 |
+
name_width = max((len(n) for n, _ in display_pairs), default=0)
|
| 155 |
+
for normalized, first_name in display_pairs:
|
| 156 |
+
if metadata and first_name in metadata:
|
| 157 |
+
m = metadata[first_name]
|
| 158 |
+
size = get_tensor_size_bytes(m)
|
| 159 |
+
total_bytes += size
|
| 160 |
+
print(f"{normalized:{name_width}} {m.get('dtype', '?'):6s} {str(m.get('shape', '')):30s} {format_size(size)}")
|
| 161 |
+
else:
|
| 162 |
+
print(normalized)
|
| 163 |
+
else:
|
| 164 |
+
name_width = max((len(n) for n in tensor_names), default=0)
|
| 165 |
+
for tensor_name in sorted(tensor_names):
|
| 166 |
+
if metadata and tensor_name in metadata:
|
| 167 |
+
m = metadata[tensor_name]
|
| 168 |
+
size = get_tensor_size_bytes(m)
|
| 169 |
+
total_bytes += size
|
| 170 |
+
print(f"{tensor_name:{name_width}} {m.get('dtype', '?'):6s} {str(m.get('shape', '')):30s} {format_size(size)}")
|
| 171 |
+
else:
|
| 172 |
+
print(tensor_name)
|
| 173 |
+
|
| 174 |
+
if show_sizes:
|
| 175 |
+
print(f"\nTotal: {format_size(total_bytes)}")
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def print_tensor_info(model_path: Path, tensor_name: str, num_values: Optional[int] = None):
|
| 179 |
+
tensor_file = find_tensor_file(model_path, tensor_name)
|
| 180 |
+
|
| 181 |
+
if tensor_file is None:
|
| 182 |
+
print(f"Error: Could not find tensor '{tensor_name}' in model index")
|
| 183 |
+
print(f"Model path: {model_path}")
|
| 184 |
+
sys.exit(1)
|
| 185 |
+
|
| 186 |
+
file_path = model_path / tensor_file
|
| 187 |
+
|
| 188 |
+
try:
|
| 189 |
+
header = read_safetensors_header(file_path)
|
| 190 |
+
tensor_meta = header.get(tensor_name, {})
|
| 191 |
+
dtype_str = tensor_meta.get("dtype")
|
| 192 |
+
|
| 193 |
+
with safe_open(file_path, framework="pt", device="cpu") as f:
|
| 194 |
+
if tensor_name in f.keys():
|
| 195 |
+
tensor_slice = f.get_slice(tensor_name)
|
| 196 |
+
shape = tensor_slice.get_shape()
|
| 197 |
+
print(f"Tensor: {tensor_name}")
|
| 198 |
+
print(f"File: {tensor_file}")
|
| 199 |
+
print(f"Shape: {shape}")
|
| 200 |
+
if dtype_str:
|
| 201 |
+
print(f"Dtype: {dtype_str}")
|
| 202 |
+
if tensor_meta:
|
| 203 |
+
print(f"Size: {format_size(get_tensor_size_bytes(tensor_meta))}")
|
| 204 |
+
if num_values is not None:
|
| 205 |
+
tensor = f.get_tensor(tensor_name)
|
| 206 |
+
if not dtype_str:
|
| 207 |
+
print(f"Dtype: {tensor.dtype}")
|
| 208 |
+
flat = tensor.flatten()
|
| 209 |
+
n = min(num_values, flat.numel())
|
| 210 |
+
print(f"Values: {flat[:n].tolist()}")
|
| 211 |
+
else:
|
| 212 |
+
print(f"Error: Tensor '{tensor_name}' not found in {tensor_file}")
|
| 213 |
+
sys.exit(1)
|
| 214 |
+
|
| 215 |
+
except FileNotFoundError:
|
| 216 |
+
print(f"Error: The file '{file_path}' was not found.")
|
| 217 |
+
sys.exit(1)
|
| 218 |
+
except Exception as e:
|
| 219 |
+
print(f"An error occurred: {e}")
|
| 220 |
+
sys.exit(1)
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def main():
|
| 224 |
+
parser = argparse.ArgumentParser(
|
| 225 |
+
description="Print tensor information from a safetensors model"
|
| 226 |
+
)
|
| 227 |
+
parser.add_argument(
|
| 228 |
+
"tensor_name",
|
| 229 |
+
nargs="?",
|
| 230 |
+
help="Name of the tensor to inspect"
|
| 231 |
+
)
|
| 232 |
+
parser.add_argument(
|
| 233 |
+
"-m", "--model-path",
|
| 234 |
+
type=Path,
|
| 235 |
+
help="Path to the model directory (default: MODEL_PATH environment variable)"
|
| 236 |
+
)
|
| 237 |
+
parser.add_argument(
|
| 238 |
+
"-l", "--list-all-short",
|
| 239 |
+
action="store_true",
|
| 240 |
+
help="List unique tensor patterns (layer numbers replaced with #)"
|
| 241 |
+
)
|
| 242 |
+
parser.add_argument(
|
| 243 |
+
"-la", "--list-all",
|
| 244 |
+
action="store_true",
|
| 245 |
+
help="List all tensor names with actual layer numbers"
|
| 246 |
+
)
|
| 247 |
+
parser.add_argument(
|
| 248 |
+
"-n", "--num-values",
|
| 249 |
+
nargs="?",
|
| 250 |
+
const=10,
|
| 251 |
+
default=None,
|
| 252 |
+
type=int,
|
| 253 |
+
metavar="N",
|
| 254 |
+
help="Print the first N values of the tensor flattened (default: 10 if flag is given without a number)"
|
| 255 |
+
)
|
| 256 |
+
parser.add_argument(
|
| 257 |
+
"-s", "--sizes",
|
| 258 |
+
action="store_true",
|
| 259 |
+
help="Show dtype, shape, and size for each tensor when listing"
|
| 260 |
+
)
|
| 261 |
+
|
| 262 |
+
args = parser.parse_args()
|
| 263 |
+
|
| 264 |
+
model_path = args.model_path
|
| 265 |
+
if model_path is None:
|
| 266 |
+
model_path_str = os.environ.get("MODEL_PATH")
|
| 267 |
+
if model_path_str is None:
|
| 268 |
+
print("Error: --model-path not provided and MODEL_PATH environment variable not set")
|
| 269 |
+
sys.exit(1)
|
| 270 |
+
model_path = Path(model_path_str)
|
| 271 |
+
|
| 272 |
+
if not model_path.exists():
|
| 273 |
+
print(f"Error: Model path does not exist: {model_path}")
|
| 274 |
+
sys.exit(1)
|
| 275 |
+
|
| 276 |
+
if not model_path.is_dir():
|
| 277 |
+
print(f"Error: Model path is not a directory: {model_path}")
|
| 278 |
+
sys.exit(1)
|
| 279 |
+
|
| 280 |
+
if args.list_all_short or args.list_all:
|
| 281 |
+
list_all_tensors(model_path, short=args.list_all_short, show_sizes=args.sizes)
|
| 282 |
+
else:
|
| 283 |
+
if args.tensor_name is None:
|
| 284 |
+
print("Error: tensor_name is required when not using --list-all-short or --list-all")
|
| 285 |
+
sys.exit(1)
|
| 286 |
+
print_tensor_info(model_path, args.tensor_name, args.num_values)
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
if __name__ == "__main__":
|
| 290 |
+
main()
|
backend/llama.cpp/examples/model-conversion/scripts/utils/perplexity-gen.sh
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -e
|
| 4 |
+
|
| 5 |
+
CONVERTED_MODEL="${1:-"$CONVERTED_MODEL"}"
|
| 6 |
+
BUILD_DIR="${2:-"$BUILD_DIR"}"
|
| 7 |
+
|
| 8 |
+
# Final check if we have a model path
|
| 9 |
+
if [ -z "$CONVERTED_MODEL" ]; then
|
| 10 |
+
echo "Error: Model path must be provided either as:" >&2
|
| 11 |
+
echo " 1. Command line argument" >&2
|
| 12 |
+
echo " 2. CONVERTED_MODEL environment variable" >&2
|
| 13 |
+
exit 1
|
| 14 |
+
fi
|
| 15 |
+
|
| 16 |
+
# Check if data/wikitext-2-raw directory exists
|
| 17 |
+
if [ ! -d "ppl/wikitext-2-raw" ]; then
|
| 18 |
+
echo "ppl/wikitext-2-raw directory does not exist. Downloading..." >&2
|
| 19 |
+
mkdir -p ppl
|
| 20 |
+
pushd ppl
|
| 21 |
+
./../../../scripts/get-wikitext-2.sh
|
| 22 |
+
popd
|
| 23 |
+
fi
|
| 24 |
+
|
| 25 |
+
mkdir -p ppl
|
| 26 |
+
OUTPUTFILE="ppl/$(basename $CONVERTED_MODEL).kld"
|
| 27 |
+
echo "Model: $CONVERTED_MODEL"
|
| 28 |
+
|
| 29 |
+
if [ -z "$BUILD_DIR" ]; then
|
| 30 |
+
BUILD_DIR="../../build"
|
| 31 |
+
fi
|
| 32 |
+
|
| 33 |
+
cmake --build $BUILD_DIR --target llama-perplexity -j8
|
| 34 |
+
|
| 35 |
+
${BUILD_DIR}/bin/llama-perplexity -m $CONVERTED_MODEL \
|
| 36 |
+
-f ppl/wikitext-2-raw/wiki.test.raw \
|
| 37 |
+
--kl-divergence-base $OUTPUTFILE
|
| 38 |
+
|
| 39 |
+
echo "Generated logits in $OUTPUTFILE"
|
| 40 |
+
|
backend/llama.cpp/examples/model-conversion/scripts/utils/perplexity-run-simple.sh
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -e
|
| 4 |
+
|
| 5 |
+
QUANTIZED_MODEL="${1:-"$QUANTIZED_MODEL"}"
|
| 6 |
+
BUILD_DIR="${2:-"$BUILD_DIR"}"
|
| 7 |
+
|
| 8 |
+
if [ -z "$QUANTIZED_MODEL" ]; then
|
| 9 |
+
echo "Error: Model path must be provided either as:" >&2
|
| 10 |
+
echo " 1. Command line argument" >&2
|
| 11 |
+
echo " 2. QUANTIZED_MODEL environment variable" >&2
|
| 12 |
+
exit 1
|
| 13 |
+
fi
|
| 14 |
+
|
| 15 |
+
# Check if data/wikitext-2-raw directory exists
|
| 16 |
+
if [ ! -d "ppl/wikitext-2-raw" ]; then
|
| 17 |
+
echo "ppl/wikitext-2-raw directory does not exist. Downloading..." >&2
|
| 18 |
+
mkdir -p ppl
|
| 19 |
+
pushd ppl
|
| 20 |
+
./../../../scripts/get-wikitext-2.sh
|
| 21 |
+
popd
|
| 22 |
+
fi
|
| 23 |
+
|
| 24 |
+
if [ -z "$BUILD_DIR" ]; then
|
| 25 |
+
BUILD_DIR="../../build"
|
| 26 |
+
fi
|
| 27 |
+
|
| 28 |
+
cmake --build $BUILD_DIR --target llama-perplexity -j8
|
| 29 |
+
|
| 30 |
+
${BUILD_DIR}/bin/llama-perplexity -m $QUANTIZED_MODEL -f ppl/wikitext-2-raw/wiki.test.raw
|
| 31 |
+
|
| 32 |
+
|
backend/llama.cpp/examples/model-conversion/scripts/utils/perplexity-run.sh
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -e
|
| 4 |
+
|
| 5 |
+
QUANTIZED_MODEL="${1:-"$QUANTIZED_MODEL"}"
|
| 6 |
+
LOGITS_FILE="${2:-"$LOGITS_FILE"}"
|
| 7 |
+
BUILD_DIR="${3:-"$BUILD_DIR"}"
|
| 8 |
+
|
| 9 |
+
if [ -z "$QUANTIZED_MODEL" ]; then
|
| 10 |
+
echo "Error: Model path must be provided either as:" >&2
|
| 11 |
+
echo " 1. Command line argument" >&2
|
| 12 |
+
echo " 2. QUANTIZED_MODEL environment variable" >&2
|
| 13 |
+
exit 1
|
| 14 |
+
fi
|
| 15 |
+
|
| 16 |
+
if [ ! -f ${LOGITS_FILE} ]; then
|
| 17 |
+
echo "Error: logits file '${LOGITS_FILE} was not found"
|
| 18 |
+
echo "Did you run the perplexity-gen.sh script?"
|
| 19 |
+
exit 1
|
| 20 |
+
fi
|
| 21 |
+
|
| 22 |
+
if [ -z "$BUILD_DIR" ]; then
|
| 23 |
+
BUILD_DIR="../../build"
|
| 24 |
+
fi
|
| 25 |
+
|
| 26 |
+
echo "Model: $QUANTIZED_MODEL"
|
| 27 |
+
echo "Data file: $LOGITS_FILE"
|
| 28 |
+
|
| 29 |
+
cmake --build $BUILD_DIR --target llama-perplexity -j8
|
| 30 |
+
|
| 31 |
+
${BUILD_DIR}/bin/llama-perplexity -m $QUANTIZED_MODEL \
|
| 32 |
+
--kl-divergence-base $LOGITS_FILE \
|
| 33 |
+
--kl-divergence
|
backend/llama.cpp/examples/model-conversion/scripts/utils/quantize.sh
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -e
|
| 4 |
+
|
| 5 |
+
CONVERTED_MODEL="${1:-"$CONVERTED_MODEL"}"
|
| 6 |
+
QUANTIZED_TYPE="${2:-"$QUANTIZED_TYPE"}"
|
| 7 |
+
TOKEN_EMBD_TYPE="${3:-"${TOKEN_EMBD_TYPE}"}"
|
| 8 |
+
OUTPUT_TYPE="${4:-"${OUTPUT_TYPE}"}"
|
| 9 |
+
BUILD_DIR="${5:-"$BUILD_DIR"}"
|
| 10 |
+
QUANTIZED_MODEL=$CONVERTED_MODEL
|
| 11 |
+
|
| 12 |
+
# Final check if we have a model path
|
| 13 |
+
if [ -z "$CONVERTED_MODEL" ]; then
|
| 14 |
+
echo "Error: Model path must be provided either as:" >&2
|
| 15 |
+
echo " 1. Command line argument" >&2
|
| 16 |
+
echo " 2. CONVERTED_MODEL environment variable" >&2
|
| 17 |
+
exit 1
|
| 18 |
+
fi
|
| 19 |
+
|
| 20 |
+
if [ -z "$QUANTIZED_TYPE" ]; then
|
| 21 |
+
echo "Error: QUANTIZED_TYPE is required" >&2
|
| 22 |
+
exit 1
|
| 23 |
+
fi
|
| 24 |
+
|
| 25 |
+
echo $CONVERTED_MODEL
|
| 26 |
+
|
| 27 |
+
# Process the quantized model filename
|
| 28 |
+
if [[ "$QUANTIZED_MODEL" == *.gguf ]]; then
|
| 29 |
+
# Remove .gguf suffix, add quantized type, then add .gguf back
|
| 30 |
+
BASE_NAME="${QUANTIZED_MODEL%.gguf}"
|
| 31 |
+
QUANTIZED_MODEL="${BASE_NAME}-${QUANTIZED_TYPE}.gguf"
|
| 32 |
+
else
|
| 33 |
+
echo "Error: QUANTIZED_MODEL must end with .gguf extension" >&2
|
| 34 |
+
exit 1
|
| 35 |
+
fi
|
| 36 |
+
|
| 37 |
+
if [ -z "$BUILD_DIR" ]; then
|
| 38 |
+
BUILD_DIR="../../build"
|
| 39 |
+
fi
|
| 40 |
+
|
| 41 |
+
cmake --build $BUILD_DIR --target llama-quantize -j8
|
| 42 |
+
|
| 43 |
+
echo $TOKEN_EMBD_TYPE
|
| 44 |
+
echo $OUTPUT_TYPE
|
| 45 |
+
|
| 46 |
+
CMD_ARGS=("${BUILD_DIR}/bin/llama-quantize")
|
| 47 |
+
[[ -n "$TOKEN_EMBD_TYPE" ]] && CMD_ARGS+=("--token-embedding-type" "$TOKEN_EMBD_TYPE")
|
| 48 |
+
[[ -n "$OUTPUT_TYPE" ]] && CMD_ARGS+=("--output-tensor-type" "$OUTPUT_TYPE")
|
| 49 |
+
CMD_ARGS+=("$CONVERTED_MODEL" "$QUANTIZED_MODEL" "$QUANTIZED_TYPE")
|
| 50 |
+
|
| 51 |
+
"${CMD_ARGS[@]}"
|
| 52 |
+
|
| 53 |
+
echo "Quantized model saved to: $QUANTIZED_MODEL"
|
backend/llama.cpp/examples/model-conversion/scripts/utils/run-embedding-server.sh
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -e
|
| 4 |
+
#
|
| 5 |
+
# First try command line argument, then environment variable, then file
|
| 6 |
+
CONVERTED_MODEL="${1:-"$CONVERTED_MODEL"}"
|
| 7 |
+
BUILD_DIR="${2:-"$BUILD_DIR"}"
|
| 8 |
+
|
| 9 |
+
# Final check if we have a model path
|
| 10 |
+
if [ -z "$CONVERTED_MODEL" ]; then
|
| 11 |
+
echo "Error: Model path must be provided either as:" >&2
|
| 12 |
+
echo " 1. Command line argument" >&2
|
| 13 |
+
echo " 2. CONVERTED_MODEL environment variable" >&2
|
| 14 |
+
exit 1
|
| 15 |
+
fi
|
| 16 |
+
|
| 17 |
+
if [ -z "$BUILD_DIR" ]; then
|
| 18 |
+
BUILD_DIR="../../build"
|
| 19 |
+
fi
|
| 20 |
+
|
| 21 |
+
echo $CONVERTED_MODEL
|
| 22 |
+
|
| 23 |
+
cmake --build $BUILD_DIR --target llama-server
|
| 24 |
+
|
| 25 |
+
${BUILD_DIR}/bin/llama-server -m $CONVERTED_MODEL \
|
| 26 |
+
--embedding \
|
| 27 |
+
--pooling none
|
backend/llama.cpp/examples/model-conversion/scripts/utils/semantic_check.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import argparse
|
| 5 |
+
import os
|
| 6 |
+
import importlib
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
from transformers import AutoTokenizer, AutoConfig, AutoModelForCausalLM, AutoModel
|
| 10 |
+
from common import compare_tokens, exit_with_warning # type: ignore[import-not-found, ty:unresolved-import]
|
| 11 |
+
|
| 12 |
+
unreleased_model_name = os.getenv('UNRELEASED_MODEL_NAME')
|
| 13 |
+
|
| 14 |
+
def cosine_similarity(a, b=None):
|
| 15 |
+
a = np.asarray(a)
|
| 16 |
+
if b is None:
|
| 17 |
+
b = a
|
| 18 |
+
else:
|
| 19 |
+
b = np.asarray(b)
|
| 20 |
+
|
| 21 |
+
if a.ndim == 1:
|
| 22 |
+
a = a.reshape(1, -1)
|
| 23 |
+
if b.ndim == 1:
|
| 24 |
+
b = b.reshape(1, -1)
|
| 25 |
+
|
| 26 |
+
a_norms = np.linalg.norm(a, axis=1, keepdims=True)
|
| 27 |
+
b_norms = np.linalg.norm(b, axis=1, keepdims=True)
|
| 28 |
+
|
| 29 |
+
a_norms = np.where(a_norms == 0, 1e-8, a_norms)
|
| 30 |
+
b_norms = np.where(b_norms == 0, 1e-8, b_norms)
|
| 31 |
+
|
| 32 |
+
a_normalized = a / a_norms
|
| 33 |
+
b_normalized = b / b_norms
|
| 34 |
+
|
| 35 |
+
# Compute cosine similarity
|
| 36 |
+
return np.dot(a_normalized, b_normalized.T)
|
| 37 |
+
|
| 38 |
+
def load_embeddings_from_file(filename, n_tokens, n_embd):
|
| 39 |
+
embeddings = np.fromfile(filename, dtype=np.float32)
|
| 40 |
+
# Check if this is pooled (single embedding) or per-token embeddings
|
| 41 |
+
if len(embeddings) == n_embd:
|
| 42 |
+
return embeddings.reshape(1, n_embd)
|
| 43 |
+
else:
|
| 44 |
+
return embeddings.reshape(n_tokens, n_embd)
|
| 45 |
+
|
| 46 |
+
def test_single_prompt_similarity(python_emb, cpp_emb, tokens, prompt):
|
| 47 |
+
np.set_printoptions(suppress=True, precision=6)
|
| 48 |
+
print("pytorch embeddings:");
|
| 49 |
+
print(python_emb)
|
| 50 |
+
print("llama.cpp embeddings:");
|
| 51 |
+
print(cpp_emb)
|
| 52 |
+
print(f"\n=== Prompt: '{prompt}' ===")
|
| 53 |
+
print(f"Tokens: {tokens}")
|
| 54 |
+
print(f"Embeddings shape: Python {python_emb.shape}, llama.cpp {cpp_emb.shape}")
|
| 55 |
+
|
| 56 |
+
n_tokens = len(tokens)
|
| 57 |
+
is_pooled = python_emb.shape[0] == 1
|
| 58 |
+
|
| 59 |
+
if is_pooled:
|
| 60 |
+
print(f"\n[Pooled Embeddings Mode - comparing single sentence embeddings]")
|
| 61 |
+
|
| 62 |
+
# 1. Direct embedding comparison for pooled embeddings
|
| 63 |
+
print(f"\n1. Raw Embedding Magnitude Comparison:")
|
| 64 |
+
py_mag = np.linalg.norm(python_emb[0])
|
| 65 |
+
cpp_mag = np.linalg.norm(cpp_emb[0])
|
| 66 |
+
ratio = py_mag / cpp_mag if cpp_mag > 0 else float('inf')
|
| 67 |
+
print(f" Pooled embedding: Python={py_mag:.3f}, llama.cpp={cpp_mag:.3f}, ratio={ratio:.3f}")
|
| 68 |
+
|
| 69 |
+
# 2. Cross-model similarity for pooled embeddings
|
| 70 |
+
print(f"\n2. Cross-Model Pooled Embedding Similarity:")
|
| 71 |
+
sim = cosine_similarity([python_emb[0]], [cpp_emb[0]])[0][0]
|
| 72 |
+
print(f" Cosine similarity: {sim:.6f}")
|
| 73 |
+
|
| 74 |
+
return {
|
| 75 |
+
'cross_model_similarities': [sim],
|
| 76 |
+
'similarity_matrix_diff': np.array([[0.0]]),
|
| 77 |
+
'max_diff': 0.0,
|
| 78 |
+
'mean_diff': 0.0,
|
| 79 |
+
'rms_diff': 0.0
|
| 80 |
+
}
|
| 81 |
+
else:
|
| 82 |
+
# Original per-token comparison logic
|
| 83 |
+
# 1. Direct embedding comparison
|
| 84 |
+
print(f"\n1. Raw Embedding Magnitude Comparison:")
|
| 85 |
+
# Check if the distance of each token embedding from the origin and compare
|
| 86 |
+
# if the vectors are on the same "sphere". This does not tell us about
|
| 87 |
+
# direction (meaning of the token embedding), just magnitude.
|
| 88 |
+
for i in range(n_tokens):
|
| 89 |
+
py_mag = np.linalg.norm(python_emb[i]) # calculate standard euclidean norm for Python embeddings
|
| 90 |
+
cpp_mag = np.linalg.norm(cpp_emb[i]) # calculate standard euclidean norm for llama.cpp embeddings
|
| 91 |
+
ratio = py_mag / cpp_mag if cpp_mag > 0 else float('inf')
|
| 92 |
+
print(f" Token {i} ({tokens[i]}): Python={py_mag:.3f}, llama.cpp={cpp_mag:.3f}, ratio={ratio:.3f}")
|
| 93 |
+
|
| 94 |
+
# 2. Cosine similarity between tokens within each model
|
| 95 |
+
# Here we check the direction of token embeddings to see if the have the
|
| 96 |
+
# same meaning (similarity). This is done by calculating cosine similarity
|
| 97 |
+
# of a pair of token embeddings within each model.
|
| 98 |
+
print(f"\n2. Within-Model Token Similarities:")
|
| 99 |
+
print(" Python model:")
|
| 100 |
+
for i in range(n_tokens):
|
| 101 |
+
for j in range(i+1, n_tokens):
|
| 102 |
+
sim = cosine_similarity([python_emb[i]], [python_emb[j]])[0][0]
|
| 103 |
+
print(f" {tokens[i]} ↔ {tokens[j]}: {sim:.4f}")
|
| 104 |
+
|
| 105 |
+
print(" llama.cpp model:")
|
| 106 |
+
for i in range(n_tokens):
|
| 107 |
+
for j in range(i+1, n_tokens):
|
| 108 |
+
sim = cosine_similarity([cpp_emb[i]], [cpp_emb[j]])[0][0]
|
| 109 |
+
print(f" {tokens[i]} ↔ {tokens[j]}: {sim:.4f}")
|
| 110 |
+
|
| 111 |
+
# 3. Cross-model similarity (same token position)
|
| 112 |
+
print(f"\n3. Cross-Model Same-Token Similarities:")
|
| 113 |
+
for i in range(n_tokens):
|
| 114 |
+
sim = cosine_similarity([python_emb[i]], [cpp_emb[i]])[0][0]
|
| 115 |
+
print(f" Token {i} ({tokens[i]}): {sim:.4f}")
|
| 116 |
+
|
| 117 |
+
# 4. Similarity matrix comparison
|
| 118 |
+
print(f"\n4. Similarity Matrix Differences:")
|
| 119 |
+
py_sim_matrix = cosine_similarity(python_emb)
|
| 120 |
+
cpp_sim_matrix = cosine_similarity(cpp_emb)
|
| 121 |
+
diff_matrix = np.abs(py_sim_matrix - cpp_sim_matrix)
|
| 122 |
+
|
| 123 |
+
print(f" Max difference: {np.max(diff_matrix):.4f}")
|
| 124 |
+
print(f" Mean difference: {np.mean(diff_matrix):.4f}")
|
| 125 |
+
print(f" RMS difference: {np.sqrt(np.mean(diff_matrix**2)):.4f}")
|
| 126 |
+
|
| 127 |
+
return {
|
| 128 |
+
'cross_model_similarities': [cosine_similarity([python_emb[i]], [cpp_emb[i]])[0][0] for i in range(n_tokens)],
|
| 129 |
+
'similarity_matrix_diff': diff_matrix,
|
| 130 |
+
'max_diff': np.max(diff_matrix),
|
| 131 |
+
'mean_diff': np.mean(diff_matrix),
|
| 132 |
+
'rms_diff': np.sqrt(np.mean(diff_matrix**2))
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
def read_prompt_from_file(file_path):
|
| 136 |
+
try:
|
| 137 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
| 138 |
+
return f.read().strip()
|
| 139 |
+
except FileNotFoundError:
|
| 140 |
+
print(f"Error: Prompts file '{file_path}' not found")
|
| 141 |
+
exit(1)
|
| 142 |
+
except Exception as e:
|
| 143 |
+
print(f"Error reading prompts file: {e}")
|
| 144 |
+
exit(1)
|
| 145 |
+
|
| 146 |
+
def main():
|
| 147 |
+
parser = argparse.ArgumentParser(description='Test semantic similarity between Python and llama.cpp embeddings')
|
| 148 |
+
parser.add_argument('--model-path', '-m', required=True, help='Path to the original Python model')
|
| 149 |
+
parser.add_argument('--python-embeddings', '-pe', help='Path to pytorch embeddings "logits" binary file')
|
| 150 |
+
parser.add_argument('--cpp-embeddings', '-ce', help='Path to llama.cpp embeddings "logits" binary file')
|
| 151 |
+
parser.add_argument('--causal', '-c', default=False, help='if the model is causal (default: false)', action='store_true')
|
| 152 |
+
parser.add_argument('--prompt', '-p', default='Hello world today', help='Test prompt')
|
| 153 |
+
parser.add_argument('--prompts-file', '-pf', help='Path to file containing prompts')
|
| 154 |
+
|
| 155 |
+
args = parser.parse_args()
|
| 156 |
+
|
| 157 |
+
if args.prompts_file:
|
| 158 |
+
prompt = read_prompt_from_file(args.prompts_file)
|
| 159 |
+
else:
|
| 160 |
+
prompt = args.prompt
|
| 161 |
+
|
| 162 |
+
python_emb_path = Path(args.python_embeddings)
|
| 163 |
+
cpp_emb_path = Path(args.cpp_embeddings)
|
| 164 |
+
|
| 165 |
+
# Extract base names (e.g., "pytorch-model-name-embeddings.bin" -> "pytorch-model-name")
|
| 166 |
+
python_model_name = python_emb_path.stem.replace("-embeddings", "")
|
| 167 |
+
cpp_model_name = cpp_emb_path.stem.replace("-embeddings", "")
|
| 168 |
+
|
| 169 |
+
print("Semantic Similarity Test Between Python and llama.cpp Embedding Models")
|
| 170 |
+
print("=" * 70)
|
| 171 |
+
|
| 172 |
+
# First verify tokens match before comparing embeddings
|
| 173 |
+
print("\n🔍 Token Comparison Check")
|
| 174 |
+
print("=" * 70)
|
| 175 |
+
data_dir = python_emb_path.parent
|
| 176 |
+
if not compare_tokens(python_model_name, cpp_model_name, type_suffix="-embeddings", output_dir=str(data_dir)):
|
| 177 |
+
exit_with_warning("\n❌ Token mismatch detected", args.model_path)
|
| 178 |
+
print()
|
| 179 |
+
|
| 180 |
+
# Single prompt detailed comparison
|
| 181 |
+
print(f"\nTesting with prompt: '{prompt}'")
|
| 182 |
+
|
| 183 |
+
# Load the python model to get configuration information and also to load the tokenizer.
|
| 184 |
+
print("Loading model and tokenizer using AutoTokenizer:", args.model_path)
|
| 185 |
+
tokenizer = AutoTokenizer.from_pretrained(args.model_path)
|
| 186 |
+
config = AutoConfig.from_pretrained(args.model_path, trust_remote_code=True)
|
| 187 |
+
|
| 188 |
+
if unreleased_model_name:
|
| 189 |
+
model_name_lower = unreleased_model_name.lower()
|
| 190 |
+
unreleased_module_path = f"transformers.models.{model_name_lower}.modular_{model_name_lower}"
|
| 191 |
+
if args.causal:
|
| 192 |
+
class_name = f"{unreleased_model_name}ForCausalLM"
|
| 193 |
+
else:
|
| 194 |
+
class_name = f"{unreleased_model_name}Model"
|
| 195 |
+
print(f"Model class: {class_name}")
|
| 196 |
+
print(f"Importing unreleased model module: {unreleased_module_path}")
|
| 197 |
+
|
| 198 |
+
try:
|
| 199 |
+
model_class = getattr(importlib.import_module(unreleased_module_path), class_name)
|
| 200 |
+
model = model_class.from_pretrained(args.model_path)
|
| 201 |
+
except (ImportError, AttributeError) as e:
|
| 202 |
+
print(f"Failed to import or load model: {e}")
|
| 203 |
+
exit(1)
|
| 204 |
+
else:
|
| 205 |
+
if args.causal:
|
| 206 |
+
model = AutoModelForCausalLM.from_pretrained(args.model_path, trust_remote_code=True)
|
| 207 |
+
else:
|
| 208 |
+
model = AutoModel.from_pretrained(args.model_path, trust_remote_code=True)
|
| 209 |
+
|
| 210 |
+
encoded = tokenizer(prompt, return_tensors="pt") # ty: ignore[call-non-callable]
|
| 211 |
+
tokens = tokenizer.convert_ids_to_tokens(encoded['input_ids'][0]) # ty: ignore[unresolved-attribute]
|
| 212 |
+
n_tokens = len(tokens)
|
| 213 |
+
print(f"n_tokens: {n_tokens}");
|
| 214 |
+
print(f"hidden_size: {model.config.hidden_size}")
|
| 215 |
+
|
| 216 |
+
# Load binary embeddings from data directory.
|
| 217 |
+
llamacpp_embeddings = load_embeddings_from_file(args.cpp_embeddings, n_tokens, model.config.hidden_size)
|
| 218 |
+
python_embeddings = load_embeddings_from_file(args.python_embeddings, n_tokens, model.config.hidden_size)
|
| 219 |
+
|
| 220 |
+
# Run comparison
|
| 221 |
+
results = test_single_prompt_similarity(python_embeddings, llamacpp_embeddings, tokens, prompt)
|
| 222 |
+
|
| 223 |
+
# Summary
|
| 224 |
+
print(f"\n=== SUMMARY ===")
|
| 225 |
+
avg_cross_sim = np.mean(results['cross_model_similarities'])
|
| 226 |
+
print(f"Average cross-model similarity: {avg_cross_sim:.4f}")
|
| 227 |
+
print(f"Similarity matrix RMS difference: {results['rms_diff']:.4f}")
|
| 228 |
+
|
| 229 |
+
# Quality assessment
|
| 230 |
+
if avg_cross_sim > 0.95:
|
| 231 |
+
print("✅ EXCELLENT: Models are highly similar")
|
| 232 |
+
elif avg_cross_sim > 0.90:
|
| 233 |
+
print("✅ VERY GOOD: Models are very similar")
|
| 234 |
+
elif avg_cross_sim > 0.80:
|
| 235 |
+
print("⚠️ GOOD: Models are reasonably similar")
|
| 236 |
+
elif avg_cross_sim > 0.70:
|
| 237 |
+
print("⚠️ FAIR: Models have some differences")
|
| 238 |
+
else:
|
| 239 |
+
exit_with_warning("❌ POOR: Models are significantly different", args.model_path)
|
| 240 |
+
|
| 241 |
+
if __name__ == "__main__":
|
| 242 |
+
main()
|
backend/llama.cpp/examples/parallel/CMakeLists.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
set(TARGET llama-parallel)
|
| 2 |
+
add_executable(${TARGET} parallel.cpp)
|
| 3 |
+
install(TARGETS ${TARGET} RUNTIME)
|
| 4 |
+
target_link_libraries(${TARGET} PRIVATE llama-common llama ${CMAKE_THREAD_LIBS_INIT})
|
| 5 |
+
target_compile_features(${TARGET} PRIVATE cxx_std_17)
|
backend/llama.cpp/examples/parallel/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# llama.cpp/example/parallel
|
| 2 |
+
|
| 3 |
+
Simplified simulation of serving incoming requests in parallel
|
| 4 |
+
|
| 5 |
+
## Example
|
| 6 |
+
|
| 7 |
+
Generate 128 client requests (`-ns 128`), simulating 8 concurrent clients (`-np 8`). The system prompt is shared (`-pps`), meaning that it is computed once at the start. The client requests consist of up to 10 junk questions (`--junk 10`) followed by the actual question.
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
llama-parallel -m model.gguf -np 8 -ns 128 --top-k 1 -pps --junk 10 -c 16384
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
> [!NOTE]
|
| 14 |
+
> It's recommended to use base models with this example. Instruction tuned models might not be able to properly follow the custom chat template specified here, so the results might not be as expected.
|
backend/llama.cpp/examples/parallel/parallel.cpp
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// A basic application simulating a server with multiple clients.
|
| 2 |
+
// The clients submit requests to the server and they are processed in parallel.
|
| 3 |
+
|
| 4 |
+
#include "arg.h"
|
| 5 |
+
#include "common.h"
|
| 6 |
+
#include "sampling.h"
|
| 7 |
+
#include "log.h"
|
| 8 |
+
#include "llama.h"
|
| 9 |
+
|
| 10 |
+
#include <algorithm>
|
| 11 |
+
#include <clocale>
|
| 12 |
+
#include <cmath>
|
| 13 |
+
#include <cstdio>
|
| 14 |
+
#include <string>
|
| 15 |
+
#include <vector>
|
| 16 |
+
#include <ctime>
|
| 17 |
+
|
| 18 |
+
// trim whitespace from the beginning and end of a string
|
| 19 |
+
static std::string trim(const std::string & str) {
|
| 20 |
+
size_t start = 0;
|
| 21 |
+
size_t end = str.size();
|
| 22 |
+
|
| 23 |
+
while (start < end && isspace(str[start])) {
|
| 24 |
+
start += 1;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
while (end > start && isspace(str[end - 1])) {
|
| 28 |
+
end -= 1;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
return str.substr(start, end - start);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
static std::string k_system =
|
| 35 |
+
R"(Transcript of a never ending dialog, where the User interacts with an Assistant.
|
| 36 |
+
The Assistant is helpful, kind, honest, good at writing, and never fails to answer the User's requests immediately and with precision.
|
| 37 |
+
|
| 38 |
+
User:
|
| 39 |
+
Recommend a nice restaurant in the area.
|
| 40 |
+
Assistant:
|
| 41 |
+
I recommend the restaurant "The Golden Duck". It is a 5 star restaurant with a great view of the city. The food is delicious and the service is excellent. The prices are reasonable and the portions are generous. The restaurant is located at 123 Main Street, New York, NY 10001. The phone number is (212) 555-1234. The hours are Monday through Friday from 11:00 am to 10:00 pm. The restaurant is closed on Saturdays and Sundays.
|
| 42 |
+
User:
|
| 43 |
+
Who is Richard Feynman?
|
| 44 |
+
Assistant:
|
| 45 |
+
Richard Feynman was an American physicist who is best known for his work in quantum mechanics and particle physics. He was awarded the Nobel Prize in Physics in 1965 for his contributions to the development of quantum electrodynamics. He was a popular lecturer and author, and he wrote several books, including "Surely You're Joking, Mr. Feynman!" and "What Do You Care What Other People Think?".
|
| 46 |
+
)";
|
| 47 |
+
|
| 48 |
+
static std::vector<std::string> k_questions = {
|
| 49 |
+
"What is the tallest mountain in the world?",
|
| 50 |
+
"Who was the first person to win two Nobel Prizes?",
|
| 51 |
+
"Which country invented paper?",
|
| 52 |
+
"What organ is primarily responsible for pumping blood throughout the body?",
|
| 53 |
+
"Which planet is known for its prominent ring system?",
|
| 54 |
+
"Who directed the movie 'Inception'?",
|
| 55 |
+
"What is the freezing point of water in Fahrenheit?",
|
| 56 |
+
"Which animal is known to have the longest lifespan?",
|
| 57 |
+
"What language has the most native speakers worldwide?",
|
| 58 |
+
"What is the capital city of Canada?",
|
| 59 |
+
"Who is credited with inventing the World Wide Web?",
|
| 60 |
+
"Which metal is liquid at room temperature?",
|
| 61 |
+
"What is the term for an animal that eats both plants and meat?",
|
| 62 |
+
"Who painted 'The Starry Night'?",
|
| 63 |
+
"What gas do humans exhale that plants use for photosynthesis?",
|
| 64 |
+
"What year did World War II end?",
|
| 65 |
+
"Which continent has the most countries?",
|
| 66 |
+
"Who wrote the novel 'Frankenstein'?",
|
| 67 |
+
"What does DNA stand for?",
|
| 68 |
+
"What is the main ingredient in traditional Japanese miso soup?"
|
| 69 |
+
};
|
| 70 |
+
|
| 71 |
+
static std::vector<std::string> k_answers = {
|
| 72 |
+
"The tallest mountain in the world is Mount Everest.",
|
| 73 |
+
"Marie Curie was the first person to win two Nobel Prizes.",
|
| 74 |
+
"Paper was invented in China.",
|
| 75 |
+
"The heart is the organ responsible for pumping blood.",
|
| 76 |
+
"Saturn is known for its prominent ring system.",
|
| 77 |
+
"Christopher Nolan directed the movie 'Inception'.",
|
| 78 |
+
"The freezing point of water in Fahrenheit is 32°F.",
|
| 79 |
+
"The bowhead whale is known to have the longest lifespan among mammals.",
|
| 80 |
+
"Mandarin Chinese has the most native speakers in the world.",
|
| 81 |
+
"The capital city of Canada is Ottawa.",
|
| 82 |
+
"Tim Berners-Lee is credited with inventing the World Wide Web.",
|
| 83 |
+
"Mercury is the metal that is liquid at room temperature.",
|
| 84 |
+
"An animal that eats both plants and meat is called an omnivore.",
|
| 85 |
+
"'The Starry Night' was painted by Vincent van Gogh.",
|
| 86 |
+
"Humans exhale carbon dioxide, which plants use in photosynthesis.",
|
| 87 |
+
"World War II ended in 1945.",
|
| 88 |
+
"Africa is the continent with the most countries.",
|
| 89 |
+
"The novel 'Frankenstein' was written by Mary Shelley.",
|
| 90 |
+
"DNA stands for Deoxyribonucleic Acid.",
|
| 91 |
+
"The main ingredient in traditional Japanese miso soup is fermented soybean paste."
|
| 92 |
+
};
|
| 93 |
+
|
| 94 |
+
static std::vector<std::string> k_prompts = {
|
| 95 |
+
"What is the meaning of life?",
|
| 96 |
+
"Tell me an interesting fact about llamas.",
|
| 97 |
+
"What is the best way to cook a steak?",
|
| 98 |
+
"Are you familiar with the Special Theory of Relativity and can you explain it to me?",
|
| 99 |
+
"Recommend some interesting books to read.",
|
| 100 |
+
"What is the best way to learn a new language?",
|
| 101 |
+
"How to get a job at Google?",
|
| 102 |
+
"If you could have any superpower, what would it be?",
|
| 103 |
+
"I want to learn how to play the piano. What would be the best way to do it?",
|
| 104 |
+
};
|
| 105 |
+
|
| 106 |
+
struct client {
|
| 107 |
+
~client() {
|
| 108 |
+
if (smpl) {
|
| 109 |
+
common_sampler_free(smpl);
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
int32_t id = 0;
|
| 114 |
+
|
| 115 |
+
llama_seq_id seq_id = -1;
|
| 116 |
+
|
| 117 |
+
llama_token sampled;
|
| 118 |
+
|
| 119 |
+
int64_t t_start_prompt;
|
| 120 |
+
int64_t t_start_gen;
|
| 121 |
+
|
| 122 |
+
int32_t n_past = 0;
|
| 123 |
+
int32_t n_prompt = 0;
|
| 124 |
+
int32_t n_decoded = 0;
|
| 125 |
+
int32_t i_batch = -1;
|
| 126 |
+
|
| 127 |
+
std::string input;
|
| 128 |
+
std::string prompt;
|
| 129 |
+
std::string response;
|
| 130 |
+
|
| 131 |
+
struct common_sampler * smpl = nullptr;
|
| 132 |
+
};
|
| 133 |
+
|
| 134 |
+
static void print_date_time() {
|
| 135 |
+
std::time_t current_time = std::time(nullptr);
|
| 136 |
+
std::tm* local_time = std::localtime(¤t_time);
|
| 137 |
+
char buffer[80];
|
| 138 |
+
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", local_time);
|
| 139 |
+
|
| 140 |
+
LOG_INF("\n");
|
| 141 |
+
LOG_INF("\033[35mrun parameters as of %s\033[0m\n", buffer);
|
| 142 |
+
LOG_INF("\n");
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
// Define a split string function to ...
|
| 146 |
+
static std::vector<std::string> split_string(const std::string& input, char delimiter) {
|
| 147 |
+
std::vector<std::string> tokens;
|
| 148 |
+
std::istringstream stream(input);
|
| 149 |
+
std::string token;
|
| 150 |
+
while (std::getline(stream, token, delimiter)) {
|
| 151 |
+
tokens.push_back(token);
|
| 152 |
+
}
|
| 153 |
+
return tokens;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
int main(int argc, char ** argv) {
|
| 157 |
+
std::setlocale(LC_NUMERIC, "C");
|
| 158 |
+
|
| 159 |
+
srand(1234);
|
| 160 |
+
|
| 161 |
+
common_params params;
|
| 162 |
+
|
| 163 |
+
params.n_predict = 128;
|
| 164 |
+
params.n_junk = 1;
|
| 165 |
+
|
| 166 |
+
common_init();
|
| 167 |
+
|
| 168 |
+
if (!common_params_parse(argc, argv, params, LLAMA_EXAMPLE_PARALLEL)) {
|
| 169 |
+
return 1;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
// number of simultaneous "clients" to simulate
|
| 173 |
+
const int32_t n_clients = params.n_parallel;
|
| 174 |
+
|
| 175 |
+
// dedicate one sequence to the system prompt
|
| 176 |
+
params.n_parallel += 1;
|
| 177 |
+
|
| 178 |
+
// requests to simulate
|
| 179 |
+
const int32_t n_seq = params.n_sequences;
|
| 180 |
+
|
| 181 |
+
// insert new requests as soon as the previous one is done
|
| 182 |
+
const bool cont_batching = params.cont_batching;
|
| 183 |
+
|
| 184 |
+
// is the system prompt shared in the cache
|
| 185 |
+
const bool is_sp_shared = params.is_pp_shared;
|
| 186 |
+
|
| 187 |
+
// extra text to insert in each client's prompt in order to make it larger
|
| 188 |
+
const int32_t n_junk = std::max(1, params.n_junk);
|
| 189 |
+
|
| 190 |
+
// signed seed, use negative values to indicate different seeds for the different clients
|
| 191 |
+
const int32_t & sseed = params.sampling.seed;
|
| 192 |
+
|
| 193 |
+
// init llama.cpp
|
| 194 |
+
llama_backend_init();
|
| 195 |
+
llama_numa_init(params.numa);
|
| 196 |
+
|
| 197 |
+
// load the target model
|
| 198 |
+
auto llama_init = common_init_from_params(params);
|
| 199 |
+
|
| 200 |
+
auto * model = llama_init->model();
|
| 201 |
+
auto * ctx = llama_init->context();
|
| 202 |
+
|
| 203 |
+
auto * mem = llama_get_memory(ctx);
|
| 204 |
+
|
| 205 |
+
const llama_vocab * vocab = llama_model_get_vocab(model);
|
| 206 |
+
|
| 207 |
+
// load the prompts from an external file if there are any
|
| 208 |
+
if (params.prompt.empty()) {
|
| 209 |
+
LOG_INF("\033[32mNo new questions so proceed with build-in defaults.\033[0m\n");
|
| 210 |
+
} else {
|
| 211 |
+
// Output each line of the input params.prompts vector and copy to k_prompts
|
| 212 |
+
int index = 0;
|
| 213 |
+
LOG_INF("\033[32mNow printing the external prompt file %s\033[0m\n\n", params.prompt_file.c_str());
|
| 214 |
+
|
| 215 |
+
std::vector<std::string> prompts = split_string(params.prompt, '\n');
|
| 216 |
+
for (const auto& prompt : prompts) {
|
| 217 |
+
k_prompts.resize(index + 1);
|
| 218 |
+
k_prompts[index] = prompt;
|
| 219 |
+
index++;
|
| 220 |
+
LOG_INF("%3d prompt: %s\n", index, prompt.c_str());
|
| 221 |
+
}
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
LOG_INF("\n\n");
|
| 225 |
+
|
| 226 |
+
const int n_ctx = llama_n_ctx(ctx);
|
| 227 |
+
|
| 228 |
+
if (sseed >= 0) {
|
| 229 |
+
LOG_INF("%s: initializing all samplers with the same RNG seed: %d (use a negative seed to have different seeds)\n", __func__, sseed);
|
| 230 |
+
} else {
|
| 231 |
+
LOG_INF("%s: initializing samplers with different RNG seeds, starting from %d\n", __func__, sseed);
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
std::vector<client> clients(n_clients);
|
| 235 |
+
for (size_t i = 0; i < clients.size(); ++i) {
|
| 236 |
+
auto & client = clients[i];
|
| 237 |
+
client.id = i;
|
| 238 |
+
client.smpl = common_sampler_init(model, params.sampling);
|
| 239 |
+
|
| 240 |
+
if (sseed < 0) {
|
| 241 |
+
params.sampling.seed--;
|
| 242 |
+
}
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
std::vector<llama_token> tokens_system;
|
| 246 |
+
|
| 247 |
+
tokens_system = common_tokenize(ctx, k_system, true);
|
| 248 |
+
const int32_t n_tokens_system = tokens_system.size();
|
| 249 |
+
|
| 250 |
+
llama_seq_id g_seq_id = 0;
|
| 251 |
+
|
| 252 |
+
// the max batch size is as large as the context to handle cases where we get very long input prompt from multiple
|
| 253 |
+
// users. regardless of the size, the main loop will chunk the batch into a maximum of params.n_batch tokens at a time
|
| 254 |
+
llama_batch batch = llama_batch_init(n_ctx, 0, 1);
|
| 255 |
+
|
| 256 |
+
int32_t n_total_prompt = 0;
|
| 257 |
+
int32_t n_total_gen = 0;
|
| 258 |
+
int32_t n_cache_miss = 0;
|
| 259 |
+
|
| 260 |
+
const auto t_main_start = ggml_time_us();
|
| 261 |
+
|
| 262 |
+
LOG_INF("%s: Simulating parallel requests from clients:\n", __func__);
|
| 263 |
+
LOG_INF("%s: n_parallel = %d, n_sequences = %d, cont_batching = %d, system tokens = %d\n", __func__, n_clients, n_seq, cont_batching, n_tokens_system);
|
| 264 |
+
LOG_INF("\n");
|
| 265 |
+
|
| 266 |
+
if (is_sp_shared) {
|
| 267 |
+
LOG_INF("%s: Evaluating the system prompt ...\n", __func__);
|
| 268 |
+
|
| 269 |
+
for (int32_t i = 0; i < n_tokens_system; ++i) {
|
| 270 |
+
common_batch_add(batch, tokens_system[i], i, { 0 }, false);
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
if (llama_decode(ctx, batch) != 0) {
|
| 274 |
+
LOG_ERR("%s: llama_decode() failed\n", __func__);
|
| 275 |
+
return 1;
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
// assign the system KV cache to all parallel sequences
|
| 279 |
+
for (int32_t i = 1; i <= n_clients; ++i) {
|
| 280 |
+
llama_memory_seq_cp(mem, 0, i, -1, -1);
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
LOG_INF("\n");
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
LOG_INF("Processing requests ...\n\n");
|
| 287 |
+
|
| 288 |
+
while (true) {
|
| 289 |
+
common_batch_clear(batch);
|
| 290 |
+
|
| 291 |
+
// decode any currently ongoing sequences
|
| 292 |
+
for (auto & client : clients) {
|
| 293 |
+
if (client.seq_id == -1) {
|
| 294 |
+
continue;
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
client.i_batch = batch.n_tokens;
|
| 298 |
+
|
| 299 |
+
common_batch_add(batch, client.sampled, client.n_past++, { client.id + 1 }, true);
|
| 300 |
+
|
| 301 |
+
client.n_decoded += 1;
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
if (batch.n_tokens == 0) {
|
| 305 |
+
// all sequences have ended - clear the entire KV cache
|
| 306 |
+
for (int i = 1; i <= n_clients; ++i) {
|
| 307 |
+
llama_memory_seq_rm(mem, i, -1, -1);
|
| 308 |
+
// but keep the system prompt
|
| 309 |
+
llama_memory_seq_cp(mem, 0, i, -1, -1);
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
LOG_INF("%s: clearing the KV cache\n", __func__);
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
// insert new sequences for decoding
|
| 316 |
+
if (cont_batching || batch.n_tokens == 0) {
|
| 317 |
+
for (auto & client : clients) {
|
| 318 |
+
if (client.seq_id == -1 && g_seq_id < n_seq) {
|
| 319 |
+
client.seq_id = g_seq_id;
|
| 320 |
+
|
| 321 |
+
client.t_start_prompt = ggml_time_us();
|
| 322 |
+
client.t_start_gen = 0;
|
| 323 |
+
|
| 324 |
+
client.input = k_prompts[rand() % k_prompts.size()];
|
| 325 |
+
client.response = "";
|
| 326 |
+
|
| 327 |
+
// construct the prompt:
|
| 328 |
+
// [system prompt] + [junk] + [user prompt]
|
| 329 |
+
client.n_past = 0;
|
| 330 |
+
client.prompt = "";
|
| 331 |
+
if (is_sp_shared) {
|
| 332 |
+
client.n_past = n_tokens_system;
|
| 333 |
+
} else {
|
| 334 |
+
client.prompt += k_system;
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
const int n_junk_cur = rand() % n_junk;
|
| 338 |
+
|
| 339 |
+
for (int i = 0; i < n_junk_cur; ++i) {
|
| 340 |
+
const int r = rand() % k_questions.size();
|
| 341 |
+
client.prompt += "User:\n" + k_questions[r] + "\nAssistant:\n " + k_answers[r] + "\n";
|
| 342 |
+
}
|
| 343 |
+
client.prompt += "User:\n" + client.input + "\nAssistant:\n";
|
| 344 |
+
|
| 345 |
+
common_sampler_reset(client.smpl);
|
| 346 |
+
|
| 347 |
+
// do not prepend BOS because we have a system prompt!
|
| 348 |
+
std::vector<llama_token> tokens_prompt;
|
| 349 |
+
tokens_prompt = common_tokenize(ctx, client.prompt, false);
|
| 350 |
+
|
| 351 |
+
for (size_t i = 0; i < tokens_prompt.size(); ++i) {
|
| 352 |
+
common_batch_add(batch, tokens_prompt[i], client.n_past++, { client.id + 1 }, false);
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
// extract the logits only for the last token
|
| 356 |
+
if (batch.n_tokens > 0) {
|
| 357 |
+
batch.logits[batch.n_tokens - 1] = true;
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
client.n_prompt = tokens_prompt.size();
|
| 361 |
+
client.n_decoded = 0;
|
| 362 |
+
client.i_batch = batch.n_tokens - 1;
|
| 363 |
+
|
| 364 |
+
LOG_INF("\033[31mClient %3d, seq %4d, junk = %4d, prompt = %d, started decoding ...\033[0m\n", client.id, client.seq_id, n_junk_cur, client.n_prompt);
|
| 365 |
+
|
| 366 |
+
g_seq_id += 1;
|
| 367 |
+
|
| 368 |
+
// insert new requests one-by-one
|
| 369 |
+
//if (cont_batching) {
|
| 370 |
+
// break;
|
| 371 |
+
//}
|
| 372 |
+
}
|
| 373 |
+
}
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
if (batch.n_tokens == 0) {
|
| 377 |
+
break;
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
// process in chunks of params.n_batch
|
| 381 |
+
int32_t n_batch = params.n_batch;
|
| 382 |
+
|
| 383 |
+
int32_t i_next = 0;
|
| 384 |
+
|
| 385 |
+
for (int32_t i = 0; i < batch.n_tokens; i = i_next) {
|
| 386 |
+
// experiment: process in powers of 2
|
| 387 |
+
//if (i + n_batch > (int32_t) batch.n_tokens && n_batch > 32) {
|
| 388 |
+
// n_batch /= 2;
|
| 389 |
+
// i -= n_batch;
|
| 390 |
+
// continue;
|
| 391 |
+
//}
|
| 392 |
+
|
| 393 |
+
const int32_t n_tokens = std::min(n_batch, batch.n_tokens - i);
|
| 394 |
+
|
| 395 |
+
llama_batch batch_view = {
|
| 396 |
+
n_tokens,
|
| 397 |
+
batch.token + i,
|
| 398 |
+
nullptr,
|
| 399 |
+
batch.pos + i,
|
| 400 |
+
batch.n_seq_id + i,
|
| 401 |
+
batch.seq_id + i,
|
| 402 |
+
batch.logits + i,
|
| 403 |
+
};
|
| 404 |
+
|
| 405 |
+
const int ret = llama_decode(ctx, batch_view);
|
| 406 |
+
if (ret != 0) {
|
| 407 |
+
if (n_batch == 1 || ret < 0) {
|
| 408 |
+
// if you get here, it means the KV cache is full - try increasing it via the context size
|
| 409 |
+
LOG_ERR("%s : failed to decode the batch, n_batch = %d, ret = %d\n", __func__, n_batch, ret);
|
| 410 |
+
return 1;
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
LOG_WRN("%s : failed to decode the batch, retrying with n_batch = %d\n", __func__, n_batch / 2);
|
| 414 |
+
|
| 415 |
+
n_cache_miss += 1;
|
| 416 |
+
|
| 417 |
+
// retry with half the batch size to try to find a free slot in the KV cache
|
| 418 |
+
n_batch /= 2;
|
| 419 |
+
|
| 420 |
+
continue;
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
LOG_DBG("%s : decoded batch of %d tokens\n", __func__, n_tokens);
|
| 424 |
+
|
| 425 |
+
// move the head of the batch forward with the number of tokens we just processed
|
| 426 |
+
i_next = i + n_tokens;
|
| 427 |
+
|
| 428 |
+
// on successful decode, restore the original batch size
|
| 429 |
+
n_batch = params.n_batch;
|
| 430 |
+
|
| 431 |
+
for (auto & client : clients) {
|
| 432 |
+
if (client.i_batch < (int) i || client.i_batch >= (int) (i + n_tokens)) {
|
| 433 |
+
continue;
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
//printf("client %d, seq %d, token %d, pos %d, batch %d\n",
|
| 437 |
+
// client.id, client.seq_id, client.sampled, client.n_decoded, client.i_batch);
|
| 438 |
+
|
| 439 |
+
const llama_token id = common_sampler_sample(client.smpl, ctx, client.i_batch - i);
|
| 440 |
+
|
| 441 |
+
common_sampler_accept(client.smpl, id, true);
|
| 442 |
+
|
| 443 |
+
if (client.n_decoded == 1) {
|
| 444 |
+
// start measuring generation time after the first token to make sure all concurrent clients
|
| 445 |
+
// have their prompt already processed
|
| 446 |
+
client.t_start_gen = ggml_time_us();
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
const std::string token_str = common_token_to_piece(ctx, id);
|
| 450 |
+
|
| 451 |
+
client.response += token_str;
|
| 452 |
+
client.sampled = id;
|
| 453 |
+
|
| 454 |
+
//printf("client %d, seq %d, token %d, pos %d, batch %d: %s\n",
|
| 455 |
+
// client.id, client.seq_id, id, client.n_decoded, client.i_batch, token_str.c_str());
|
| 456 |
+
|
| 457 |
+
if (client.n_decoded > 2 &&
|
| 458 |
+
(llama_vocab_is_eog(vocab, id) ||
|
| 459 |
+
(params.n_predict > 0 && client.n_decoded >= params.n_predict) ||
|
| 460 |
+
client.response.find("User:") != std::string::npos)) {
|
| 461 |
+
// basic reverse prompt
|
| 462 |
+
const size_t pos = client.response.find("User:");
|
| 463 |
+
if (pos != std::string::npos) {
|
| 464 |
+
client.response = client.response.substr(0, pos);
|
| 465 |
+
}
|
| 466 |
+
|
| 467 |
+
// delete only the generated part of the sequence, i.e. keep the system prompt in the cache
|
| 468 |
+
llama_memory_seq_rm(mem, client.id + 1, -1, -1);
|
| 469 |
+
llama_memory_seq_cp(mem, 0, client.id + 1, -1, -1);
|
| 470 |
+
|
| 471 |
+
const auto t_main_end = ggml_time_us();
|
| 472 |
+
|
| 473 |
+
LOG_INF("\033[31mClient %3d, seq %3d/%3d, prompt %4d t, response %4d t, time %5.2f s, speed %5.2f t/s, cache miss %d \033[0m \n\nInput: %s\n\033[35mResponse: %s\033[0m\n\n",
|
| 474 |
+
client.id, client.seq_id, n_seq, client.n_prompt, client.n_decoded,
|
| 475 |
+
(t_main_end - client.t_start_prompt) / 1e6,
|
| 476 |
+
(double) (client.n_prompt + client.n_decoded) / (t_main_end - client.t_start_prompt) * 1e6,
|
| 477 |
+
n_cache_miss,
|
| 478 |
+
::trim(client.input).c_str(),
|
| 479 |
+
::trim(client.response).c_str());
|
| 480 |
+
|
| 481 |
+
n_total_prompt += client.n_prompt;
|
| 482 |
+
n_total_gen += client.n_decoded;
|
| 483 |
+
|
| 484 |
+
client.seq_id = -1;
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
client.i_batch = -1;
|
| 488 |
+
}
|
| 489 |
+
}
|
| 490 |
+
}
|
| 491 |
+
|
| 492 |
+
const auto t_main_end = ggml_time_us();
|
| 493 |
+
|
| 494 |
+
print_date_time();
|
| 495 |
+
|
| 496 |
+
LOG_INF("%s: n_parallel = %d, n_sequences = %d, cont_batching = %d, system tokens = %d\n", __func__, n_clients, n_seq, cont_batching, n_tokens_system);
|
| 497 |
+
if (params.prompt_file.empty()) {
|
| 498 |
+
params.prompt_file = "used built-in defaults";
|
| 499 |
+
}
|
| 500 |
+
LOG_INF("External prompt file: \033[32m%s\033[0m\n", params.prompt_file.c_str());
|
| 501 |
+
LOG_INF("Model and path used: \033[32m%s\033[0m\n\n", params.model.path.c_str());
|
| 502 |
+
|
| 503 |
+
LOG_INF("Total prompt tokens: %6d, speed: %5.2f t/s\n", n_total_prompt, (double) (n_total_prompt ) / (t_main_end - t_main_start) * 1e6);
|
| 504 |
+
LOG_INF("Total gen tokens: %6d, speed: %5.2f t/s\n", n_total_gen, (double) (n_total_gen ) / (t_main_end - t_main_start) * 1e6);
|
| 505 |
+
LOG_INF("Total speed (AVG): %6s speed: %5.2f t/s\n", "", (double) (n_total_prompt + n_total_gen) / (t_main_end - t_main_start) * 1e6);
|
| 506 |
+
LOG_INF("Cache misses: %6d\n", n_cache_miss);
|
| 507 |
+
|
| 508 |
+
LOG_INF("\n");
|
| 509 |
+
|
| 510 |
+
// TODO: print sampling/grammar timings for all clients
|
| 511 |
+
llama_perf_context_print(ctx);
|
| 512 |
+
|
| 513 |
+
llama_batch_free(batch);
|
| 514 |
+
|
| 515 |
+
llama_backend_free();
|
| 516 |
+
|
| 517 |
+
LOG("\n\n");
|
| 518 |
+
|
| 519 |
+
return 0;
|
| 520 |
+
}
|
backend/llama.cpp/examples/passkey/CMakeLists.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
set(TARGET llama-passkey)
|
| 2 |
+
add_executable(${TARGET} passkey.cpp)
|
| 3 |
+
install(TARGETS ${TARGET} RUNTIME)
|
| 4 |
+
target_link_libraries(${TARGET} PRIVATE llama-common llama ${CMAKE_THREAD_LIBS_INIT})
|
| 5 |
+
target_compile_features(${TARGET} PRIVATE cxx_std_17)
|
backend/llama.cpp/examples/passkey/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# llama.cpp/example/passkey
|
| 2 |
+
|
| 3 |
+
A passkey retrieval task is an evaluation method used to measure a language
|
| 4 |
+
models ability to recall information from long contexts.
|
| 5 |
+
|
| 6 |
+
See the following PRs for more info:
|
| 7 |
+
|
| 8 |
+
- https://github.com/ggml-org/llama.cpp/pull/3856
|
| 9 |
+
- https://github.com/ggml-org/llama.cpp/pull/4810
|
| 10 |
+
|
| 11 |
+
### Usage
|
| 12 |
+
|
| 13 |
+
```bash
|
| 14 |
+
llama-passkey -m ./models/llama-7b-v2/ggml-model-f16.gguf --junk 250
|
| 15 |
+
```
|
backend/llama.cpp/examples/passkey/passkey.cpp
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "arg.h"
|
| 2 |
+
#include "common.h"
|
| 3 |
+
#include "log.h"
|
| 4 |
+
#include "llama.h"
|
| 5 |
+
|
| 6 |
+
#include <clocale>
|
| 7 |
+
#include <cmath>
|
| 8 |
+
#include <cstdio>
|
| 9 |
+
#include <string>
|
| 10 |
+
#include <vector>
|
| 11 |
+
#include <algorithm>
|
| 12 |
+
|
| 13 |
+
static void print_usage(int, char ** argv) {
|
| 14 |
+
LOG("\nexample usage:\n");
|
| 15 |
+
LOG("\n %s -m model.gguf --junk 250 --pos 90 --keep 32 --grp-attn-n 2 [--seed 1234]\n", argv[0]);
|
| 16 |
+
LOG("\n");
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
int main(int argc, char ** argv) {
|
| 20 |
+
std::setlocale(LC_NUMERIC, "C");
|
| 21 |
+
|
| 22 |
+
common_params params;
|
| 23 |
+
|
| 24 |
+
params.n_junk = 250;
|
| 25 |
+
params.n_keep = 32;
|
| 26 |
+
params.i_pos = -1;
|
| 27 |
+
|
| 28 |
+
common_init();
|
| 29 |
+
|
| 30 |
+
if (!common_params_parse(argc, argv, params, LLAMA_EXAMPLE_PASSKEY, print_usage)) {
|
| 31 |
+
return 1;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
int n_junk = params.n_junk;
|
| 35 |
+
int n_keep = params.n_keep;
|
| 36 |
+
int n_grp = params.grp_attn_n;
|
| 37 |
+
int i_pos = params.i_pos;
|
| 38 |
+
|
| 39 |
+
if (i_pos == -1) {
|
| 40 |
+
i_pos = rand() % n_junk;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
const std::string prompt_prefix = "There is an important info hidden inside a lot of irrelevant text. Find it and memorize them. I will quiz you about the important information there.";
|
| 44 |
+
const std::string prompt_suffix = " What is the pass key? The pass key is";
|
| 45 |
+
|
| 46 |
+
// generate junk text
|
| 47 |
+
params.prompt = prompt_prefix;
|
| 48 |
+
|
| 49 |
+
const int passkey = rand() % 50000 + 1;
|
| 50 |
+
|
| 51 |
+
for (int i = 0; i < n_junk; i++) {
|
| 52 |
+
if (i % n_junk == i_pos) {
|
| 53 |
+
params.prompt += " The pass key is " + std::to_string(passkey) + ". Remember it. " + std::to_string(passkey) + " is the pass key.";
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
params.prompt += " The grass is green. The sky is blue. The sun is yellow. Here we go. There and back again.";
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
params.prompt += prompt_suffix;
|
| 60 |
+
|
| 61 |
+
// init LLM
|
| 62 |
+
|
| 63 |
+
llama_backend_init();
|
| 64 |
+
llama_numa_init(params.numa);
|
| 65 |
+
|
| 66 |
+
// initialize the model
|
| 67 |
+
|
| 68 |
+
llama_model_params model_params = common_model_params_to_llama(params);
|
| 69 |
+
|
| 70 |
+
llama_model * model = llama_model_load_from_file(params.model.path.c_str(), model_params);
|
| 71 |
+
|
| 72 |
+
if (model == NULL) {
|
| 73 |
+
LOG_ERR("%s: unable to load model\n" , __func__);
|
| 74 |
+
return 1;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
const llama_vocab * vocab = llama_model_get_vocab(model);
|
| 78 |
+
|
| 79 |
+
// initialize the context
|
| 80 |
+
|
| 81 |
+
llama_context_params ctx_params = common_context_params_to_llama(params);
|
| 82 |
+
|
| 83 |
+
ctx_params.n_ctx = llama_model_n_ctx_train(model)*n_grp + n_keep;
|
| 84 |
+
|
| 85 |
+
GGML_ASSERT(ctx_params.n_batch % n_grp == 0 && "n_batch must be divisible by n_grp");
|
| 86 |
+
|
| 87 |
+
llama_context * ctx = llama_init_from_model(model, ctx_params);
|
| 88 |
+
if (ctx == NULL) {
|
| 89 |
+
LOG_ERR("%s: failed to create the llama_context\n" , __func__);
|
| 90 |
+
return 1;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
auto sparams = llama_sampler_chain_default_params();
|
| 94 |
+
|
| 95 |
+
llama_sampler * smpl = llama_sampler_chain_init(sparams);
|
| 96 |
+
|
| 97 |
+
llama_sampler_chain_add(smpl, llama_sampler_init_greedy());
|
| 98 |
+
|
| 99 |
+
// tokenize the prompt
|
| 100 |
+
std::vector<llama_token> tokens_list;
|
| 101 |
+
tokens_list = common_tokenize(ctx, params.prompt, true);
|
| 102 |
+
|
| 103 |
+
// tokenize the prefix and use it as a sink
|
| 104 |
+
const int n_tokens_prefix = common_tokenize(ctx, prompt_prefix, true).size();
|
| 105 |
+
|
| 106 |
+
const int n_tokens_all = tokens_list.size();
|
| 107 |
+
|
| 108 |
+
// we leave a margin of 16 tokens for the generated text - it should contain just the passkey
|
| 109 |
+
const int n_predict = 16;
|
| 110 |
+
|
| 111 |
+
// total length of the sequences including the prompt
|
| 112 |
+
const int n_len = n_tokens_all + n_predict;
|
| 113 |
+
|
| 114 |
+
const int n_ctx = llama_n_ctx(ctx) - n_keep;
|
| 115 |
+
const int n_kv_req = llama_n_ctx(ctx);
|
| 116 |
+
const int n_batch = ctx_params.n_batch;
|
| 117 |
+
const int n_batch_grp = ctx_params.n_batch/n_grp;
|
| 118 |
+
|
| 119 |
+
LOG_INF("\n%s: n_len = %d, n_ctx = %d, n_kv_req = %d, n_grp = %d, n_batch = %d, n_junk = %d, i_pos = %d\n", __func__, n_len, n_ctx, n_kv_req, n_grp, n_batch, n_junk, i_pos);
|
| 120 |
+
|
| 121 |
+
// print the prompt token-by-token
|
| 122 |
+
|
| 123 |
+
LOG_INF("\n");
|
| 124 |
+
LOG_INF("prefix tokens: %d\n", n_tokens_prefix);
|
| 125 |
+
LOG_INF("prompt tokens: %d\n", n_tokens_all);
|
| 126 |
+
//LOG_INF("prompt: %s\n", params.prompt.c_str());
|
| 127 |
+
|
| 128 |
+
llama_batch batch = llama_batch_init(params.n_batch, 0, 1);
|
| 129 |
+
|
| 130 |
+
int n_past = 0;
|
| 131 |
+
|
| 132 |
+
auto * mem = llama_get_memory(ctx);
|
| 133 |
+
|
| 134 |
+
// fill the KV cache
|
| 135 |
+
for (int i = 0; i < n_ctx; i += n_batch) {
|
| 136 |
+
if (i > 0 && n_grp > 1) {
|
| 137 |
+
// if SelfExtend is enabled, we compress the position from the last batch by a factor of n_grp
|
| 138 |
+
const int ib = i/n_batch - 1;
|
| 139 |
+
const int bd = n_batch_grp*(n_grp - 1);
|
| 140 |
+
|
| 141 |
+
llama_memory_seq_add(mem, 0, n_past - n_batch, n_past, ib*bd);
|
| 142 |
+
llama_memory_seq_div(mem, 0, n_past - n_batch + ib*bd, n_past + ib*bd, n_grp);
|
| 143 |
+
|
| 144 |
+
n_past = llama_memory_seq_pos_max(mem, 0) + 1;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
common_batch_clear(batch);
|
| 148 |
+
|
| 149 |
+
for (int j = 0; j < n_batch && i + j < n_tokens_all; j++) {
|
| 150 |
+
common_batch_add(batch, tokens_list[i + j], n_past++, { 0 }, false);
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
if (i + n_batch >= n_tokens_all) {
|
| 154 |
+
batch.logits[batch.n_tokens - 1] = true;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
if (llama_decode(ctx, batch) != 0) {
|
| 158 |
+
LOG_INF("%s: llama_decode() failed\n", __func__);
|
| 159 |
+
return 1;
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
LOG_INF("%s: processed: [%6d, %6d)\n", __func__, i, std::min(i + n_batch, n_tokens_all));
|
| 163 |
+
|
| 164 |
+
if (i + n_batch >= n_tokens_all) {
|
| 165 |
+
break;
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
for (int i = n_ctx; i < n_tokens_all; i += n_batch) {
|
| 170 |
+
const int n_discard = n_batch;
|
| 171 |
+
|
| 172 |
+
LOG_INF("%s: shifting KV cache with %d\n", __func__, n_discard);
|
| 173 |
+
|
| 174 |
+
llama_memory_seq_rm (mem, 0, n_keep , n_keep + n_discard);
|
| 175 |
+
llama_memory_seq_add(mem, 0, n_keep + n_discard, n_ctx, -n_discard);
|
| 176 |
+
|
| 177 |
+
n_past = llama_memory_seq_pos_max(mem, 0) + 1;
|
| 178 |
+
|
| 179 |
+
common_batch_clear(batch);
|
| 180 |
+
|
| 181 |
+
for (int j = 0; j < n_batch && i + j < n_tokens_all; j++) {
|
| 182 |
+
common_batch_add(batch, tokens_list[i + j], n_past++, { 0 }, false);
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
if (i + n_batch >= n_tokens_all) {
|
| 186 |
+
batch.logits[batch.n_tokens - 1] = true;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
if (llama_decode(ctx, batch) != 0) {
|
| 190 |
+
LOG_ERR("%s: llama_decode() failed\n", __func__);
|
| 191 |
+
return 1;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
LOG_INF("%s: processed: [%6d, %6d)\n", __func__, i, std::min(i + n_batch, n_tokens_all));
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
{
|
| 198 |
+
const int n_discard = n_past - n_ctx + n_predict;
|
| 199 |
+
|
| 200 |
+
if (n_discard > 0) {
|
| 201 |
+
LOG_INF("%s: shifting KV cache with %d to free space for the answer\n", __func__, n_discard);
|
| 202 |
+
|
| 203 |
+
llama_memory_seq_rm (mem, 0, n_keep , n_keep + n_discard);
|
| 204 |
+
llama_memory_seq_add(mem, 0, n_keep + n_discard, n_ctx, -n_discard);
|
| 205 |
+
|
| 206 |
+
n_past = llama_memory_seq_pos_max(mem, 0) + 1;
|
| 207 |
+
}
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
LOG_INF("\n");
|
| 211 |
+
LOG_INF("%s: passkey = %d, inserted at position %d / %d (token pos: ~%d)\n", __func__, passkey, i_pos, n_junk, (i_pos * n_tokens_all) / n_junk);
|
| 212 |
+
LOG_INF("\n");
|
| 213 |
+
|
| 214 |
+
// main loop
|
| 215 |
+
|
| 216 |
+
int n_cur = n_tokens_all;
|
| 217 |
+
int n_decode = 0;
|
| 218 |
+
|
| 219 |
+
LOG_INF("%s", prompt_suffix.c_str());
|
| 220 |
+
|
| 221 |
+
const auto t_main_start = ggml_time_us();
|
| 222 |
+
|
| 223 |
+
while (n_cur <= n_len) {
|
| 224 |
+
// sample the next token
|
| 225 |
+
{
|
| 226 |
+
const llama_token new_token_id = llama_sampler_sample(smpl, ctx, batch.n_tokens - 1);
|
| 227 |
+
|
| 228 |
+
// is it an end of generation?
|
| 229 |
+
if (llama_vocab_is_eog(vocab, new_token_id) || n_cur == n_len) {
|
| 230 |
+
LOG("\n");
|
| 231 |
+
|
| 232 |
+
break;
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
LOG("%s", common_token_to_piece(ctx, new_token_id).c_str());
|
| 236 |
+
|
| 237 |
+
n_decode += 1;
|
| 238 |
+
|
| 239 |
+
// prepare the next batch
|
| 240 |
+
common_batch_clear(batch);
|
| 241 |
+
|
| 242 |
+
// push this new token for next evaluation
|
| 243 |
+
common_batch_add(batch, new_token_id, n_past++, { 0 }, true);
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
n_cur += 1;
|
| 247 |
+
|
| 248 |
+
// evaluate the current batch with the transformer model
|
| 249 |
+
if (llama_decode(ctx, batch)) {
|
| 250 |
+
LOG_ERR("%s : failed to eval, return code %d\n", __func__, 1);
|
| 251 |
+
return 1;
|
| 252 |
+
}
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
LOG("\n");
|
| 256 |
+
|
| 257 |
+
const auto t_main_end = ggml_time_us();
|
| 258 |
+
|
| 259 |
+
LOG_INF("%s: decoded %d tokens in %.2f s, speed: %.2f t/s\n",
|
| 260 |
+
__func__, n_decode, (t_main_end - t_main_start) / 1000000.0f, n_decode / ((t_main_end - t_main_start) / 1000000.0f));
|
| 261 |
+
|
| 262 |
+
LOG("\n");
|
| 263 |
+
llama_perf_context_print(ctx);
|
| 264 |
+
|
| 265 |
+
LOG("\n");
|
| 266 |
+
|
| 267 |
+
llama_sampler_free(smpl);
|
| 268 |
+
|
| 269 |
+
llama_batch_free(batch);
|
| 270 |
+
|
| 271 |
+
llama_free(ctx);
|
| 272 |
+
llama_model_free(model);
|
| 273 |
+
|
| 274 |
+
llama_backend_free();
|
| 275 |
+
|
| 276 |
+
return 0;
|
| 277 |
+
}
|
backend/llama.cpp/examples/pydantic_models_to_grammar.py
ADDED
|
@@ -0,0 +1,1322 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import inspect
|
| 4 |
+
import json
|
| 5 |
+
import re
|
| 6 |
+
from copy import copy
|
| 7 |
+
from enum import Enum
|
| 8 |
+
from inspect import getdoc, isclass
|
| 9 |
+
from typing import TYPE_CHECKING, Any, Callable, Optional, Union, get_args, get_origin, get_type_hints
|
| 10 |
+
|
| 11 |
+
from docstring_parser import parse
|
| 12 |
+
from pydantic import BaseModel, create_model
|
| 13 |
+
|
| 14 |
+
if TYPE_CHECKING:
|
| 15 |
+
from types import GenericAlias
|
| 16 |
+
else:
|
| 17 |
+
# python 3.8 compat
|
| 18 |
+
from typing import _GenericAlias as GenericAlias
|
| 19 |
+
|
| 20 |
+
# TODO: fix this
|
| 21 |
+
# pyright: reportAttributeAccessIssue=information
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class PydanticDataType(Enum):
|
| 25 |
+
"""
|
| 26 |
+
Defines the data types supported by the grammar_generator.
|
| 27 |
+
|
| 28 |
+
Attributes:
|
| 29 |
+
STRING (str): Represents a string data type.
|
| 30 |
+
BOOLEAN (str): Represents a boolean data type.
|
| 31 |
+
INTEGER (str): Represents an integer data type.
|
| 32 |
+
FLOAT (str): Represents a float data type.
|
| 33 |
+
OBJECT (str): Represents an object data type.
|
| 34 |
+
ARRAY (str): Represents an array data type.
|
| 35 |
+
ENUM (str): Represents an enum data type.
|
| 36 |
+
CUSTOM_CLASS (str): Represents a custom class data type.
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
STRING = "string"
|
| 40 |
+
TRIPLE_QUOTED_STRING = "triple_quoted_string"
|
| 41 |
+
MARKDOWN_CODE_BLOCK = "markdown_code_block"
|
| 42 |
+
BOOLEAN = "boolean"
|
| 43 |
+
INTEGER = "integer"
|
| 44 |
+
FLOAT = "float"
|
| 45 |
+
OBJECT = "object"
|
| 46 |
+
ARRAY = "array"
|
| 47 |
+
ENUM = "enum"
|
| 48 |
+
ANY = "any"
|
| 49 |
+
NULL = "null"
|
| 50 |
+
CUSTOM_CLASS = "custom-class"
|
| 51 |
+
CUSTOM_DICT = "custom-dict"
|
| 52 |
+
SET = "set"
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def map_pydantic_type_to_gbnf(pydantic_type: type[Any]) -> str:
|
| 56 |
+
origin_type = get_origin(pydantic_type)
|
| 57 |
+
origin_type = pydantic_type if origin_type is None else origin_type
|
| 58 |
+
|
| 59 |
+
if isclass(origin_type) and issubclass(origin_type, str):
|
| 60 |
+
return PydanticDataType.STRING.value
|
| 61 |
+
elif isclass(origin_type) and issubclass(origin_type, bool):
|
| 62 |
+
return PydanticDataType.BOOLEAN.value
|
| 63 |
+
elif isclass(origin_type) and issubclass(origin_type, int):
|
| 64 |
+
return PydanticDataType.INTEGER.value
|
| 65 |
+
elif isclass(origin_type) and issubclass(origin_type, float):
|
| 66 |
+
return PydanticDataType.FLOAT.value
|
| 67 |
+
elif isclass(origin_type) and issubclass(origin_type, Enum):
|
| 68 |
+
return PydanticDataType.ENUM.value
|
| 69 |
+
|
| 70 |
+
elif isclass(origin_type) and issubclass(origin_type, BaseModel):
|
| 71 |
+
return format_model_and_field_name(origin_type.__name__)
|
| 72 |
+
elif origin_type is list:
|
| 73 |
+
element_type = get_args(pydantic_type)[0]
|
| 74 |
+
return f"{map_pydantic_type_to_gbnf(element_type)}-list"
|
| 75 |
+
elif origin_type is set:
|
| 76 |
+
element_type = get_args(pydantic_type)[0]
|
| 77 |
+
return f"{map_pydantic_type_to_gbnf(element_type)}-set"
|
| 78 |
+
elif origin_type is Union:
|
| 79 |
+
union_types = get_args(pydantic_type)
|
| 80 |
+
union_rules = [map_pydantic_type_to_gbnf(ut) for ut in union_types]
|
| 81 |
+
return f"union-{'-or-'.join(union_rules)}"
|
| 82 |
+
elif origin_type is Optional:
|
| 83 |
+
element_type = get_args(pydantic_type)[0]
|
| 84 |
+
return f"optional-{map_pydantic_type_to_gbnf(element_type)}"
|
| 85 |
+
elif isclass(origin_type):
|
| 86 |
+
return f"{PydanticDataType.CUSTOM_CLASS.value}-{format_model_and_field_name(origin_type.__name__)}"
|
| 87 |
+
elif origin_type is dict:
|
| 88 |
+
key_type, value_type = get_args(pydantic_type)
|
| 89 |
+
return f"custom-dict-key-type-{format_model_and_field_name(map_pydantic_type_to_gbnf(key_type))}-value-type-{format_model_and_field_name(map_pydantic_type_to_gbnf(value_type))}"
|
| 90 |
+
else:
|
| 91 |
+
return "unknown"
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def format_model_and_field_name(model_name: str) -> str:
|
| 95 |
+
parts = re.findall("[A-Z][^A-Z]*", model_name)
|
| 96 |
+
if not parts: # Check if the list is empty
|
| 97 |
+
return model_name.lower().replace("_", "-")
|
| 98 |
+
return "-".join(part.lower().replace("_", "-") for part in parts)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def generate_list_rule(element_type):
|
| 102 |
+
"""
|
| 103 |
+
Generate a GBNF rule for a list of a given element type.
|
| 104 |
+
|
| 105 |
+
:param element_type: The type of the elements in the list (e.g., 'string').
|
| 106 |
+
:return: A string representing the GBNF rule for a list of the given type.
|
| 107 |
+
"""
|
| 108 |
+
rule_name = f"{map_pydantic_type_to_gbnf(element_type)}-list"
|
| 109 |
+
element_rule = map_pydantic_type_to_gbnf(element_type)
|
| 110 |
+
list_rule = rf'{rule_name} ::= "[" {element_rule} ("," {element_rule})* "]"'
|
| 111 |
+
return list_rule
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def get_members_structure(cls, rule_name):
|
| 115 |
+
if issubclass(cls, Enum):
|
| 116 |
+
# Handle Enum types
|
| 117 |
+
members = [f'"\\"{member.value}\\""' for name, member in cls.__members__.items()]
|
| 118 |
+
return f"{cls.__name__.lower()} ::= " + " | ".join(members)
|
| 119 |
+
if cls.__annotations__ and cls.__annotations__ != {}:
|
| 120 |
+
result = f'{rule_name} ::= "{{"'
|
| 121 |
+
# Modify this comprehension
|
| 122 |
+
members = [
|
| 123 |
+
f' "\\"{name}\\"" ":" {map_pydantic_type_to_gbnf(param_type)}'
|
| 124 |
+
for name, param_type in get_type_hints(cls).items()
|
| 125 |
+
if name != "self"
|
| 126 |
+
]
|
| 127 |
+
|
| 128 |
+
result += '"," '.join(members)
|
| 129 |
+
result += ' "}"'
|
| 130 |
+
return result
|
| 131 |
+
if rule_name == "custom-class-any":
|
| 132 |
+
result = f"{rule_name} ::= "
|
| 133 |
+
result += "value"
|
| 134 |
+
return result
|
| 135 |
+
|
| 136 |
+
init_signature = inspect.signature(cls.__init__)
|
| 137 |
+
parameters = init_signature.parameters
|
| 138 |
+
result = f'{rule_name} ::= "{{"'
|
| 139 |
+
# Modify this comprehension too
|
| 140 |
+
members = [
|
| 141 |
+
f' "\\"{name}\\"" ":" {map_pydantic_type_to_gbnf(param.annotation)}'
|
| 142 |
+
for name, param in parameters.items()
|
| 143 |
+
if name != "self" and param.annotation != inspect.Parameter.empty
|
| 144 |
+
]
|
| 145 |
+
|
| 146 |
+
result += '", "'.join(members)
|
| 147 |
+
result += ' "}"'
|
| 148 |
+
return result
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def regex_to_gbnf(regex_pattern: str) -> str:
|
| 152 |
+
"""
|
| 153 |
+
Translate a basic regex pattern to a GBNF rule.
|
| 154 |
+
Note: This function handles only a subset of simple regex patterns.
|
| 155 |
+
"""
|
| 156 |
+
gbnf_rule = regex_pattern
|
| 157 |
+
|
| 158 |
+
# Translate common regex components to GBNF
|
| 159 |
+
gbnf_rule = gbnf_rule.replace("\\d", "[0-9]")
|
| 160 |
+
gbnf_rule = gbnf_rule.replace("\\s", "[ \t\n]")
|
| 161 |
+
|
| 162 |
+
# Handle quantifiers and other regex syntax that is similar in GBNF
|
| 163 |
+
# (e.g., '*', '+', '?', character classes)
|
| 164 |
+
|
| 165 |
+
return gbnf_rule
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def generate_gbnf_integer_rules(max_digit=None, min_digit=None):
|
| 169 |
+
"""
|
| 170 |
+
|
| 171 |
+
Generate GBNF Integer Rules
|
| 172 |
+
|
| 173 |
+
Generates GBNF (Generalized Backus-Naur Form) rules for integers based on the given maximum and minimum digits.
|
| 174 |
+
|
| 175 |
+
Parameters:
|
| 176 |
+
max_digit (int): The maximum number of digits for the integer. Default is None.
|
| 177 |
+
min_digit (int): The minimum number of digits for the integer. Default is None.
|
| 178 |
+
|
| 179 |
+
Returns:
|
| 180 |
+
integer_rule (str): The identifier for the integer rule generated.
|
| 181 |
+
additional_rules (list): A list of additional rules generated based on the given maximum and minimum digits.
|
| 182 |
+
|
| 183 |
+
"""
|
| 184 |
+
additional_rules = []
|
| 185 |
+
|
| 186 |
+
# Define the rule identifier based on max_digit and min_digit
|
| 187 |
+
integer_rule = "integer-part"
|
| 188 |
+
if max_digit is not None:
|
| 189 |
+
integer_rule += f"-max{max_digit}"
|
| 190 |
+
if min_digit is not None:
|
| 191 |
+
integer_rule += f"-min{min_digit}"
|
| 192 |
+
|
| 193 |
+
# Handling Integer Rules
|
| 194 |
+
if max_digit is not None or min_digit is not None:
|
| 195 |
+
# Start with an empty rule part
|
| 196 |
+
integer_rule_part = ""
|
| 197 |
+
|
| 198 |
+
# Add mandatory digits as per min_digit
|
| 199 |
+
if min_digit is not None:
|
| 200 |
+
integer_rule_part += "[0-9] " * min_digit
|
| 201 |
+
|
| 202 |
+
# Add optional digits up to max_digit
|
| 203 |
+
if max_digit is not None:
|
| 204 |
+
optional_digits = max_digit - (min_digit if min_digit is not None else 0)
|
| 205 |
+
integer_rule_part += "".join(["[0-9]? " for _ in range(optional_digits)])
|
| 206 |
+
|
| 207 |
+
# Trim the rule part and append it to additional rules
|
| 208 |
+
integer_rule_part = integer_rule_part.strip()
|
| 209 |
+
if integer_rule_part:
|
| 210 |
+
additional_rules.append(f"{integer_rule} ::= {integer_rule_part}")
|
| 211 |
+
|
| 212 |
+
return integer_rule, additional_rules
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
def generate_gbnf_float_rules(max_digit=None, min_digit=None, max_precision=None, min_precision=None):
|
| 216 |
+
"""
|
| 217 |
+
Generate GBNF float rules based on the given constraints.
|
| 218 |
+
|
| 219 |
+
:param max_digit: Maximum number of digits in the integer part (default: None)
|
| 220 |
+
:param min_digit: Minimum number of digits in the integer part (default: None)
|
| 221 |
+
:param max_precision: Maximum number of digits in the fractional part (default: None)
|
| 222 |
+
:param min_precision: Minimum number of digits in the fractional part (default: None)
|
| 223 |
+
:return: A tuple containing the float rule and additional rules as a list
|
| 224 |
+
|
| 225 |
+
Example Usage:
|
| 226 |
+
max_digit = 3
|
| 227 |
+
min_digit = 1
|
| 228 |
+
max_precision = 2
|
| 229 |
+
min_precision = 1
|
| 230 |
+
generate_gbnf_float_rules(max_digit, min_digit, max_precision, min_precision)
|
| 231 |
+
|
| 232 |
+
Output:
|
| 233 |
+
('float-3-1-2-1', ['integer-part-max3-min1 ::= [0-9] [0-9] [0-9]?', 'fractional-part-max2-min1 ::= [0-9] [0-9]?', 'float-3-1-2-1 ::= integer-part-max3-min1 "." fractional-part-max2-min
|
| 234 |
+
*1'])
|
| 235 |
+
|
| 236 |
+
Note:
|
| 237 |
+
GBNF stands for Generalized Backus-Naur Form, which is a notation technique to specify the syntax of programming languages or other formal grammars.
|
| 238 |
+
"""
|
| 239 |
+
additional_rules = []
|
| 240 |
+
|
| 241 |
+
# Define the integer part rule
|
| 242 |
+
integer_part_rule = (
|
| 243 |
+
"integer-part"
|
| 244 |
+
+ (f"-max{max_digit}" if max_digit is not None else "")
|
| 245 |
+
+ (f"-min{min_digit}" if min_digit is not None else "")
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
# Define the fractional part rule based on precision constraints
|
| 249 |
+
fractional_part_rule = "fractional-part"
|
| 250 |
+
fractional_rule_part = ""
|
| 251 |
+
if max_precision is not None or min_precision is not None:
|
| 252 |
+
fractional_part_rule += (f"-max{max_precision}" if max_precision is not None else "") + (
|
| 253 |
+
f"-min{min_precision}" if min_precision is not None else ""
|
| 254 |
+
)
|
| 255 |
+
# Minimum number of digits
|
| 256 |
+
fractional_rule_part = "[0-9]" * (min_precision if min_precision is not None else 1)
|
| 257 |
+
# Optional additional digits
|
| 258 |
+
fractional_rule_part += "".join(
|
| 259 |
+
[" [0-9]?"] * ((max_precision - (
|
| 260 |
+
min_precision if min_precision is not None else 1)) if max_precision is not None else 0)
|
| 261 |
+
)
|
| 262 |
+
additional_rules.append(f"{fractional_part_rule} ::= {fractional_rule_part}")
|
| 263 |
+
|
| 264 |
+
# Define the float rule
|
| 265 |
+
float_rule = f"float-{max_digit if max_digit is not None else 'X'}-{min_digit if min_digit is not None else 'X'}-{max_precision if max_precision is not None else 'X'}-{min_precision if min_precision is not None else 'X'}"
|
| 266 |
+
additional_rules.append(f'{float_rule} ::= {integer_part_rule} "." {fractional_part_rule}')
|
| 267 |
+
|
| 268 |
+
# Generating the integer part rule definition, if necessary
|
| 269 |
+
if max_digit is not None or min_digit is not None:
|
| 270 |
+
integer_rule_part = "[0-9]"
|
| 271 |
+
if min_digit is not None and min_digit > 1:
|
| 272 |
+
integer_rule_part += " [0-9]" * (min_digit - 1)
|
| 273 |
+
if max_digit is not None:
|
| 274 |
+
integer_rule_part += "".join([" [0-9]?"] * (max_digit - (min_digit if min_digit is not None else 1)))
|
| 275 |
+
additional_rules.append(f"{integer_part_rule} ::= {integer_rule_part.strip()}")
|
| 276 |
+
|
| 277 |
+
return float_rule, additional_rules
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def generate_gbnf_rule_for_type(
|
| 281 |
+
model_name, field_name, field_type, is_optional, processed_models, created_rules, field_info=None
|
| 282 |
+
) -> tuple[str, list[str]]:
|
| 283 |
+
"""
|
| 284 |
+
Generate GBNF rule for a given field type.
|
| 285 |
+
|
| 286 |
+
:param model_name: Name of the model.
|
| 287 |
+
|
| 288 |
+
:param field_name: Name of the field.
|
| 289 |
+
:param field_type: Type of the field.
|
| 290 |
+
:param is_optional: Whether the field is optional.
|
| 291 |
+
:param processed_models: List of processed models.
|
| 292 |
+
:param created_rules: List of created rules.
|
| 293 |
+
:param field_info: Additional information about the field (optional).
|
| 294 |
+
|
| 295 |
+
:return: Tuple containing the GBNF type and a list of additional rules.
|
| 296 |
+
:rtype: tuple[str, list]
|
| 297 |
+
"""
|
| 298 |
+
rules = []
|
| 299 |
+
|
| 300 |
+
field_name = format_model_and_field_name(field_name)
|
| 301 |
+
gbnf_type = map_pydantic_type_to_gbnf(field_type)
|
| 302 |
+
|
| 303 |
+
origin_type = get_origin(field_type)
|
| 304 |
+
origin_type = field_type if origin_type is None else origin_type
|
| 305 |
+
|
| 306 |
+
if isclass(origin_type) and issubclass(origin_type, BaseModel):
|
| 307 |
+
nested_model_name = format_model_and_field_name(field_type.__name__)
|
| 308 |
+
nested_model_rules, _ = generate_gbnf_grammar(field_type, processed_models, created_rules)
|
| 309 |
+
rules.extend(nested_model_rules)
|
| 310 |
+
gbnf_type, rules = nested_model_name, rules
|
| 311 |
+
elif isclass(origin_type) and issubclass(origin_type, Enum):
|
| 312 |
+
enum_values = [f'"\\"{e.value}\\""' for e in field_type] # Adding escaped quotes
|
| 313 |
+
enum_rule = f"{model_name}-{field_name} ::= {' | '.join(enum_values)}"
|
| 314 |
+
rules.append(enum_rule)
|
| 315 |
+
gbnf_type, rules = model_name + "-" + field_name, rules
|
| 316 |
+
elif origin_type is list: # Array
|
| 317 |
+
element_type = get_args(field_type)[0]
|
| 318 |
+
element_rule_name, additional_rules = generate_gbnf_rule_for_type(
|
| 319 |
+
model_name, f"{field_name}-element", element_type, is_optional, processed_models, created_rules
|
| 320 |
+
)
|
| 321 |
+
rules.extend(additional_rules)
|
| 322 |
+
array_rule = f"""{model_name}-{field_name} ::= "[" ws {element_rule_name} ("," ws {element_rule_name})* "]" """
|
| 323 |
+
rules.append(array_rule)
|
| 324 |
+
gbnf_type, rules = model_name + "-" + field_name, rules
|
| 325 |
+
|
| 326 |
+
elif origin_type is set: # Array
|
| 327 |
+
element_type = get_args(field_type)[0]
|
| 328 |
+
element_rule_name, additional_rules = generate_gbnf_rule_for_type(
|
| 329 |
+
model_name, f"{field_name}-element", element_type, is_optional, processed_models, created_rules
|
| 330 |
+
)
|
| 331 |
+
rules.extend(additional_rules)
|
| 332 |
+
array_rule = f"""{model_name}-{field_name} ::= "[" ws {element_rule_name} ("," ws {element_rule_name})* "]" """
|
| 333 |
+
rules.append(array_rule)
|
| 334 |
+
gbnf_type, rules = model_name + "-" + field_name, rules
|
| 335 |
+
|
| 336 |
+
elif gbnf_type.startswith("custom-class-"):
|
| 337 |
+
rules.append(get_members_structure(field_type, gbnf_type))
|
| 338 |
+
elif gbnf_type.startswith("custom-dict-"):
|
| 339 |
+
key_type, value_type = get_args(field_type)
|
| 340 |
+
|
| 341 |
+
additional_key_type, additional_key_rules = generate_gbnf_rule_for_type(
|
| 342 |
+
model_name, f"{field_name}-key-type", key_type, is_optional, processed_models, created_rules
|
| 343 |
+
)
|
| 344 |
+
additional_value_type, additional_value_rules = generate_gbnf_rule_for_type(
|
| 345 |
+
model_name, f"{field_name}-value-type", value_type, is_optional, processed_models, created_rules
|
| 346 |
+
)
|
| 347 |
+
gbnf_type = rf'{gbnf_type} ::= "{{" ( {additional_key_type} ": " {additional_value_type} ("," "\n" ws {additional_key_type} ":" {additional_value_type})* )? "}}" '
|
| 348 |
+
|
| 349 |
+
rules.extend(additional_key_rules)
|
| 350 |
+
rules.extend(additional_value_rules)
|
| 351 |
+
elif gbnf_type.startswith("union-"):
|
| 352 |
+
union_types = get_args(field_type)
|
| 353 |
+
union_rules = []
|
| 354 |
+
|
| 355 |
+
for union_type in union_types:
|
| 356 |
+
if isinstance(union_type, GenericAlias):
|
| 357 |
+
union_gbnf_type, union_rules_list = generate_gbnf_rule_for_type(
|
| 358 |
+
model_name, field_name, union_type, False, processed_models, created_rules
|
| 359 |
+
)
|
| 360 |
+
union_rules.append(union_gbnf_type)
|
| 361 |
+
rules.extend(union_rules_list)
|
| 362 |
+
|
| 363 |
+
elif not issubclass(union_type, type(None)):
|
| 364 |
+
union_gbnf_type, union_rules_list = generate_gbnf_rule_for_type(
|
| 365 |
+
model_name, field_name, union_type, False, processed_models, created_rules
|
| 366 |
+
)
|
| 367 |
+
union_rules.append(union_gbnf_type)
|
| 368 |
+
rules.extend(union_rules_list)
|
| 369 |
+
|
| 370 |
+
# Defining the union grammar rule separately
|
| 371 |
+
if len(union_rules) == 1:
|
| 372 |
+
union_grammar_rule = f"{model_name}-{field_name}-optional ::= {' | '.join(union_rules)} | null"
|
| 373 |
+
else:
|
| 374 |
+
union_grammar_rule = f"{model_name}-{field_name}-union ::= {' | '.join(union_rules)}"
|
| 375 |
+
rules.append(union_grammar_rule)
|
| 376 |
+
if len(union_rules) == 1:
|
| 377 |
+
gbnf_type = f"{model_name}-{field_name}-optional"
|
| 378 |
+
else:
|
| 379 |
+
gbnf_type = f"{model_name}-{field_name}-union"
|
| 380 |
+
elif isclass(origin_type) and issubclass(origin_type, str):
|
| 381 |
+
if field_info and hasattr(field_info, "json_schema_extra") and field_info.json_schema_extra is not None:
|
| 382 |
+
triple_quoted_string = field_info.json_schema_extra.get("triple_quoted_string", False)
|
| 383 |
+
markdown_string = field_info.json_schema_extra.get("markdown_code_block", False)
|
| 384 |
+
|
| 385 |
+
gbnf_type = PydanticDataType.TRIPLE_QUOTED_STRING.value if triple_quoted_string else PydanticDataType.STRING.value
|
| 386 |
+
gbnf_type = PydanticDataType.MARKDOWN_CODE_BLOCK.value if markdown_string else gbnf_type
|
| 387 |
+
|
| 388 |
+
elif field_info and hasattr(field_info, "pattern"):
|
| 389 |
+
# Convert regex pattern to grammar rule
|
| 390 |
+
regex_pattern = field_info.regex.pattern
|
| 391 |
+
gbnf_type = f"pattern-{field_name} ::= {regex_to_gbnf(regex_pattern)}"
|
| 392 |
+
else:
|
| 393 |
+
gbnf_type = PydanticDataType.STRING.value
|
| 394 |
+
|
| 395 |
+
elif (
|
| 396 |
+
isclass(origin_type)
|
| 397 |
+
and issubclass(origin_type, float)
|
| 398 |
+
and field_info
|
| 399 |
+
and hasattr(field_info, "json_schema_extra")
|
| 400 |
+
and field_info.json_schema_extra is not None
|
| 401 |
+
):
|
| 402 |
+
# Retrieve precision attributes for floats
|
| 403 |
+
max_precision = (
|
| 404 |
+
field_info.json_schema_extra.get("max_precision") if field_info and hasattr(field_info,
|
| 405 |
+
"json_schema_extra") else None
|
| 406 |
+
)
|
| 407 |
+
min_precision = (
|
| 408 |
+
field_info.json_schema_extra.get("min_precision") if field_info and hasattr(field_info,
|
| 409 |
+
"json_schema_extra") else None
|
| 410 |
+
)
|
| 411 |
+
max_digits = field_info.json_schema_extra.get("max_digit") if field_info and hasattr(field_info,
|
| 412 |
+
"json_schema_extra") else None
|
| 413 |
+
min_digits = field_info.json_schema_extra.get("min_digit") if field_info and hasattr(field_info,
|
| 414 |
+
"json_schema_extra") else None
|
| 415 |
+
|
| 416 |
+
# Generate GBNF rule for float with given attributes
|
| 417 |
+
gbnf_type, rules = generate_gbnf_float_rules(
|
| 418 |
+
max_digit=max_digits, min_digit=min_digits, max_precision=max_precision, min_precision=min_precision
|
| 419 |
+
)
|
| 420 |
+
|
| 421 |
+
elif (
|
| 422 |
+
isclass(origin_type)
|
| 423 |
+
and issubclass(origin_type, int)
|
| 424 |
+
and field_info
|
| 425 |
+
and hasattr(field_info, "json_schema_extra")
|
| 426 |
+
and field_info.json_schema_extra is not None
|
| 427 |
+
):
|
| 428 |
+
# Retrieve digit attributes for integers
|
| 429 |
+
max_digits = field_info.json_schema_extra.get("max_digit") if field_info and hasattr(field_info,
|
| 430 |
+
"json_schema_extra") else None
|
| 431 |
+
min_digits = field_info.json_schema_extra.get("min_digit") if field_info and hasattr(field_info,
|
| 432 |
+
"json_schema_extra") else None
|
| 433 |
+
|
| 434 |
+
# Generate GBNF rule for integer with given attributes
|
| 435 |
+
gbnf_type, rules = generate_gbnf_integer_rules(max_digit=max_digits, min_digit=min_digits)
|
| 436 |
+
else:
|
| 437 |
+
gbnf_type, rules = gbnf_type, []
|
| 438 |
+
|
| 439 |
+
return gbnf_type, rules
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
def generate_gbnf_grammar(model: type[BaseModel], processed_models: set[type[BaseModel]], created_rules: dict[str, list[str]]) -> tuple[list[str], bool]:
|
| 443 |
+
"""
|
| 444 |
+
|
| 445 |
+
Generate GBnF Grammar
|
| 446 |
+
|
| 447 |
+
Generates a GBnF grammar for a given model.
|
| 448 |
+
|
| 449 |
+
:param model: A Pydantic model class to generate the grammar for. Must be a subclass of BaseModel.
|
| 450 |
+
:param processed_models: A set of already processed models to prevent infinite recursion.
|
| 451 |
+
:param created_rules: A dict containing already created rules to prevent duplicates.
|
| 452 |
+
:return: A list of GBnF grammar rules in string format. And two booleans indicating if an extra markdown or triple quoted string is in the grammar.
|
| 453 |
+
Example Usage:
|
| 454 |
+
```
|
| 455 |
+
model = MyModel
|
| 456 |
+
processed_models = set()
|
| 457 |
+
created_rules = dict()
|
| 458 |
+
|
| 459 |
+
gbnf_grammar = generate_gbnf_grammar(model, processed_models, created_rules)
|
| 460 |
+
```
|
| 461 |
+
"""
|
| 462 |
+
if model in processed_models:
|
| 463 |
+
return [], False
|
| 464 |
+
|
| 465 |
+
processed_models.add(model)
|
| 466 |
+
model_name = format_model_and_field_name(model.__name__)
|
| 467 |
+
|
| 468 |
+
if not issubclass(model, BaseModel):
|
| 469 |
+
# For non-Pydantic classes, generate model_fields from __annotations__ or __init__
|
| 470 |
+
if hasattr(model, "__annotations__") and model.__annotations__:
|
| 471 |
+
model_fields = {name: (typ, ...) for name, typ in get_type_hints(model).items()}
|
| 472 |
+
else:
|
| 473 |
+
init_signature = inspect.signature(model.__init__)
|
| 474 |
+
parameters = init_signature.parameters
|
| 475 |
+
model_fields = {name: (param.annotation, param.default) for name, param in parameters.items() if
|
| 476 |
+
name != "self"}
|
| 477 |
+
else:
|
| 478 |
+
# For Pydantic models, use model_fields and check for ellipsis (required fields)
|
| 479 |
+
model_fields = get_type_hints(model)
|
| 480 |
+
|
| 481 |
+
model_rule_parts = []
|
| 482 |
+
nested_rules = []
|
| 483 |
+
has_markdown_code_block = False
|
| 484 |
+
has_triple_quoted_string = False
|
| 485 |
+
look_for_markdown_code_block = False
|
| 486 |
+
look_for_triple_quoted_string = False
|
| 487 |
+
for field_name, field_info in model_fields.items():
|
| 488 |
+
if not issubclass(model, BaseModel):
|
| 489 |
+
field_type, default_value = field_info
|
| 490 |
+
# Check if the field is optional (not required)
|
| 491 |
+
is_optional = (default_value is not inspect.Parameter.empty) and (default_value is not Ellipsis)
|
| 492 |
+
else:
|
| 493 |
+
field_type = field_info
|
| 494 |
+
field_info = model.model_fields[field_name]
|
| 495 |
+
is_optional = field_info.is_required is False and get_origin(field_type) is Optional
|
| 496 |
+
rule_name, additional_rules = generate_gbnf_rule_for_type(
|
| 497 |
+
model_name, format_model_and_field_name(field_name), field_type, is_optional, processed_models,
|
| 498 |
+
created_rules, field_info
|
| 499 |
+
)
|
| 500 |
+
look_for_markdown_code_block = True if rule_name == "markdown_code_block" else False
|
| 501 |
+
look_for_triple_quoted_string = True if rule_name == "triple_quoted_string" else False
|
| 502 |
+
if not look_for_markdown_code_block and not look_for_triple_quoted_string:
|
| 503 |
+
if rule_name not in created_rules:
|
| 504 |
+
created_rules[rule_name] = additional_rules
|
| 505 |
+
model_rule_parts.append(f' ws "\\"{field_name}\\"" ":" ws {rule_name}') # Adding escaped quotes
|
| 506 |
+
nested_rules.extend(additional_rules)
|
| 507 |
+
else:
|
| 508 |
+
has_triple_quoted_string = look_for_triple_quoted_string
|
| 509 |
+
has_markdown_code_block = look_for_markdown_code_block
|
| 510 |
+
|
| 511 |
+
fields_joined = r' "," "\n" '.join(model_rule_parts)
|
| 512 |
+
model_rule = rf'{model_name} ::= "{{" "\n" {fields_joined} "\n" ws "}}"'
|
| 513 |
+
|
| 514 |
+
has_special_string = False
|
| 515 |
+
if has_triple_quoted_string:
|
| 516 |
+
model_rule += '"\\n" ws "}"'
|
| 517 |
+
model_rule += '"\\n" triple-quoted-string'
|
| 518 |
+
has_special_string = True
|
| 519 |
+
if has_markdown_code_block:
|
| 520 |
+
model_rule += '"\\n" ws "}"'
|
| 521 |
+
model_rule += '"\\n" markdown-code-block'
|
| 522 |
+
has_special_string = True
|
| 523 |
+
all_rules = [model_rule] + nested_rules
|
| 524 |
+
|
| 525 |
+
return all_rules, has_special_string
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
def generate_gbnf_grammar_from_pydantic_models(
|
| 529 |
+
models: list[type[BaseModel]], outer_object_name: str | None = None, outer_object_content: str | None = None,
|
| 530 |
+
list_of_outputs: bool = False
|
| 531 |
+
) -> str:
|
| 532 |
+
"""
|
| 533 |
+
Generate GBNF Grammar from Pydantic Models.
|
| 534 |
+
|
| 535 |
+
This method takes a list of Pydantic models and uses them to generate a GBNF grammar string. The generated grammar string can be used for parsing and validating data using the generated
|
| 536 |
+
* grammar.
|
| 537 |
+
|
| 538 |
+
Args:
|
| 539 |
+
models (list[type[BaseModel]]): A list of Pydantic models to generate the grammar from.
|
| 540 |
+
outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.
|
| 541 |
+
outer_object_content (str): Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling.
|
| 542 |
+
list_of_outputs (str, optional): Allows a list of output objects
|
| 543 |
+
Returns:
|
| 544 |
+
str: The generated GBNF grammar string.
|
| 545 |
+
|
| 546 |
+
Examples:
|
| 547 |
+
models = [UserModel, PostModel]
|
| 548 |
+
grammar = generate_gbnf_grammar_from_pydantic(models)
|
| 549 |
+
print(grammar)
|
| 550 |
+
# Output:
|
| 551 |
+
# root ::= UserModel | PostModel
|
| 552 |
+
# ...
|
| 553 |
+
"""
|
| 554 |
+
processed_models: set[type[BaseModel]] = set()
|
| 555 |
+
all_rules = []
|
| 556 |
+
created_rules: dict[str, list[str]] = {}
|
| 557 |
+
if outer_object_name is None:
|
| 558 |
+
for model in models:
|
| 559 |
+
model_rules, _ = generate_gbnf_grammar(model, processed_models, created_rules)
|
| 560 |
+
all_rules.extend(model_rules)
|
| 561 |
+
|
| 562 |
+
if list_of_outputs:
|
| 563 |
+
root_rule = r'root ::= (" "| "\n") "[" ws grammar-models ("," ws grammar-models)* ws "]"' + "\n"
|
| 564 |
+
else:
|
| 565 |
+
root_rule = r'root ::= (" "| "\n") grammar-models' + "\n"
|
| 566 |
+
root_rule += "grammar-models ::= " + " | ".join(
|
| 567 |
+
[format_model_and_field_name(model.__name__) for model in models])
|
| 568 |
+
all_rules.insert(0, root_rule)
|
| 569 |
+
return "\n".join(all_rules)
|
| 570 |
+
elif outer_object_name is not None:
|
| 571 |
+
if list_of_outputs:
|
| 572 |
+
root_rule = (
|
| 573 |
+
rf'root ::= (" "| "\n") "[" ws {format_model_and_field_name(outer_object_name)} ("," ws {format_model_and_field_name(outer_object_name)})* ws "]"'
|
| 574 |
+
+ "\n"
|
| 575 |
+
)
|
| 576 |
+
else:
|
| 577 |
+
root_rule = f"root ::= {format_model_and_field_name(outer_object_name)}\n"
|
| 578 |
+
|
| 579 |
+
model_rule = (
|
| 580 |
+
rf'{format_model_and_field_name(outer_object_name)} ::= (" "| "\n") "{{" ws "\"{outer_object_name}\"" ":" ws grammar-models'
|
| 581 |
+
)
|
| 582 |
+
|
| 583 |
+
fields_joined = " | ".join(
|
| 584 |
+
[rf"{format_model_and_field_name(model.__name__)}-grammar-model" for model in models])
|
| 585 |
+
|
| 586 |
+
grammar_model_rules = f"\ngrammar-models ::= {fields_joined}"
|
| 587 |
+
mod_rules = []
|
| 588 |
+
for model in models:
|
| 589 |
+
mod_rule = rf"{format_model_and_field_name(model.__name__)}-grammar-model ::= "
|
| 590 |
+
mod_rule += (
|
| 591 |
+
rf'"\"{model.__name__}\"" "," ws "\"{outer_object_content}\"" ":" ws {format_model_and_field_name(model.__name__)}' + "\n"
|
| 592 |
+
)
|
| 593 |
+
mod_rules.append(mod_rule)
|
| 594 |
+
grammar_model_rules += "\n" + "\n".join(mod_rules)
|
| 595 |
+
|
| 596 |
+
for model in models:
|
| 597 |
+
model_rules, has_special_string = generate_gbnf_grammar(model, processed_models,
|
| 598 |
+
created_rules)
|
| 599 |
+
|
| 600 |
+
if not has_special_string:
|
| 601 |
+
model_rules[0] += r'"\n" ws "}"'
|
| 602 |
+
|
| 603 |
+
all_rules.extend(model_rules)
|
| 604 |
+
|
| 605 |
+
all_rules.insert(0, root_rule + model_rule + grammar_model_rules)
|
| 606 |
+
return "\n".join(all_rules)
|
| 607 |
+
|
| 608 |
+
|
| 609 |
+
def get_primitive_grammar(grammar):
|
| 610 |
+
"""
|
| 611 |
+
Returns the needed GBNF primitive grammar for a given GBNF grammar string.
|
| 612 |
+
|
| 613 |
+
Args:
|
| 614 |
+
grammar (str): The string containing the GBNF grammar.
|
| 615 |
+
|
| 616 |
+
Returns:
|
| 617 |
+
str: GBNF primitive grammar string.
|
| 618 |
+
"""
|
| 619 |
+
type_list: list[type[object]] = []
|
| 620 |
+
if "string-list" in grammar:
|
| 621 |
+
type_list.append(str)
|
| 622 |
+
if "boolean-list" in grammar:
|
| 623 |
+
type_list.append(bool)
|
| 624 |
+
if "integer-list" in grammar:
|
| 625 |
+
type_list.append(int)
|
| 626 |
+
if "float-list" in grammar:
|
| 627 |
+
type_list.append(float)
|
| 628 |
+
additional_grammar = [generate_list_rule(t) for t in type_list]
|
| 629 |
+
primitive_grammar = r"""
|
| 630 |
+
boolean ::= "true" | "false"
|
| 631 |
+
null ::= "null"
|
| 632 |
+
string ::= "\"" (
|
| 633 |
+
[^"\\] |
|
| 634 |
+
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
|
| 635 |
+
)* "\"" ws
|
| 636 |
+
ws ::= ([ \t\n] ws)?
|
| 637 |
+
float ::= ("-"? ([0] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? ws
|
| 638 |
+
|
| 639 |
+
integer ::= [0-9]+"""
|
| 640 |
+
|
| 641 |
+
any_block = ""
|
| 642 |
+
if "custom-class-any" in grammar:
|
| 643 |
+
any_block = """
|
| 644 |
+
value ::= object | array | string | number | boolean | null
|
| 645 |
+
|
| 646 |
+
object ::=
|
| 647 |
+
"{" ws (
|
| 648 |
+
string ":" ws value
|
| 649 |
+
("," ws string ":" ws value)*
|
| 650 |
+
)? "}" ws
|
| 651 |
+
|
| 652 |
+
array ::=
|
| 653 |
+
"[" ws (
|
| 654 |
+
value
|
| 655 |
+
("," ws value)*
|
| 656 |
+
)? "]" ws
|
| 657 |
+
|
| 658 |
+
number ::= integer | float"""
|
| 659 |
+
|
| 660 |
+
markdown_code_block_grammar = ""
|
| 661 |
+
if "markdown-code-block" in grammar:
|
| 662 |
+
markdown_code_block_grammar = r'''
|
| 663 |
+
markdown-code-block ::= opening-triple-ticks markdown-code-block-content closing-triple-ticks
|
| 664 |
+
markdown-code-block-content ::= ( [^`] | "`" [^`] | "`" "`" [^`] )*
|
| 665 |
+
opening-triple-ticks ::= "```" "python" "\n" | "```" "c" "\n" | "```" "cpp" "\n" | "```" "txt" "\n" | "```" "text" "\n" | "```" "json" "\n" | "```" "javascript" "\n" | "```" "css" "\n" | "```" "html" "\n" | "```" "markdown" "\n"
|
| 666 |
+
closing-triple-ticks ::= "```" "\n"'''
|
| 667 |
+
|
| 668 |
+
if "triple-quoted-string" in grammar:
|
| 669 |
+
markdown_code_block_grammar = r"""
|
| 670 |
+
triple-quoted-string ::= triple-quotes triple-quoted-string-content triple-quotes
|
| 671 |
+
triple-quoted-string-content ::= ( [^'] | "'" [^'] | "'" "'" [^'] )*
|
| 672 |
+
triple-quotes ::= "'''" """
|
| 673 |
+
return "\n" + "\n".join(additional_grammar) + any_block + primitive_grammar + markdown_code_block_grammar
|
| 674 |
+
|
| 675 |
+
|
| 676 |
+
def generate_markdown_documentation(
|
| 677 |
+
pydantic_models: list[type[BaseModel]], model_prefix="Model", fields_prefix="Fields",
|
| 678 |
+
documentation_with_field_description=True
|
| 679 |
+
) -> str:
|
| 680 |
+
"""
|
| 681 |
+
Generate markdown documentation for a list of Pydantic models.
|
| 682 |
+
|
| 683 |
+
Args:
|
| 684 |
+
pydantic_models (list[type[BaseModel]]): list of Pydantic model classes.
|
| 685 |
+
model_prefix (str): Prefix for the model section.
|
| 686 |
+
fields_prefix (str): Prefix for the fields section.
|
| 687 |
+
documentation_with_field_description (bool): Include field descriptions in the documentation.
|
| 688 |
+
|
| 689 |
+
Returns:
|
| 690 |
+
str: Generated text documentation.
|
| 691 |
+
"""
|
| 692 |
+
documentation = ""
|
| 693 |
+
pyd_models: list[tuple[type[BaseModel], bool]] = [(model, True) for model in pydantic_models]
|
| 694 |
+
for model, add_prefix in pyd_models:
|
| 695 |
+
if add_prefix:
|
| 696 |
+
documentation += f"{model_prefix}: {model.__name__}\n"
|
| 697 |
+
else:
|
| 698 |
+
documentation += f"Model: {model.__name__}\n"
|
| 699 |
+
|
| 700 |
+
# Handling multi-line model description with proper indentation
|
| 701 |
+
|
| 702 |
+
class_doc = getdoc(model)
|
| 703 |
+
base_class_doc = getdoc(BaseModel)
|
| 704 |
+
class_description = class_doc if class_doc and class_doc != base_class_doc else ""
|
| 705 |
+
if class_description != "":
|
| 706 |
+
documentation += " Description: "
|
| 707 |
+
documentation += format_multiline_description(class_description, 0) + "\n"
|
| 708 |
+
|
| 709 |
+
if add_prefix:
|
| 710 |
+
# Indenting the fields section
|
| 711 |
+
documentation += f" {fields_prefix}:\n"
|
| 712 |
+
else:
|
| 713 |
+
documentation += f" Fields:\n" # noqa: F541
|
| 714 |
+
if isclass(model) and issubclass(model, BaseModel):
|
| 715 |
+
for name, field_type in get_type_hints(model).items():
|
| 716 |
+
# if name == "markdown_code_block":
|
| 717 |
+
# continue
|
| 718 |
+
if get_origin(field_type) == list:
|
| 719 |
+
element_type = get_args(field_type)[0]
|
| 720 |
+
if isclass(element_type) and issubclass(element_type, BaseModel):
|
| 721 |
+
pyd_models.append((element_type, False))
|
| 722 |
+
if get_origin(field_type) == Union:
|
| 723 |
+
element_types = get_args(field_type)
|
| 724 |
+
for element_type in element_types:
|
| 725 |
+
if isclass(element_type) and issubclass(element_type, BaseModel):
|
| 726 |
+
pyd_models.append((element_type, False))
|
| 727 |
+
documentation += generate_field_markdown(
|
| 728 |
+
name, field_type, model, documentation_with_field_description=documentation_with_field_description
|
| 729 |
+
)
|
| 730 |
+
documentation += "\n"
|
| 731 |
+
|
| 732 |
+
if hasattr(model, "Config") and hasattr(model.Config,
|
| 733 |
+
"json_schema_extra") and "example" in model.Config.json_schema_extra:
|
| 734 |
+
documentation += f" Expected Example Output for {format_model_and_field_name(model.__name__)}:\n"
|
| 735 |
+
json_example = json.dumps(model.Config.json_schema_extra["example"])
|
| 736 |
+
documentation += format_multiline_description(json_example, 2) + "\n"
|
| 737 |
+
|
| 738 |
+
return documentation
|
| 739 |
+
|
| 740 |
+
|
| 741 |
+
def generate_field_markdown(
|
| 742 |
+
field_name: str, field_type: type[Any], model: type[BaseModel], depth=1,
|
| 743 |
+
documentation_with_field_description=True
|
| 744 |
+
) -> str:
|
| 745 |
+
"""
|
| 746 |
+
Generate markdown documentation for a Pydantic model field.
|
| 747 |
+
|
| 748 |
+
Args:
|
| 749 |
+
field_name (str): Name of the field.
|
| 750 |
+
field_type (type[Any]): Type of the field.
|
| 751 |
+
model (type[BaseModel]): Pydantic model class.
|
| 752 |
+
depth (int): Indentation depth in the documentation.
|
| 753 |
+
documentation_with_field_description (bool): Include field descriptions in the documentation.
|
| 754 |
+
|
| 755 |
+
Returns:
|
| 756 |
+
str: Generated text documentation for the field.
|
| 757 |
+
"""
|
| 758 |
+
indent = " " * depth
|
| 759 |
+
|
| 760 |
+
field_info = model.model_fields.get(field_name)
|
| 761 |
+
field_description = field_info.description if field_info and field_info.description else ""
|
| 762 |
+
|
| 763 |
+
origin_type = get_origin(field_type)
|
| 764 |
+
origin_type = field_type if origin_type is None else origin_type
|
| 765 |
+
|
| 766 |
+
if origin_type == list:
|
| 767 |
+
element_type = get_args(field_type)[0]
|
| 768 |
+
field_text = f"{indent}{field_name} ({format_model_and_field_name(field_type.__name__)} of {format_model_and_field_name(element_type.__name__)})"
|
| 769 |
+
if field_description != "":
|
| 770 |
+
field_text += ":\n"
|
| 771 |
+
else:
|
| 772 |
+
field_text += "\n"
|
| 773 |
+
elif origin_type == Union:
|
| 774 |
+
element_types = get_args(field_type)
|
| 775 |
+
types = []
|
| 776 |
+
for element_type in element_types:
|
| 777 |
+
types.append(format_model_and_field_name(element_type.__name__))
|
| 778 |
+
field_text = f"{indent}{field_name} ({' or '.join(types)})"
|
| 779 |
+
if field_description != "":
|
| 780 |
+
field_text += ":\n"
|
| 781 |
+
else:
|
| 782 |
+
field_text += "\n"
|
| 783 |
+
else:
|
| 784 |
+
field_text = f"{indent}{field_name} ({format_model_and_field_name(field_type.__name__)})"
|
| 785 |
+
if field_description != "":
|
| 786 |
+
field_text += ":\n"
|
| 787 |
+
else:
|
| 788 |
+
field_text += "\n"
|
| 789 |
+
|
| 790 |
+
if not documentation_with_field_description:
|
| 791 |
+
return field_text
|
| 792 |
+
|
| 793 |
+
if field_description != "":
|
| 794 |
+
field_text += f" Description: {field_description}\n"
|
| 795 |
+
|
| 796 |
+
# Check for and include field-specific examples if available
|
| 797 |
+
if hasattr(model, "Config") and hasattr(model.Config,
|
| 798 |
+
"json_schema_extra") and "example" in model.Config.json_schema_extra:
|
| 799 |
+
field_example = model.Config.json_schema_extra["example"].get(field_name)
|
| 800 |
+
if field_example is not None:
|
| 801 |
+
example_text = f"'{field_example}'" if isinstance(field_example, str) else field_example
|
| 802 |
+
field_text += f"{indent} Example: {example_text}\n"
|
| 803 |
+
|
| 804 |
+
if isclass(origin_type) and issubclass(origin_type, BaseModel):
|
| 805 |
+
field_text += f"{indent} Details:\n"
|
| 806 |
+
for name, type_ in get_type_hints(field_type).items():
|
| 807 |
+
field_text += generate_field_markdown(name, type_, field_type, depth + 2)
|
| 808 |
+
|
| 809 |
+
return field_text
|
| 810 |
+
|
| 811 |
+
|
| 812 |
+
def format_json_example(example: dict[str, Any], depth: int) -> str:
|
| 813 |
+
"""
|
| 814 |
+
Format a JSON example into a readable string with indentation.
|
| 815 |
+
|
| 816 |
+
Args:
|
| 817 |
+
example (dict): JSON example to be formatted.
|
| 818 |
+
depth (int): Indentation depth.
|
| 819 |
+
|
| 820 |
+
Returns:
|
| 821 |
+
str: Formatted JSON example string.
|
| 822 |
+
"""
|
| 823 |
+
indent = " " * depth
|
| 824 |
+
formatted_example = "{\n"
|
| 825 |
+
for key, value in example.items():
|
| 826 |
+
value_text = f"'{value}'" if isinstance(value, str) else value
|
| 827 |
+
formatted_example += f"{indent}{key}: {value_text},\n"
|
| 828 |
+
formatted_example = formatted_example.rstrip(",\n") + "\n" + indent + "}"
|
| 829 |
+
return formatted_example
|
| 830 |
+
|
| 831 |
+
|
| 832 |
+
def generate_text_documentation(
|
| 833 |
+
pydantic_models: list[type[BaseModel]], model_prefix="Model", fields_prefix="Fields",
|
| 834 |
+
documentation_with_field_description=True
|
| 835 |
+
) -> str:
|
| 836 |
+
"""
|
| 837 |
+
Generate text documentation for a list of Pydantic models.
|
| 838 |
+
|
| 839 |
+
Args:
|
| 840 |
+
pydantic_models (list[type[BaseModel]]): List of Pydantic model classes.
|
| 841 |
+
model_prefix (str): Prefix for the model section.
|
| 842 |
+
fields_prefix (str): Prefix for the fields section.
|
| 843 |
+
documentation_with_field_description (bool): Include field descriptions in the documentation.
|
| 844 |
+
|
| 845 |
+
Returns:
|
| 846 |
+
str: Generated text documentation.
|
| 847 |
+
"""
|
| 848 |
+
documentation = ""
|
| 849 |
+
pyd_models: list[tuple[type[BaseModel], bool]] = [(model, True) for model in pydantic_models]
|
| 850 |
+
for model, add_prefix in pyd_models:
|
| 851 |
+
if add_prefix:
|
| 852 |
+
documentation += f"{model_prefix}: {model.__name__}\n"
|
| 853 |
+
else:
|
| 854 |
+
documentation += f"Model: {model.__name__}\n"
|
| 855 |
+
|
| 856 |
+
# Handling multi-line model description with proper indentation
|
| 857 |
+
|
| 858 |
+
class_doc = getdoc(model)
|
| 859 |
+
base_class_doc = getdoc(BaseModel)
|
| 860 |
+
class_description = class_doc if class_doc and class_doc != base_class_doc else ""
|
| 861 |
+
if class_description != "":
|
| 862 |
+
documentation += " Description: "
|
| 863 |
+
documentation += "\n" + format_multiline_description(class_description, 2) + "\n"
|
| 864 |
+
|
| 865 |
+
if isclass(model) and issubclass(model, BaseModel):
|
| 866 |
+
documentation_fields = ""
|
| 867 |
+
for name, field_type in get_type_hints(model).items():
|
| 868 |
+
# if name == "markdown_code_block":
|
| 869 |
+
# continue
|
| 870 |
+
if get_origin(field_type) == list:
|
| 871 |
+
element_type = get_args(field_type)[0]
|
| 872 |
+
if isclass(element_type) and issubclass(element_type, BaseModel):
|
| 873 |
+
pyd_models.append((element_type, False))
|
| 874 |
+
if get_origin(field_type) == Union:
|
| 875 |
+
element_types = get_args(field_type)
|
| 876 |
+
for element_type in element_types:
|
| 877 |
+
if isclass(element_type) and issubclass(element_type, BaseModel):
|
| 878 |
+
pyd_models.append((element_type, False))
|
| 879 |
+
documentation_fields += generate_field_text(
|
| 880 |
+
name, field_type, model, documentation_with_field_description=documentation_with_field_description
|
| 881 |
+
)
|
| 882 |
+
if documentation_fields != "":
|
| 883 |
+
if add_prefix:
|
| 884 |
+
documentation += f" {fields_prefix}:\n{documentation_fields}"
|
| 885 |
+
else:
|
| 886 |
+
documentation += f" Fields:\n{documentation_fields}"
|
| 887 |
+
documentation += "\n"
|
| 888 |
+
|
| 889 |
+
if hasattr(model, "Config") and hasattr(model.Config,
|
| 890 |
+
"json_schema_extra") and "example" in model.Config.json_schema_extra:
|
| 891 |
+
documentation += f" Expected Example Output for {format_model_and_field_name(model.__name__)}:\n"
|
| 892 |
+
json_example = json.dumps(model.Config.json_schema_extra["example"])
|
| 893 |
+
documentation += format_multiline_description(json_example, 2) + "\n"
|
| 894 |
+
|
| 895 |
+
return documentation
|
| 896 |
+
|
| 897 |
+
|
| 898 |
+
def generate_field_text(
|
| 899 |
+
field_name: str, field_type: type[Any], model: type[BaseModel], depth=1,
|
| 900 |
+
documentation_with_field_description=True
|
| 901 |
+
) -> str:
|
| 902 |
+
"""
|
| 903 |
+
Generate text documentation for a Pydantic model field.
|
| 904 |
+
|
| 905 |
+
Args:
|
| 906 |
+
field_name (str): Name of the field.
|
| 907 |
+
field_type (type[Any]): Type of the field.
|
| 908 |
+
model (type[BaseModel]): Pydantic model class.
|
| 909 |
+
depth (int): Indentation depth in the documentation.
|
| 910 |
+
documentation_with_field_description (bool): Include field descriptions in the documentation.
|
| 911 |
+
|
| 912 |
+
Returns:
|
| 913 |
+
str: Generated text documentation for the field.
|
| 914 |
+
"""
|
| 915 |
+
indent = " " * depth
|
| 916 |
+
|
| 917 |
+
field_info = model.model_fields.get(field_name)
|
| 918 |
+
field_description = field_info.description if field_info and field_info.description else ""
|
| 919 |
+
|
| 920 |
+
if get_origin(field_type) == list:
|
| 921 |
+
element_type = get_args(field_type)[0]
|
| 922 |
+
field_text = f"{indent}{field_name} ({format_model_and_field_name(field_type.__name__)} of {format_model_and_field_name(element_type.__name__)})"
|
| 923 |
+
if field_description != "":
|
| 924 |
+
field_text += ":\n"
|
| 925 |
+
else:
|
| 926 |
+
field_text += "\n"
|
| 927 |
+
elif get_origin(field_type) == Union:
|
| 928 |
+
element_types = get_args(field_type)
|
| 929 |
+
types = []
|
| 930 |
+
for element_type in element_types:
|
| 931 |
+
types.append(format_model_and_field_name(element_type.__name__))
|
| 932 |
+
field_text = f"{indent}{field_name} ({' or '.join(types)})"
|
| 933 |
+
if field_description != "":
|
| 934 |
+
field_text += ":\n"
|
| 935 |
+
else:
|
| 936 |
+
field_text += "\n"
|
| 937 |
+
else:
|
| 938 |
+
field_text = f"{indent}{field_name} ({format_model_and_field_name(field_type.__name__)})"
|
| 939 |
+
if field_description != "":
|
| 940 |
+
field_text += ":\n"
|
| 941 |
+
else:
|
| 942 |
+
field_text += "\n"
|
| 943 |
+
|
| 944 |
+
if not documentation_with_field_description:
|
| 945 |
+
return field_text
|
| 946 |
+
|
| 947 |
+
if field_description != "":
|
| 948 |
+
field_text += f"{indent} Description: " + field_description + "\n"
|
| 949 |
+
|
| 950 |
+
# Check for and include field-specific examples if available
|
| 951 |
+
if hasattr(model, "Config") and hasattr(model.Config,
|
| 952 |
+
"json_schema_extra") and "example" in model.Config.json_schema_extra:
|
| 953 |
+
field_example = model.Config.json_schema_extra["example"].get(field_name)
|
| 954 |
+
if field_example is not None:
|
| 955 |
+
example_text = f"'{field_example}'" if isinstance(field_example, str) else field_example
|
| 956 |
+
field_text += f"{indent} Example: {example_text}\n"
|
| 957 |
+
|
| 958 |
+
if isclass(field_type) and issubclass(field_type, BaseModel):
|
| 959 |
+
field_text += f"{indent} Details:\n"
|
| 960 |
+
for name, type_ in get_type_hints(field_type).items():
|
| 961 |
+
field_text += generate_field_text(name, type_, field_type, depth + 2)
|
| 962 |
+
|
| 963 |
+
return field_text
|
| 964 |
+
|
| 965 |
+
|
| 966 |
+
def format_multiline_description(description: str, indent_level: int) -> str:
|
| 967 |
+
"""
|
| 968 |
+
Format a multiline description with proper indentation.
|
| 969 |
+
|
| 970 |
+
Args:
|
| 971 |
+
description (str): Multiline description.
|
| 972 |
+
indent_level (int): Indentation level.
|
| 973 |
+
|
| 974 |
+
Returns:
|
| 975 |
+
str: Formatted multiline description.
|
| 976 |
+
"""
|
| 977 |
+
indent = " " * indent_level
|
| 978 |
+
return indent + description.replace("\n", "\n" + indent)
|
| 979 |
+
|
| 980 |
+
|
| 981 |
+
def save_gbnf_grammar_and_documentation(
|
| 982 |
+
grammar, documentation, grammar_file_path="./grammar.gbnf", documentation_file_path="./grammar_documentation.md"
|
| 983 |
+
):
|
| 984 |
+
"""
|
| 985 |
+
Save GBNF grammar and documentation to specified files.
|
| 986 |
+
|
| 987 |
+
Args:
|
| 988 |
+
grammar (str): GBNF grammar string.
|
| 989 |
+
documentation (str): Documentation string.
|
| 990 |
+
grammar_file_path (str): File path to save the GBNF grammar.
|
| 991 |
+
documentation_file_path (str): File path to save the documentation.
|
| 992 |
+
|
| 993 |
+
Returns:
|
| 994 |
+
None
|
| 995 |
+
"""
|
| 996 |
+
try:
|
| 997 |
+
with open(grammar_file_path, "w") as file:
|
| 998 |
+
file.write(grammar + get_primitive_grammar(grammar))
|
| 999 |
+
print(f"Grammar successfully saved to {grammar_file_path}")
|
| 1000 |
+
except IOError as e:
|
| 1001 |
+
print(f"An error occurred while saving the grammar file: {e}")
|
| 1002 |
+
|
| 1003 |
+
try:
|
| 1004 |
+
with open(documentation_file_path, "w") as file:
|
| 1005 |
+
file.write(documentation)
|
| 1006 |
+
print(f"Documentation successfully saved to {documentation_file_path}")
|
| 1007 |
+
except IOError as e:
|
| 1008 |
+
print(f"An error occurred while saving the documentation file: {e}")
|
| 1009 |
+
|
| 1010 |
+
|
| 1011 |
+
def remove_empty_lines(string):
|
| 1012 |
+
"""
|
| 1013 |
+
Remove empty lines from a string.
|
| 1014 |
+
|
| 1015 |
+
Args:
|
| 1016 |
+
string (str): Input string.
|
| 1017 |
+
|
| 1018 |
+
Returns:
|
| 1019 |
+
str: String with empty lines removed.
|
| 1020 |
+
"""
|
| 1021 |
+
lines = string.splitlines()
|
| 1022 |
+
non_empty_lines = [line for line in lines if line.strip() != ""]
|
| 1023 |
+
string_no_empty_lines = "\n".join(non_empty_lines)
|
| 1024 |
+
return string_no_empty_lines
|
| 1025 |
+
|
| 1026 |
+
|
| 1027 |
+
def generate_and_save_gbnf_grammar_and_documentation(
|
| 1028 |
+
pydantic_model_list,
|
| 1029 |
+
grammar_file_path="./generated_grammar.gbnf",
|
| 1030 |
+
documentation_file_path="./generated_grammar_documentation.md",
|
| 1031 |
+
outer_object_name: str | None = None,
|
| 1032 |
+
outer_object_content: str | None = None,
|
| 1033 |
+
model_prefix: str = "Output Model",
|
| 1034 |
+
fields_prefix: str = "Output Fields",
|
| 1035 |
+
list_of_outputs: bool = False,
|
| 1036 |
+
documentation_with_field_description=True,
|
| 1037 |
+
):
|
| 1038 |
+
"""
|
| 1039 |
+
Generate GBNF grammar and documentation, and save them to specified files.
|
| 1040 |
+
|
| 1041 |
+
Args:
|
| 1042 |
+
pydantic_model_list: List of Pydantic model classes.
|
| 1043 |
+
grammar_file_path (str): File path to save the generated GBNF grammar.
|
| 1044 |
+
documentation_file_path (str): File path to save the generated documentation.
|
| 1045 |
+
outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.
|
| 1046 |
+
outer_object_content (str): Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling.
|
| 1047 |
+
model_prefix (str): Prefix for the model section in the documentation.
|
| 1048 |
+
fields_prefix (str): Prefix for the fields section in the documentation.
|
| 1049 |
+
list_of_outputs (bool): Whether the output is a list of items.
|
| 1050 |
+
documentation_with_field_description (bool): Include field descriptions in the documentation.
|
| 1051 |
+
|
| 1052 |
+
Returns:
|
| 1053 |
+
None
|
| 1054 |
+
"""
|
| 1055 |
+
documentation = generate_markdown_documentation(
|
| 1056 |
+
pydantic_model_list, model_prefix, fields_prefix,
|
| 1057 |
+
documentation_with_field_description=documentation_with_field_description
|
| 1058 |
+
)
|
| 1059 |
+
grammar = generate_gbnf_grammar_from_pydantic_models(pydantic_model_list, outer_object_name, outer_object_content,
|
| 1060 |
+
list_of_outputs)
|
| 1061 |
+
grammar = remove_empty_lines(grammar)
|
| 1062 |
+
save_gbnf_grammar_and_documentation(grammar, documentation, grammar_file_path, documentation_file_path)
|
| 1063 |
+
|
| 1064 |
+
|
| 1065 |
+
def generate_gbnf_grammar_and_documentation(
|
| 1066 |
+
pydantic_model_list,
|
| 1067 |
+
outer_object_name: str | None = None,
|
| 1068 |
+
outer_object_content: str | None = None,
|
| 1069 |
+
model_prefix: str = "Output Model",
|
| 1070 |
+
fields_prefix: str = "Output Fields",
|
| 1071 |
+
list_of_outputs: bool = False,
|
| 1072 |
+
documentation_with_field_description=True,
|
| 1073 |
+
):
|
| 1074 |
+
"""
|
| 1075 |
+
Generate GBNF grammar and documentation for a list of Pydantic models.
|
| 1076 |
+
|
| 1077 |
+
Args:
|
| 1078 |
+
pydantic_model_list: List of Pydantic model classes.
|
| 1079 |
+
outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.
|
| 1080 |
+
outer_object_content (str): Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling.
|
| 1081 |
+
model_prefix (str): Prefix for the model section in the documentation.
|
| 1082 |
+
fields_prefix (str): Prefix for the fields section in the documentation.
|
| 1083 |
+
list_of_outputs (bool): Whether the output is a list of items.
|
| 1084 |
+
documentation_with_field_description (bool): Include field descriptions in the documentation.
|
| 1085 |
+
|
| 1086 |
+
Returns:
|
| 1087 |
+
tuple: GBNF grammar string, documentation string.
|
| 1088 |
+
"""
|
| 1089 |
+
documentation = generate_markdown_documentation(
|
| 1090 |
+
copy(pydantic_model_list), model_prefix, fields_prefix,
|
| 1091 |
+
documentation_with_field_description=documentation_with_field_description
|
| 1092 |
+
)
|
| 1093 |
+
grammar = generate_gbnf_grammar_from_pydantic_models(pydantic_model_list, outer_object_name, outer_object_content,
|
| 1094 |
+
list_of_outputs)
|
| 1095 |
+
grammar = remove_empty_lines(grammar + get_primitive_grammar(grammar))
|
| 1096 |
+
return grammar, documentation
|
| 1097 |
+
|
| 1098 |
+
|
| 1099 |
+
def generate_gbnf_grammar_and_documentation_from_dictionaries(
|
| 1100 |
+
dictionaries: list[dict[str, Any]],
|
| 1101 |
+
outer_object_name: str | None = None,
|
| 1102 |
+
outer_object_content: str | None = None,
|
| 1103 |
+
model_prefix: str = "Output Model",
|
| 1104 |
+
fields_prefix: str = "Output Fields",
|
| 1105 |
+
list_of_outputs: bool = False,
|
| 1106 |
+
documentation_with_field_description=True,
|
| 1107 |
+
):
|
| 1108 |
+
"""
|
| 1109 |
+
Generate GBNF grammar and documentation from a list of dictionaries.
|
| 1110 |
+
|
| 1111 |
+
Args:
|
| 1112 |
+
dictionaries (list[dict]): List of dictionaries representing Pydantic models.
|
| 1113 |
+
outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.
|
| 1114 |
+
outer_object_content (str): Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling.
|
| 1115 |
+
model_prefix (str): Prefix for the model section in the documentation.
|
| 1116 |
+
fields_prefix (str): Prefix for the fields section in the documentation.
|
| 1117 |
+
list_of_outputs (bool): Whether the output is a list of items.
|
| 1118 |
+
documentation_with_field_description (bool): Include field descriptions in the documentation.
|
| 1119 |
+
|
| 1120 |
+
Returns:
|
| 1121 |
+
tuple: GBNF grammar string, documentation string.
|
| 1122 |
+
"""
|
| 1123 |
+
pydantic_model_list = create_dynamic_models_from_dictionaries(dictionaries)
|
| 1124 |
+
documentation = generate_markdown_documentation(
|
| 1125 |
+
copy(pydantic_model_list), model_prefix, fields_prefix,
|
| 1126 |
+
documentation_with_field_description=documentation_with_field_description
|
| 1127 |
+
)
|
| 1128 |
+
grammar = generate_gbnf_grammar_from_pydantic_models(pydantic_model_list, outer_object_name, outer_object_content,
|
| 1129 |
+
list_of_outputs)
|
| 1130 |
+
grammar = remove_empty_lines(grammar + get_primitive_grammar(grammar))
|
| 1131 |
+
return grammar, documentation
|
| 1132 |
+
|
| 1133 |
+
|
| 1134 |
+
def create_dynamic_model_from_function(func: Callable[..., Any]):
|
| 1135 |
+
"""
|
| 1136 |
+
Creates a dynamic Pydantic model from a given function's type hints and adds the function as a 'run' method.
|
| 1137 |
+
|
| 1138 |
+
Args:
|
| 1139 |
+
func (Callable): A function with type hints from which to create the model.
|
| 1140 |
+
|
| 1141 |
+
Returns:
|
| 1142 |
+
A dynamic Pydantic model class with the provided function as a 'run' method.
|
| 1143 |
+
"""
|
| 1144 |
+
|
| 1145 |
+
# Get the signature of the function
|
| 1146 |
+
sig = inspect.signature(func)
|
| 1147 |
+
|
| 1148 |
+
# Parse the docstring
|
| 1149 |
+
assert func.__doc__ is not None
|
| 1150 |
+
docstring = parse(func.__doc__)
|
| 1151 |
+
|
| 1152 |
+
dynamic_fields = {}
|
| 1153 |
+
param_docs = []
|
| 1154 |
+
for param in sig.parameters.values():
|
| 1155 |
+
# Exclude 'self' parameter
|
| 1156 |
+
if param.name == "self":
|
| 1157 |
+
continue
|
| 1158 |
+
|
| 1159 |
+
# Assert that the parameter has a type annotation
|
| 1160 |
+
if param.annotation == inspect.Parameter.empty:
|
| 1161 |
+
raise TypeError(f"""Parameter '{param.name}' in function '{getattr(func, "__name__", "")}' lacks a type annotation""")
|
| 1162 |
+
|
| 1163 |
+
# Find the parameter's description in the docstring
|
| 1164 |
+
param_doc = next((d for d in docstring.params if d.arg_name == param.name), None)
|
| 1165 |
+
|
| 1166 |
+
# Assert that the parameter has a description
|
| 1167 |
+
if not param_doc or not param_doc.description:
|
| 1168 |
+
raise ValueError(
|
| 1169 |
+
f"""Parameter '{param.name}' in function '{getattr(func, "__name__", "")}' lacks a description in the docstring""")
|
| 1170 |
+
|
| 1171 |
+
# Add parameter details to the schema
|
| 1172 |
+
param_docs.append((param.name, param_doc))
|
| 1173 |
+
if param.default == inspect.Parameter.empty:
|
| 1174 |
+
default_value = ...
|
| 1175 |
+
else:
|
| 1176 |
+
default_value = param.default
|
| 1177 |
+
dynamic_fields[param.name] = (
|
| 1178 |
+
param.annotation if param.annotation != inspect.Parameter.empty else str, default_value)
|
| 1179 |
+
# Creating the dynamic model
|
| 1180 |
+
dynamic_model = create_model(f"{getattr(func, '__name__')}", **dynamic_fields)
|
| 1181 |
+
|
| 1182 |
+
for name, param_doc in param_docs:
|
| 1183 |
+
dynamic_model.model_fields[name].description = param_doc.description
|
| 1184 |
+
|
| 1185 |
+
dynamic_model.__doc__ = docstring.short_description
|
| 1186 |
+
|
| 1187 |
+
def run_method_wrapper(self):
|
| 1188 |
+
func_args = {name: getattr(self, name) for name, _ in dynamic_fields.items()}
|
| 1189 |
+
return func(**func_args)
|
| 1190 |
+
|
| 1191 |
+
# Adding the wrapped function as a 'run' method
|
| 1192 |
+
setattr(dynamic_model, "run", run_method_wrapper)
|
| 1193 |
+
return dynamic_model
|
| 1194 |
+
|
| 1195 |
+
|
| 1196 |
+
def add_run_method_to_dynamic_model(model: type[BaseModel], func: Callable[..., Any]):
|
| 1197 |
+
"""
|
| 1198 |
+
Add a 'run' method to a dynamic Pydantic model, using the provided function.
|
| 1199 |
+
|
| 1200 |
+
Args:
|
| 1201 |
+
model (type[BaseModel]): Dynamic Pydantic model class.
|
| 1202 |
+
func (Callable): Function to be added as a 'run' method to the model.
|
| 1203 |
+
|
| 1204 |
+
Returns:
|
| 1205 |
+
type[BaseModel]: Pydantic model class with the added 'run' method.
|
| 1206 |
+
"""
|
| 1207 |
+
|
| 1208 |
+
def run_method_wrapper(self):
|
| 1209 |
+
func_args = {name: getattr(self, name) for name in model.model_fields}
|
| 1210 |
+
return func(**func_args)
|
| 1211 |
+
|
| 1212 |
+
# Adding the wrapped function as a 'run' method
|
| 1213 |
+
setattr(model, "run", run_method_wrapper)
|
| 1214 |
+
|
| 1215 |
+
return model
|
| 1216 |
+
|
| 1217 |
+
|
| 1218 |
+
def create_dynamic_models_from_dictionaries(dictionaries: list[dict[str, Any]]):
|
| 1219 |
+
"""
|
| 1220 |
+
Create a list of dynamic Pydantic model classes from a list of dictionaries.
|
| 1221 |
+
|
| 1222 |
+
Args:
|
| 1223 |
+
dictionaries (list[dict]): List of dictionaries representing model structures.
|
| 1224 |
+
|
| 1225 |
+
Returns:
|
| 1226 |
+
list[type[BaseModel]]: List of generated dynamic Pydantic model classes.
|
| 1227 |
+
"""
|
| 1228 |
+
dynamic_models = []
|
| 1229 |
+
for func in dictionaries:
|
| 1230 |
+
model_name = format_model_and_field_name(func.get("name", ""))
|
| 1231 |
+
dyn_model = convert_dictionary_to_pydantic_model(func, model_name)
|
| 1232 |
+
dynamic_models.append(dyn_model)
|
| 1233 |
+
return dynamic_models
|
| 1234 |
+
|
| 1235 |
+
|
| 1236 |
+
def map_grammar_names_to_pydantic_model_class(pydantic_model_list):
|
| 1237 |
+
output = {}
|
| 1238 |
+
for model in pydantic_model_list:
|
| 1239 |
+
output[format_model_and_field_name(model.__name__)] = model
|
| 1240 |
+
|
| 1241 |
+
return output
|
| 1242 |
+
|
| 1243 |
+
|
| 1244 |
+
def json_schema_to_python_types(schema):
|
| 1245 |
+
type_map = {
|
| 1246 |
+
"any": Any,
|
| 1247 |
+
"string": str,
|
| 1248 |
+
"number": float,
|
| 1249 |
+
"integer": int,
|
| 1250 |
+
"boolean": bool,
|
| 1251 |
+
"array": list,
|
| 1252 |
+
}
|
| 1253 |
+
return type_map[schema]
|
| 1254 |
+
|
| 1255 |
+
|
| 1256 |
+
def list_to_enum(enum_name, values):
|
| 1257 |
+
return Enum(enum_name, {value: value for value in values})
|
| 1258 |
+
|
| 1259 |
+
|
| 1260 |
+
def convert_dictionary_to_pydantic_model(dictionary: dict[str, Any], model_name: str = "CustomModel") -> type[Any]:
|
| 1261 |
+
"""
|
| 1262 |
+
Convert a dictionary to a Pydantic model class.
|
| 1263 |
+
|
| 1264 |
+
Args:
|
| 1265 |
+
dictionary (dict): Dictionary representing the model structure.
|
| 1266 |
+
model_name (str): Name of the generated Pydantic model.
|
| 1267 |
+
|
| 1268 |
+
Returns:
|
| 1269 |
+
type[BaseModel]: Generated Pydantic model class.
|
| 1270 |
+
"""
|
| 1271 |
+
fields: dict[str, Any] = {}
|
| 1272 |
+
|
| 1273 |
+
if "properties" in dictionary:
|
| 1274 |
+
for field_name, field_data in dictionary.get("properties", {}).items():
|
| 1275 |
+
if field_data == "object":
|
| 1276 |
+
submodel = convert_dictionary_to_pydantic_model(dictionary, f"{model_name}_{field_name}")
|
| 1277 |
+
fields[field_name] = (submodel, ...)
|
| 1278 |
+
else:
|
| 1279 |
+
field_type = field_data.get("type", "str")
|
| 1280 |
+
|
| 1281 |
+
if field_data.get("enum", []):
|
| 1282 |
+
fields[field_name] = (list_to_enum(field_name, field_data.get("enum", [])), ...)
|
| 1283 |
+
elif field_type == "array":
|
| 1284 |
+
items = field_data.get("items", {})
|
| 1285 |
+
if items != {}:
|
| 1286 |
+
array = {"properties": items}
|
| 1287 |
+
array_type = convert_dictionary_to_pydantic_model(array, f"{model_name}_{field_name}_items")
|
| 1288 |
+
fields[field_name] = (list[array_type], ...) # ty: ignore[invalid-type-form]
|
| 1289 |
+
else:
|
| 1290 |
+
fields[field_name] = (list, ...)
|
| 1291 |
+
elif field_type == "object":
|
| 1292 |
+
submodel = convert_dictionary_to_pydantic_model(field_data, f"{model_name}_{field_name}")
|
| 1293 |
+
fields[field_name] = (submodel, ...)
|
| 1294 |
+
elif field_type == "required":
|
| 1295 |
+
required = field_data.get("enum", [])
|
| 1296 |
+
for key, field in fields.items():
|
| 1297 |
+
if key not in required:
|
| 1298 |
+
optional_type = fields[key][0]
|
| 1299 |
+
fields[key] = (Optional[optional_type], ...)
|
| 1300 |
+
else:
|
| 1301 |
+
field_type = json_schema_to_python_types(field_type)
|
| 1302 |
+
fields[field_name] = (field_type, ...)
|
| 1303 |
+
if "function" in dictionary:
|
| 1304 |
+
for field_name, field_data in dictionary.get("function", {}).items():
|
| 1305 |
+
if field_name == "name":
|
| 1306 |
+
model_name = field_data
|
| 1307 |
+
elif field_name == "description":
|
| 1308 |
+
fields["__doc__"] = field_data
|
| 1309 |
+
elif field_name == "parameters":
|
| 1310 |
+
return convert_dictionary_to_pydantic_model(field_data, f"{model_name}")
|
| 1311 |
+
|
| 1312 |
+
if "parameters" in dictionary:
|
| 1313 |
+
field_data = {"function": dictionary}
|
| 1314 |
+
return convert_dictionary_to_pydantic_model(field_data, f"{model_name}")
|
| 1315 |
+
if "required" in dictionary:
|
| 1316 |
+
required = dictionary.get("required", [])
|
| 1317 |
+
for key, field in fields.items():
|
| 1318 |
+
if key not in required:
|
| 1319 |
+
optional_type = fields[key][0]
|
| 1320 |
+
fields[key] = (Optional[optional_type], ...)
|
| 1321 |
+
custom_model = create_model(model_name, **fields)
|
| 1322 |
+
return custom_model
|
backend/llama.cpp/examples/pydantic_models_to_grammar_examples.py
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
"""Function calling example using pydantic models."""
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
import argparse
|
| 8 |
+
import datetime
|
| 9 |
+
import json
|
| 10 |
+
import logging
|
| 11 |
+
import textwrap
|
| 12 |
+
import sys
|
| 13 |
+
from enum import Enum
|
| 14 |
+
from typing import Optional, Union
|
| 15 |
+
|
| 16 |
+
import requests
|
| 17 |
+
from pydantic import BaseModel, Field
|
| 18 |
+
from pydantic_models_to_grammar import (add_run_method_to_dynamic_model, convert_dictionary_to_pydantic_model,
|
| 19 |
+
create_dynamic_model_from_function, generate_gbnf_grammar_and_documentation)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def create_completion(host, prompt, gbnf_grammar):
|
| 23 |
+
"""Calls the /completion API on llama-server.
|
| 24 |
+
|
| 25 |
+
See
|
| 26 |
+
https://github.com/ggml-org/llama.cpp/tree/HEAD/tools/server#api-endpoints
|
| 27 |
+
"""
|
| 28 |
+
print(f" Request:\n Grammar:\n{textwrap.indent(gbnf_grammar, ' ')}\n Prompt:\n{textwrap.indent(prompt.rstrip(), ' ')}")
|
| 29 |
+
headers = {"Content-Type": "application/json"}
|
| 30 |
+
data = {"prompt": prompt, "grammar": gbnf_grammar}
|
| 31 |
+
result = requests.post(f"http://{host}/completion", headers=headers, json=data).json()
|
| 32 |
+
assert data.get("error") is None, data
|
| 33 |
+
logging.info("Result: %s", result)
|
| 34 |
+
content = result["content"]
|
| 35 |
+
print(f" Model: {result['model']}")
|
| 36 |
+
print(f" Result:\n{textwrap.indent(json.dumps(json.loads(content), indent=2), ' ')}")
|
| 37 |
+
return content
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# A function for the agent to send a message to the user.
|
| 41 |
+
class SendMessageToUser(BaseModel):
|
| 42 |
+
"""Send a message to the User."""
|
| 43 |
+
chain_of_thought: str = Field(..., description="Your chain of thought while sending the message.")
|
| 44 |
+
message: str = Field(..., description="Message you want to send to the user.")
|
| 45 |
+
|
| 46 |
+
def run(self):
|
| 47 |
+
print(f"SendMessageToUser: {self.message}")
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def example_rce(host):
|
| 51 |
+
"""Minimal test case where the LLM call an arbitrary python function."""
|
| 52 |
+
print("- example_rce")
|
| 53 |
+
tools = [SendMessageToUser]
|
| 54 |
+
gbnf_grammar, documentation = generate_gbnf_grammar_and_documentation(
|
| 55 |
+
pydantic_model_list=tools, outer_object_name="function",
|
| 56 |
+
outer_object_content="function_parameters", model_prefix="Function", fields_prefix="Parameters")
|
| 57 |
+
system_message = "You are an advanced AI, tasked to assist the user by calling functions in JSON format. The following are the available functions and their parameters and types:\n\n" + documentation
|
| 58 |
+
user_message = "What is 42 * 42?"
|
| 59 |
+
prompt = f"<|im_start|>system\n{system_message}<|im_end|>\n<|im_start|>user\n{user_message}<|im_end|>\n<|im_start|>assistant"
|
| 60 |
+
text = create_completion(host, prompt, gbnf_grammar)
|
| 61 |
+
json_data = json.loads(text)
|
| 62 |
+
tools_map = {tool.__name__:tool for tool in tools}
|
| 63 |
+
# This finds "SendMessageToUser":
|
| 64 |
+
tool = tools_map.get(json_data["function"])
|
| 65 |
+
if not tool:
|
| 66 |
+
print(f"Error: unknown tool {json_data['function']}")
|
| 67 |
+
return 1
|
| 68 |
+
tool(**json_data["function_parameters"]).run()
|
| 69 |
+
return 0
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
# Enum for the calculator tool.
|
| 73 |
+
class MathOperation(Enum):
|
| 74 |
+
ADD = "add"
|
| 75 |
+
SUBTRACT = "subtract"
|
| 76 |
+
MULTIPLY = "multiply"
|
| 77 |
+
DIVIDE = "divide"
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
# Simple pydantic calculator tool for the agent that can add, subtract,
|
| 81 |
+
# multiply, and divide. Docstring and description of fields will be used in
|
| 82 |
+
# system prompt.
|
| 83 |
+
class Calculator(BaseModel):
|
| 84 |
+
"""Perform a math operation on two numbers."""
|
| 85 |
+
number_one: Union[int, float] = Field(..., description="First number.")
|
| 86 |
+
operation: MathOperation = Field(..., description="Math operation to perform.")
|
| 87 |
+
number_two: Union[int, float] = Field(..., description="Second number.")
|
| 88 |
+
|
| 89 |
+
def run(self):
|
| 90 |
+
if self.operation == MathOperation.ADD:
|
| 91 |
+
return self.number_one + self.number_two
|
| 92 |
+
elif self.operation == MathOperation.SUBTRACT:
|
| 93 |
+
return self.number_one - self.number_two
|
| 94 |
+
elif self.operation == MathOperation.MULTIPLY:
|
| 95 |
+
return self.number_one * self.number_two
|
| 96 |
+
elif self.operation == MathOperation.DIVIDE:
|
| 97 |
+
return self.number_one / self.number_two
|
| 98 |
+
else:
|
| 99 |
+
raise ValueError("Unknown operation.")
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def example_calculator(host):
|
| 103 |
+
"""Have the LLM ask to get a calculation done.
|
| 104 |
+
|
| 105 |
+
Here the grammar gets generated by passing the available function models to
|
| 106 |
+
generate_gbnf_grammar_and_documentation function. This also generates a
|
| 107 |
+
documentation usable by the LLM.
|
| 108 |
+
|
| 109 |
+
pydantic_model_list is the list of pydantic models outer_object_name is an
|
| 110 |
+
optional name for an outer object around the actual model object. Like a
|
| 111 |
+
"function" object with "function_parameters" which contains the actual model
|
| 112 |
+
object. If None, no outer object will be generated outer_object_content is
|
| 113 |
+
the name of outer object content.
|
| 114 |
+
|
| 115 |
+
model_prefix is the optional prefix for models in the documentation. (Default="Output Model")
|
| 116 |
+
fields_prefix is the prefix for the model fields in the documentation. (Default="Output Fields")
|
| 117 |
+
"""
|
| 118 |
+
print("- example_calculator")
|
| 119 |
+
tools = [SendMessageToUser, Calculator]
|
| 120 |
+
gbnf_grammar, documentation = generate_gbnf_grammar_and_documentation(
|
| 121 |
+
pydantic_model_list=tools, outer_object_name="function",
|
| 122 |
+
outer_object_content="function_parameters", model_prefix="Function", fields_prefix="Parameters")
|
| 123 |
+
system_message = "You are an advanced AI, tasked to assist the user by calling functions in JSON format. The following are the available functions and their parameters and types:\n\n" + documentation
|
| 124 |
+
user_message1 = "What is 42 * 42?"
|
| 125 |
+
prompt = f"<|im_start|>system\n{system_message}<|im_end|>\n<|im_start|>user\n{user_message1}<|im_end|>\n<|im_start|>assistant"
|
| 126 |
+
text = create_completion(host, prompt, gbnf_grammar)
|
| 127 |
+
json_data = json.loads(text)
|
| 128 |
+
expected = {
|
| 129 |
+
"function": "Calculator",
|
| 130 |
+
"function_parameters": {
|
| 131 |
+
"number_one": 42,
|
| 132 |
+
"operation": "multiply",
|
| 133 |
+
"number_two": 42
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
if json_data != expected:
|
| 137 |
+
print(" Result is not as expected!")
|
| 138 |
+
tools_map = {tool.__name__:tool for tool in tools}
|
| 139 |
+
# This finds "Calculator":
|
| 140 |
+
tool = tools_map.get(json_data["function"])
|
| 141 |
+
if not tool:
|
| 142 |
+
print(f"Error: unknown tool {json_data['function']}")
|
| 143 |
+
return 1
|
| 144 |
+
result = tool(**json_data["function_parameters"]).run()
|
| 145 |
+
print(f" Call {json_data['function']} gave result {result}")
|
| 146 |
+
return 0
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
class Category(Enum):
|
| 150 |
+
"""The category of the book."""
|
| 151 |
+
Fiction = "Fiction"
|
| 152 |
+
NonFiction = "Non-Fiction"
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
class Book(BaseModel):
|
| 156 |
+
"""Represents an entry about a book."""
|
| 157 |
+
title: str = Field(..., description="Title of the book.")
|
| 158 |
+
author: str = Field(..., description="Author of the book.")
|
| 159 |
+
published_year: Optional[int] = Field(..., description="Publishing year of the book.")
|
| 160 |
+
keywords: list[str] = Field(..., description="A list of keywords.")
|
| 161 |
+
category: Category = Field(..., description="Category of the book.")
|
| 162 |
+
summary: str = Field(..., description="Summary of the book.")
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def example_struct(host):
|
| 166 |
+
"""A example structured output based on pydantic models.
|
| 167 |
+
|
| 168 |
+
The LLM will create an entry for a Book database out of an unstructured
|
| 169 |
+
text. We need no additional parameters other than our list of pydantic
|
| 170 |
+
models.
|
| 171 |
+
"""
|
| 172 |
+
print("- example_struct")
|
| 173 |
+
tools = [Book]
|
| 174 |
+
gbnf_grammar, documentation = generate_gbnf_grammar_and_documentation(pydantic_model_list=tools)
|
| 175 |
+
system_message = "You are an advanced AI, tasked to create a dataset entry in JSON for a Book. The following is the expected output model:\n\n" + documentation
|
| 176 |
+
text = """The Feynman Lectures on Physics is a physics textbook based on some lectures by Richard Feynman, a Nobel laureate who has sometimes been called "The Great Explainer". The lectures were presented before undergraduate students at the California Institute of Technology (Caltech), during 1961–1963. The book's co-authors are Feynman, Robert B. Leighton, and Matthew Sands."""
|
| 177 |
+
prompt = f"<|im_start|>system\n{system_message}<|im_end|>\n<|im_start|>user\n{text}<|im_end|>\n<|im_start|>assistant"
|
| 178 |
+
text = create_completion(host, prompt, gbnf_grammar)
|
| 179 |
+
json_data = json.loads(text)
|
| 180 |
+
# In this case, there's no function nor function_parameters.
|
| 181 |
+
# Here the result will vary based on the LLM used.
|
| 182 |
+
keys = sorted(["title", "author", "published_year", "keywords", "category", "summary"])
|
| 183 |
+
if keys != sorted(json_data.keys()):
|
| 184 |
+
print(f"Unexpected result: {sorted(json_data.keys())}")
|
| 185 |
+
return 1
|
| 186 |
+
book = Book(**json_data)
|
| 187 |
+
print(f" As a Book object: %s" % book)
|
| 188 |
+
return 0
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def get_current_datetime(output_format: Optional[str] = None):
|
| 192 |
+
"""Get the current date and time in the given format.
|
| 193 |
+
|
| 194 |
+
Args:
|
| 195 |
+
output_format: formatting string for the date and time, defaults to '%Y-%m-%d %H:%M:%S'
|
| 196 |
+
"""
|
| 197 |
+
return datetime.datetime.now().strftime(output_format or "%Y-%m-%d %H:%M:%S")
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
# Example function to get the weather.
|
| 201 |
+
def get_current_weather(location, unit):
|
| 202 |
+
"""Get the current weather in a given location"""
|
| 203 |
+
if "London" in location:
|
| 204 |
+
return json.dumps({"location": "London", "temperature": "42", "unit": unit.value})
|
| 205 |
+
elif "New York" in location:
|
| 206 |
+
return json.dumps({"location": "New York", "temperature": "24", "unit": unit.value})
|
| 207 |
+
elif "North Pole" in location:
|
| 208 |
+
return json.dumps({"location": "North Pole", "temperature": "-42", "unit": unit.value})
|
| 209 |
+
return json.dumps({"location": location, "temperature": "unknown"})
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def example_concurrent(host):
|
| 213 |
+
"""An example for parallel function calling with a Python function, a pydantic
|
| 214 |
+
function model and an OpenAI like function definition.
|
| 215 |
+
"""
|
| 216 |
+
print("- example_concurrent")
|
| 217 |
+
# Function definition in OpenAI style.
|
| 218 |
+
current_weather_tool = {
|
| 219 |
+
"type": "function",
|
| 220 |
+
"function": {
|
| 221 |
+
"name": "get_current_weather",
|
| 222 |
+
"description": "Get the current weather in a given location",
|
| 223 |
+
"parameters": {
|
| 224 |
+
"type": "object",
|
| 225 |
+
"properties": {
|
| 226 |
+
"location": {
|
| 227 |
+
"type": "string",
|
| 228 |
+
"description": "The city and state, e.g. San Francisco, CA",
|
| 229 |
+
},
|
| 230 |
+
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
|
| 231 |
+
},
|
| 232 |
+
"required": ["location"],
|
| 233 |
+
},
|
| 234 |
+
},
|
| 235 |
+
}
|
| 236 |
+
# Convert OpenAI function definition into pydantic model.
|
| 237 |
+
current_weather_tool_model = convert_dictionary_to_pydantic_model(current_weather_tool)
|
| 238 |
+
# Add the actual function to a pydantic model.
|
| 239 |
+
current_weather_tool_model = add_run_method_to_dynamic_model(current_weather_tool_model, get_current_weather)
|
| 240 |
+
|
| 241 |
+
# Convert normal Python function to a pydantic model.
|
| 242 |
+
current_datetime_model = create_dynamic_model_from_function(get_current_datetime)
|
| 243 |
+
|
| 244 |
+
tools = [SendMessageToUser, Calculator, current_datetime_model, current_weather_tool_model]
|
| 245 |
+
gbnf_grammar, documentation = generate_gbnf_grammar_and_documentation(
|
| 246 |
+
pydantic_model_list=tools, outer_object_name="function",
|
| 247 |
+
outer_object_content="params", model_prefix="Function", fields_prefix="Parameters", list_of_outputs=True)
|
| 248 |
+
system_message = "You are an advanced AI assistant. You are interacting with the user and with your environment by calling functions. You call functions by writing JSON objects, which represent specific function calls.\nBelow is a list of your available function calls:\n\n" + documentation
|
| 249 |
+
text = """Get the date and time, get the current weather in celsius in London and solve the following calculation: 42 * 42"""
|
| 250 |
+
prompt = f"<|im_start|>system\n{system_message}<|im_end|>\n<|im_start|>user\n{text}<|im_end|>\n<|im_start|>assistant"
|
| 251 |
+
text = create_completion(host, prompt, gbnf_grammar)
|
| 252 |
+
json_data = json.loads(text)
|
| 253 |
+
expected = [
|
| 254 |
+
{
|
| 255 |
+
"function": "get_current_datetime",
|
| 256 |
+
"params": {
|
| 257 |
+
"output_format": "%Y-%m-%d %H:%M:%S"
|
| 258 |
+
}
|
| 259 |
+
},
|
| 260 |
+
{
|
| 261 |
+
"function": "get_current_weather",
|
| 262 |
+
"params": {
|
| 263 |
+
"location": "London",
|
| 264 |
+
"unit": "celsius"
|
| 265 |
+
}
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"function": "Calculator",
|
| 269 |
+
"params": {
|
| 270 |
+
"number_one": 42,
|
| 271 |
+
"operation": "multiply",
|
| 272 |
+
"number_two": 42
|
| 273 |
+
}
|
| 274 |
+
}
|
| 275 |
+
]
|
| 276 |
+
res = 0
|
| 277 |
+
if json_data != expected:
|
| 278 |
+
print(" Result is not as expected!")
|
| 279 |
+
print(" This can happen on highly quantized models")
|
| 280 |
+
res = 1
|
| 281 |
+
tools_map = {tool.__name__:tool for tool in tools}
|
| 282 |
+
for call in json_data:
|
| 283 |
+
tool = tools_map.get(call["function"])
|
| 284 |
+
if not tool:
|
| 285 |
+
print(f"Error: unknown tool {call['function']}")
|
| 286 |
+
return 1
|
| 287 |
+
result = tool(**call["params"]).run()
|
| 288 |
+
print(f" Call {call['function']} returned {result}")
|
| 289 |
+
# Should output something like this:
|
| 290 |
+
# Call get_current_datetime returned 2024-07-15 09:50:38
|
| 291 |
+
# Call get_current_weather returned {"location": "London", "temperature": "42", "unit": "celsius"}
|
| 292 |
+
# Call Calculator returned 1764
|
| 293 |
+
return res
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def main():
|
| 297 |
+
parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__)
|
| 298 |
+
parser.add_argument("--host", default="localhost:8080", help="llama.cpp server")
|
| 299 |
+
parser.add_argument("-v", "--verbose", action="store_true", help="enables logging")
|
| 300 |
+
args = parser.parse_args()
|
| 301 |
+
logging.basicConfig(level=logging.INFO if args.verbose else logging.ERROR)
|
| 302 |
+
ret = 0
|
| 303 |
+
# Comment out below to only run the example you want.
|
| 304 |
+
ret = ret or example_rce(args.host)
|
| 305 |
+
ret = ret or example_calculator(args.host)
|
| 306 |
+
ret = ret or example_struct(args.host)
|
| 307 |
+
ret = ret or example_concurrent(args.host)
|
| 308 |
+
return ret
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
if __name__ == "__main__":
|
| 312 |
+
sys.exit(main())
|
backend/llama.cpp/examples/reason-act.sh
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
cd `dirname $0`
|
| 4 |
+
cd ..
|
| 5 |
+
|
| 6 |
+
# get -m model parameter otherwise defer to default
|
| 7 |
+
if [ "$1" == "-m" ]; then
|
| 8 |
+
MODEL="-m $2 "
|
| 9 |
+
fi
|
| 10 |
+
|
| 11 |
+
./llama-cli $MODEL --color \
|
| 12 |
+
-f ./prompts/reason-act.txt \
|
| 13 |
+
-i --interactive-first \
|
| 14 |
+
--top_k 10000 --temp 0.2 --repeat_penalty 1 -t 7 -c 2048 \
|
| 15 |
+
-r "Question:" -r "Observation:" --in-prefix " " \
|
| 16 |
+
-n -1
|
backend/llama.cpp/examples/regex_to_grammar.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json, subprocess, sys, os
|
| 2 |
+
|
| 3 |
+
assert len(sys.argv) >= 2
|
| 4 |
+
[_, pattern, *rest] = sys.argv
|
| 5 |
+
|
| 6 |
+
print(subprocess.check_output(
|
| 7 |
+
[
|
| 8 |
+
"python",
|
| 9 |
+
os.path.join(
|
| 10 |
+
os.path.dirname(os.path.realpath(__file__)),
|
| 11 |
+
"json_schema_to_grammar.py"),
|
| 12 |
+
*rest,
|
| 13 |
+
"-",
|
| 14 |
+
"--raw-pattern",
|
| 15 |
+
],
|
| 16 |
+
text=True,
|
| 17 |
+
input=json.dumps({
|
| 18 |
+
"type": "string",
|
| 19 |
+
"pattern": pattern,
|
| 20 |
+
}, indent=2)))
|
backend/llama.cpp/examples/retrieval/CMakeLists.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
set(TARGET llama-retrieval)
|
| 2 |
+
add_executable(${TARGET} retrieval.cpp)
|
| 3 |
+
install(TARGETS ${TARGET} RUNTIME)
|
| 4 |
+
target_link_libraries(${TARGET} PRIVATE llama-common llama ${CMAKE_THREAD_LIBS_INIT})
|
| 5 |
+
target_compile_features(${TARGET} PRIVATE cxx_std_17)
|
backend/llama.cpp/examples/retrieval/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# llama.cpp/examples/retrieval
|
| 2 |
+
|
| 3 |
+
Demonstration of simple retrieval technique based on cosine similarity
|
| 4 |
+
|
| 5 |
+
More info:
|
| 6 |
+
https://github.com/ggml-org/llama.cpp/pull/6193
|
| 7 |
+
|
| 8 |
+
### How to use
|
| 9 |
+
|
| 10 |
+
`retieval.cpp` has parameters of its own:
|
| 11 |
+
- `--context-file`: file to be embedded - state this option multiple times to embed multiple files
|
| 12 |
+
- `--chunk-size`: minimum size of each text chunk to be embedded
|
| 13 |
+
- `--chunk-separator`: STRING to divide chunks by. newline by default
|
| 14 |
+
|
| 15 |
+
`retrieval` example can be tested as follows:
|
| 16 |
+
|
| 17 |
+
```bash
|
| 18 |
+
llama-retrieval --model ./models/bge-base-en-v1.5-f16.gguf --top-k 3 --context-file README.md --context-file License --chunk-size 100 --chunk-separator .
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
This chunks and embeds all given files and starts a loop requesting query inputs:
|
| 22 |
+
|
| 23 |
+
```
|
| 24 |
+
Enter query:
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
On each query input, top k chunks are shown along with file name, chunk position within file and original text:
|
| 28 |
+
|
| 29 |
+
```
|
| 30 |
+
Enter query: describe the mit license
|
| 31 |
+
batch_decode: n_tokens = 6, n_seq = 1
|
| 32 |
+
Top 3 similar chunks:
|
| 33 |
+
filename: README.md
|
| 34 |
+
filepos: 119
|
| 35 |
+
similarity: 0.762334
|
| 36 |
+
textdata:
|
| 37 |
+
png)
|
| 38 |
+
|
| 39 |
+
[](https://opensource.org/licenses/MIT)
|
| 40 |
+
|
| 41 |
+
[Roadmap](https://github.
|
| 42 |
+
--------------------
|
| 43 |
+
filename: License
|
| 44 |
+
filepos: 0
|
| 45 |
+
similarity: 0.725146
|
| 46 |
+
textdata:
|
| 47 |
+
MIT License
|
| 48 |
+
|
| 49 |
+
Copyright (c) 2023 Georgi Gerganov
|
| 50 |
+
|
| 51 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 52 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 53 |
+
in the Software without restriction, including without limitation the rights
|
| 54 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 55 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 56 |
+
furnished to do so, subject to the following conditions:
|
| 57 |
+
|
| 58 |
+
The above copyright notice and this permission notice shall be included in all
|
| 59 |
+
copies or substantial portions of the Software.
|
| 60 |
+
--------------------
|
| 61 |
+
filename: README.md
|
| 62 |
+
filepos: 9178
|
| 63 |
+
similarity: 0.621722
|
| 64 |
+
textdata:
|
| 65 |
+
com/cztomsik/ava) (MIT)
|
| 66 |
+
- [ptsochantaris/emeltal](https://github.com/ptsochantaris/emeltal)
|
| 67 |
+
- [pythops/tenere](https://github.
|
| 68 |
+
--------------------
|
| 69 |
+
```
|
backend/llama.cpp/examples/retrieval/retrieval.cpp
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "arg.h"
|
| 2 |
+
#include "common.h"
|
| 3 |
+
#include "log.h"
|
| 4 |
+
#include "llama.h"
|
| 5 |
+
|
| 6 |
+
#include <algorithm>
|
| 7 |
+
#include <clocale>
|
| 8 |
+
#include <fstream>
|
| 9 |
+
#include <iostream> // TODO: remove me
|
| 10 |
+
|
| 11 |
+
static void print_usage(int, char ** argv) {
|
| 12 |
+
LOG("\nexample usage:\n");
|
| 13 |
+
LOG("\n %s --model ./models/bge-base-en-v1.5-f16.gguf --top-k 3 --context-file README.md --context-file License --chunk-size 100 --chunk-separator .\n", argv[0]);
|
| 14 |
+
LOG("\n");
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
struct chunk {
|
| 18 |
+
// filename
|
| 19 |
+
std::string filename;
|
| 20 |
+
// original file position
|
| 21 |
+
size_t filepos;
|
| 22 |
+
// original text data
|
| 23 |
+
std::string textdata;
|
| 24 |
+
// tokenized text data
|
| 25 |
+
std::vector<llama_token> tokens;
|
| 26 |
+
// embedding
|
| 27 |
+
std::vector<float> embedding;
|
| 28 |
+
};
|
| 29 |
+
|
| 30 |
+
// chunk file data to chunks of size >= chunk_size
|
| 31 |
+
// chunk_separator is the separator between chunks
|
| 32 |
+
static std::vector<chunk> chunk_file(const std::string & filename, int chunk_size, const std::string & chunk_separator) {
|
| 33 |
+
std::vector<chunk> chunks;
|
| 34 |
+
std::ifstream f(filename.c_str());
|
| 35 |
+
|
| 36 |
+
if (!f.is_open()) {
|
| 37 |
+
LOG_ERR("could not open file %s\n", filename.c_str());
|
| 38 |
+
return chunks;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
chunk current_chunk;
|
| 42 |
+
char buffer[1024];
|
| 43 |
+
int64_t filepos = 0;
|
| 44 |
+
std::string current;
|
| 45 |
+
while (f.read(buffer, 1024)) {
|
| 46 |
+
current += std::string(buffer, f.gcount());
|
| 47 |
+
size_t pos;
|
| 48 |
+
while ((pos = current.find(chunk_separator)) != std::string::npos) {
|
| 49 |
+
current_chunk.textdata += current.substr(0, pos + chunk_separator.size());
|
| 50 |
+
if ((int) current_chunk.textdata.size() > chunk_size) {
|
| 51 |
+
// save chunk
|
| 52 |
+
current_chunk.filepos = filepos;
|
| 53 |
+
current_chunk.filename = filename;
|
| 54 |
+
chunks.push_back(current_chunk);
|
| 55 |
+
// update filepos
|
| 56 |
+
filepos += (int) current_chunk.textdata.size();
|
| 57 |
+
// reset current_chunk
|
| 58 |
+
current_chunk = chunk();
|
| 59 |
+
}
|
| 60 |
+
current = current.substr(pos + chunk_separator.size());
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
}
|
| 64 |
+
// add leftover data to last chunk
|
| 65 |
+
if (current_chunk.textdata.size() > 0) {
|
| 66 |
+
if (chunks.empty()) {
|
| 67 |
+
current_chunk.filepos = filepos;
|
| 68 |
+
current_chunk.filename = filename;
|
| 69 |
+
chunks.push_back(current_chunk);
|
| 70 |
+
} else {
|
| 71 |
+
chunks.back().textdata += current_chunk.textdata;
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
f.close();
|
| 75 |
+
return chunks;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
static void batch_add_seq(llama_batch & batch, const std::vector<int32_t> & tokens, llama_seq_id seq_id) {
|
| 79 |
+
size_t n_tokens = tokens.size();
|
| 80 |
+
for (size_t i = 0; i < n_tokens; i++) {
|
| 81 |
+
common_batch_add(batch, tokens[i], i, { seq_id }, true);
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
static void batch_process(llama_context * ctx, llama_batch & batch, float * output, int n_seq, int n_embd) {
|
| 86 |
+
// clear previous kv_cache values (irrelevant for embeddings)
|
| 87 |
+
llama_memory_clear(llama_get_memory(ctx), false);
|
| 88 |
+
|
| 89 |
+
// run model
|
| 90 |
+
LOG_INF("%s: n_tokens = %d, n_seq = %d\n", __func__, batch.n_tokens, n_seq);
|
| 91 |
+
if (llama_decode(ctx, batch) < 0) {
|
| 92 |
+
LOG_ERR("%s : failed to process\n", __func__);
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
for (int i = 0; i < batch.n_tokens; i++) {
|
| 96 |
+
if (!batch.logits[i]) {
|
| 97 |
+
continue;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
// try to get sequence embeddings - supported only when pooling_type is not NONE
|
| 101 |
+
const float * embd = llama_get_embeddings_seq(ctx, batch.seq_id[i][0]);
|
| 102 |
+
if (embd == NULL) {
|
| 103 |
+
embd = llama_get_embeddings_ith(ctx, i);
|
| 104 |
+
if (embd == NULL) {
|
| 105 |
+
LOG_ERR("%s: failed to get embeddings for token %d\n", __func__, i);
|
| 106 |
+
continue;
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
float * out = output + batch.seq_id[i][0] * n_embd;
|
| 111 |
+
common_embd_normalize(embd, out, n_embd, 2);
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
int main(int argc, char ** argv) {
|
| 116 |
+
std::setlocale(LC_NUMERIC, "C");
|
| 117 |
+
|
| 118 |
+
common_params params;
|
| 119 |
+
|
| 120 |
+
common_init();
|
| 121 |
+
|
| 122 |
+
if (!common_params_parse(argc, argv, params, LLAMA_EXAMPLE_RETRIEVAL, print_usage)) {
|
| 123 |
+
return 1;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
// For BERT models, batch size must be equal to ubatch size
|
| 127 |
+
params.n_ubatch = params.n_batch;
|
| 128 |
+
params.embedding = true;
|
| 129 |
+
|
| 130 |
+
if (params.chunk_size <= 0) {
|
| 131 |
+
LOG_ERR("chunk_size must be positive\n");
|
| 132 |
+
return 1;
|
| 133 |
+
}
|
| 134 |
+
if (params.context_files.empty()) {
|
| 135 |
+
LOG_ERR("context_files must be specified\n");
|
| 136 |
+
return 1;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
LOG_INF("processing files:\n");
|
| 140 |
+
for (auto & context_file : params.context_files) {
|
| 141 |
+
LOG_INF("%s\n", context_file.c_str());
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
std::vector<chunk> chunks;
|
| 145 |
+
for (auto & context_file : params.context_files) {
|
| 146 |
+
std::vector<chunk> file_chunk = chunk_file(context_file, params.chunk_size, params.chunk_separator);
|
| 147 |
+
chunks.insert(chunks.end(), file_chunk.begin(), file_chunk.end());
|
| 148 |
+
}
|
| 149 |
+
LOG_INF("Number of chunks: %zu\n", chunks.size());
|
| 150 |
+
|
| 151 |
+
llama_backend_init();
|
| 152 |
+
llama_numa_init(params.numa);
|
| 153 |
+
|
| 154 |
+
// load the model
|
| 155 |
+
auto llama_init = common_init_from_params(params);
|
| 156 |
+
|
| 157 |
+
auto * model = llama_init->model();
|
| 158 |
+
auto * ctx = llama_init->context();
|
| 159 |
+
|
| 160 |
+
if (model == NULL) {
|
| 161 |
+
LOG_ERR("%s: unable to load model\n", __func__);
|
| 162 |
+
return 1;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
const llama_vocab * vocab = llama_model_get_vocab(model);
|
| 166 |
+
|
| 167 |
+
const int n_ctx_train = llama_model_n_ctx_train(model);
|
| 168 |
+
const int n_ctx = llama_n_ctx(ctx);
|
| 169 |
+
|
| 170 |
+
const enum llama_pooling_type pooling_type = llama_pooling_type(ctx);
|
| 171 |
+
if (pooling_type == LLAMA_POOLING_TYPE_NONE) {
|
| 172 |
+
LOG_ERR("%s: pooling type NONE not supported\n", __func__);
|
| 173 |
+
return 1;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
if (n_ctx > n_ctx_train) {
|
| 177 |
+
LOG_WRN("%s: warning: model was trained on only %d context tokens (%d specified)\n",
|
| 178 |
+
__func__, n_ctx_train, n_ctx);
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
// print system information
|
| 182 |
+
{
|
| 183 |
+
LOG_INF("\n");
|
| 184 |
+
LOG_INF("%s\n", common_params_get_system_info(params).c_str());
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
// max batch size
|
| 188 |
+
const uint64_t n_batch = params.n_batch;
|
| 189 |
+
GGML_ASSERT(params.n_batch >= params.n_ctx);
|
| 190 |
+
|
| 191 |
+
// tokenize the prompts and trim
|
| 192 |
+
for (auto & chunk : chunks) {
|
| 193 |
+
auto inp = common_tokenize(ctx, chunk.textdata, true, false);
|
| 194 |
+
if (inp.size() > n_batch) {
|
| 195 |
+
LOG_ERR("%s: chunk size (%lld) exceeds batch size (%lld), increase batch size and re-run\n",
|
| 196 |
+
__func__, (long long int) inp.size(), (long long int) n_batch);
|
| 197 |
+
return 1;
|
| 198 |
+
}
|
| 199 |
+
// add eos if not present
|
| 200 |
+
if (llama_vocab_eos(vocab) >= 0 && (inp.empty() || inp.back() != llama_vocab_eos(vocab))) {
|
| 201 |
+
inp.push_back(llama_vocab_eos(vocab));
|
| 202 |
+
}
|
| 203 |
+
chunk.tokens = inp;
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
// tokenization stats
|
| 207 |
+
if (params.verbose_prompt) {
|
| 208 |
+
for (int i = 0; i < (int) chunks.size(); i++) {
|
| 209 |
+
LOG_INF("%s: prompt %d: '%s'\n", __func__, i, chunks[i].textdata.c_str());
|
| 210 |
+
LOG_INF("%s: number of tokens in prompt = %zu\n", __func__, chunks[i].tokens.size());
|
| 211 |
+
for (int j = 0; j < (int) chunks[i].tokens.size(); j++) {
|
| 212 |
+
LOG_INF("%6d -> '%s'\n", chunks[i].tokens[j], common_token_to_piece(ctx, chunks[i].tokens[j]).c_str());
|
| 213 |
+
}
|
| 214 |
+
LOG_INF("\n\n");
|
| 215 |
+
}
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
// initialize batch
|
| 219 |
+
const int n_chunks = chunks.size();
|
| 220 |
+
struct llama_batch batch = llama_batch_init(n_batch, 0, 1);
|
| 221 |
+
|
| 222 |
+
// allocate output
|
| 223 |
+
const int n_embd_out = llama_model_n_embd_out(model);
|
| 224 |
+
std::vector<float> embeddings(n_chunks * n_embd_out, 0);
|
| 225 |
+
float * emb = embeddings.data();
|
| 226 |
+
|
| 227 |
+
// break into batches
|
| 228 |
+
unsigned int p = 0; // number of prompts processed already
|
| 229 |
+
unsigned int s = 0; // number of prompts in current batch
|
| 230 |
+
for (int k = 0; k < n_chunks; k++) {
|
| 231 |
+
// clamp to n_batch tokens
|
| 232 |
+
auto & inp = chunks[k].tokens;
|
| 233 |
+
|
| 234 |
+
const uint64_t n_toks = inp.size();
|
| 235 |
+
|
| 236 |
+
// encode if at capacity
|
| 237 |
+
if (batch.n_tokens + n_toks > n_batch || s >= llama_n_seq_max(ctx)) {
|
| 238 |
+
float * out = emb + p * n_embd_out;
|
| 239 |
+
batch_process(ctx, batch, out, s, n_embd_out);
|
| 240 |
+
common_batch_clear(batch);
|
| 241 |
+
p += s;
|
| 242 |
+
s = 0;
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
// add to batch
|
| 246 |
+
batch_add_seq(batch, inp, s);
|
| 247 |
+
s += 1;
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
// final batch
|
| 251 |
+
float * out = emb + p * n_embd_out;
|
| 252 |
+
batch_process(ctx, batch, out, s, n_embd_out);
|
| 253 |
+
|
| 254 |
+
// save embeddings to chunks
|
| 255 |
+
for (int i = 0; i < n_chunks; i++) {
|
| 256 |
+
chunks[i].embedding = std::vector<float>(emb + i * n_embd_out, emb + (i + 1) * n_embd_out);
|
| 257 |
+
// clear tokens as they are no longer needed
|
| 258 |
+
chunks[i].tokens.clear();
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
struct llama_batch query_batch = llama_batch_init(n_batch, 0, 1);
|
| 262 |
+
|
| 263 |
+
// start loop, receive query and return top k similar chunks based on cosine similarity
|
| 264 |
+
std::string query;
|
| 265 |
+
while (true) {
|
| 266 |
+
LOG("Enter query: ");
|
| 267 |
+
std::getline(std::cin, query);
|
| 268 |
+
std::vector<int32_t> query_tokens = common_tokenize(ctx, query, true);
|
| 269 |
+
|
| 270 |
+
batch_add_seq(query_batch, query_tokens, 0);
|
| 271 |
+
|
| 272 |
+
std::vector<float> query_emb(n_embd_out, 0);
|
| 273 |
+
batch_process(ctx, query_batch, query_emb.data(), 1, n_embd_out);
|
| 274 |
+
|
| 275 |
+
common_batch_clear(query_batch);
|
| 276 |
+
|
| 277 |
+
// compute cosine similarities
|
| 278 |
+
{
|
| 279 |
+
std::vector<std::pair<int, float>> similarities;
|
| 280 |
+
for (int i = 0; i < n_chunks; i++) {
|
| 281 |
+
float sim = common_embd_similarity_cos(chunks[i].embedding.data(), query_emb.data(), n_embd_out);
|
| 282 |
+
similarities.push_back(std::make_pair(i, sim));
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
// sort similarities
|
| 286 |
+
std::sort(similarities.begin(), similarities.end(), [](const std::pair<int, float> & a, const std::pair<int, float> & b) {
|
| 287 |
+
return a.second > b.second;
|
| 288 |
+
});
|
| 289 |
+
|
| 290 |
+
LOG("Top %d similar chunks:\n", params.sampling.top_k);
|
| 291 |
+
for (int i = 0; i < std::min(params.sampling.top_k, (int) chunks.size()); i++) {
|
| 292 |
+
LOG("filename: %s\n", chunks[similarities[i].first].filename.c_str());
|
| 293 |
+
LOG("filepos: %lld\n", (long long int) chunks[similarities[i].first].filepos);
|
| 294 |
+
LOG("similarity: %f\n", similarities[i].second);
|
| 295 |
+
LOG("textdata:\n%s\n", chunks[similarities[i].first].textdata.c_str());
|
| 296 |
+
LOG("--------------------\n");
|
| 297 |
+
}
|
| 298 |
+
}
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
LOG("\n");
|
| 302 |
+
llama_perf_context_print(ctx);
|
| 303 |
+
|
| 304 |
+
// clean up
|
| 305 |
+
llama_batch_free(query_batch);
|
| 306 |
+
llama_backend_free();
|
| 307 |
+
}
|
backend/llama.cpp/examples/server-llama2-13B.sh
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -e
|
| 4 |
+
|
| 5 |
+
cd "$(dirname "$0")/.." || exit
|
| 6 |
+
|
| 7 |
+
# Specify the model you want to use here:
|
| 8 |
+
MODEL="${MODEL:-./models/llama-2-13b-chat.ggmlv3.q5_K_M.bin}"
|
| 9 |
+
PROMPT_TEMPLATE=${PROMPT_TEMPLATE:-./prompts/chat-system.txt}
|
| 10 |
+
|
| 11 |
+
# Adjust to the number of CPU cores you want to use.
|
| 12 |
+
N_THREAD="${N_THREAD:-12}"
|
| 13 |
+
|
| 14 |
+
# Note: you can also override the generation options by specifying them on the command line:
|
| 15 |
+
GEN_OPTIONS="${GEN_OPTIONS:---ctx_size 4096 --batch-size 1024}"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
# shellcheck disable=SC2086 # Intended splitting of GEN_OPTIONS
|
| 19 |
+
./llama-server $GEN_OPTIONS \
|
| 20 |
+
--model "$MODEL" \
|
| 21 |
+
--threads "$N_THREAD" \
|
| 22 |
+
--rope-freq-scale 1.0 \
|
| 23 |
+
"$@"
|
| 24 |
+
|
| 25 |
+
# I used this to test the model with mps, but omitted it from the general purpose. If you want to use it, just specify it on the command line.
|
| 26 |
+
# -ngl 1 \
|
backend/llama.cpp/examples/server_embd.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
import asyncio.threads
|
| 3 |
+
import requests
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
n = 8
|
| 8 |
+
|
| 9 |
+
result = []
|
| 10 |
+
|
| 11 |
+
async def requests_post_async(*args, **kwargs):
|
| 12 |
+
return await asyncio.threads.to_thread(requests.post, *args, **kwargs)
|
| 13 |
+
|
| 14 |
+
async def main():
|
| 15 |
+
model_url = "http://127.0.0.1:6900"
|
| 16 |
+
responses: list[requests.Response] = await asyncio.gather(*[requests_post_async(
|
| 17 |
+
url= f"{model_url}/embedding",
|
| 18 |
+
json= {"content": "a "*1022}
|
| 19 |
+
) for i in range(n)])
|
| 20 |
+
|
| 21 |
+
for response in responses:
|
| 22 |
+
embedding = response.json()["embedding"]
|
| 23 |
+
print(embedding[-8:])
|
| 24 |
+
result.append(embedding)
|
| 25 |
+
|
| 26 |
+
asyncio.run(main())
|
| 27 |
+
|
| 28 |
+
# compute cosine similarity
|
| 29 |
+
|
| 30 |
+
for i in range(n-1):
|
| 31 |
+
for j in range(i+1, n):
|
| 32 |
+
embedding1 = np.array(result[i])
|
| 33 |
+
embedding2 = np.array(result[j])
|
| 34 |
+
similarity = np.dot(embedding1, embedding2) / (np.linalg.norm(embedding1) * np.linalg.norm(embedding2))
|
| 35 |
+
print(f"Similarity between {i} and {j}: {similarity:.2f}")
|
backend/llama.cpp/examples/simple-chat/CMakeLists.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
set(TARGET llama-simple-chat)
|
| 2 |
+
add_executable(${TARGET} simple-chat.cpp)
|
| 3 |
+
install(TARGETS ${TARGET} RUNTIME)
|
| 4 |
+
target_link_libraries(${TARGET} PRIVATE llama ${CMAKE_THREAD_LIBS_INIT})
|
| 5 |
+
target_compile_features(${TARGET} PRIVATE cxx_std_17)
|
backend/llama.cpp/examples/simple-chat/README.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# llama.cpp/example/simple-chat
|
| 2 |
+
|
| 3 |
+
The purpose of this example is to demonstrate a minimal usage of llama.cpp to create a simple chat program using the chat template from the GGUF file.
|
| 4 |
+
|
| 5 |
+
```bash
|
| 6 |
+
./llama-simple-chat -m Meta-Llama-3.1-8B-Instruct.gguf -c 2048
|
| 7 |
+
...
|
backend/llama.cpp/examples/simple-chat/simple-chat.cpp
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "llama.h"
|
| 2 |
+
#include <clocale>
|
| 3 |
+
#include <cstdio>
|
| 4 |
+
#include <cstring>
|
| 5 |
+
#include <iostream>
|
| 6 |
+
#include <string>
|
| 7 |
+
#include <vector>
|
| 8 |
+
|
| 9 |
+
static void print_usage(int, char ** argv) {
|
| 10 |
+
printf("\nexample usage:\n");
|
| 11 |
+
printf("\n %s -m model.gguf [-c context_size] [-ngl n_gpu_layers]\n", argv[0]);
|
| 12 |
+
printf("\n");
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
int main(int argc, char ** argv) {
|
| 16 |
+
std::setlocale(LC_NUMERIC, "C");
|
| 17 |
+
|
| 18 |
+
std::string model_path;
|
| 19 |
+
int ngl = 99;
|
| 20 |
+
int n_ctx = 2048;
|
| 21 |
+
|
| 22 |
+
// parse command line arguments
|
| 23 |
+
for (int i = 1; i < argc; i++) {
|
| 24 |
+
try {
|
| 25 |
+
if (strcmp(argv[i], "-m") == 0) {
|
| 26 |
+
if (i + 1 < argc) {
|
| 27 |
+
model_path = argv[++i];
|
| 28 |
+
} else {
|
| 29 |
+
print_usage(argc, argv);
|
| 30 |
+
return 1;
|
| 31 |
+
}
|
| 32 |
+
} else if (strcmp(argv[i], "-c") == 0) {
|
| 33 |
+
if (i + 1 < argc) {
|
| 34 |
+
n_ctx = std::stoi(argv[++i]);
|
| 35 |
+
} else {
|
| 36 |
+
print_usage(argc, argv);
|
| 37 |
+
return 1;
|
| 38 |
+
}
|
| 39 |
+
} else if (strcmp(argv[i], "-ngl") == 0) {
|
| 40 |
+
if (i + 1 < argc) {
|
| 41 |
+
ngl = std::stoi(argv[++i]);
|
| 42 |
+
} else {
|
| 43 |
+
print_usage(argc, argv);
|
| 44 |
+
return 1;
|
| 45 |
+
}
|
| 46 |
+
} else {
|
| 47 |
+
print_usage(argc, argv);
|
| 48 |
+
return 1;
|
| 49 |
+
}
|
| 50 |
+
} catch (std::exception & e) {
|
| 51 |
+
fprintf(stderr, "error: %s\n", e.what());
|
| 52 |
+
print_usage(argc, argv);
|
| 53 |
+
return 1;
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
if (model_path.empty()) {
|
| 57 |
+
print_usage(argc, argv);
|
| 58 |
+
return 1;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
// only print errors
|
| 62 |
+
llama_log_set([](enum ggml_log_level level, const char * text, void * /* user_data */) {
|
| 63 |
+
if (level >= GGML_LOG_LEVEL_ERROR) {
|
| 64 |
+
fprintf(stderr, "%s", text);
|
| 65 |
+
}
|
| 66 |
+
}, nullptr);
|
| 67 |
+
|
| 68 |
+
// load dynamic backends
|
| 69 |
+
ggml_backend_load_all();
|
| 70 |
+
|
| 71 |
+
// initialize the model
|
| 72 |
+
llama_model_params model_params = llama_model_default_params();
|
| 73 |
+
model_params.n_gpu_layers = ngl;
|
| 74 |
+
|
| 75 |
+
llama_model * model = llama_model_load_from_file(model_path.c_str(), model_params);
|
| 76 |
+
if (!model) {
|
| 77 |
+
fprintf(stderr , "%s: error: unable to load model\n" , __func__);
|
| 78 |
+
return 1;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
const llama_vocab * vocab = llama_model_get_vocab(model);
|
| 82 |
+
|
| 83 |
+
// initialize the context
|
| 84 |
+
llama_context_params ctx_params = llama_context_default_params();
|
| 85 |
+
ctx_params.n_ctx = n_ctx;
|
| 86 |
+
ctx_params.n_batch = n_ctx;
|
| 87 |
+
|
| 88 |
+
llama_context * ctx = llama_init_from_model(model, ctx_params);
|
| 89 |
+
if (!ctx) {
|
| 90 |
+
fprintf(stderr , "%s: error: failed to create the llama_context\n" , __func__);
|
| 91 |
+
return 1;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
// initialize the sampler
|
| 95 |
+
llama_sampler * smpl = llama_sampler_chain_init(llama_sampler_chain_default_params());
|
| 96 |
+
llama_sampler_chain_add(smpl, llama_sampler_init_min_p(0.05f, 1));
|
| 97 |
+
llama_sampler_chain_add(smpl, llama_sampler_init_temp(0.8f));
|
| 98 |
+
llama_sampler_chain_add(smpl, llama_sampler_init_dist(LLAMA_DEFAULT_SEED));
|
| 99 |
+
|
| 100 |
+
// helper function to evaluate a prompt and generate a response
|
| 101 |
+
auto generate = [&](const std::string & prompt) {
|
| 102 |
+
std::string response;
|
| 103 |
+
|
| 104 |
+
const bool is_first = llama_memory_seq_pos_max(llama_get_memory(ctx), 0) == -1;
|
| 105 |
+
|
| 106 |
+
// tokenize the prompt
|
| 107 |
+
const int n_prompt_tokens = -llama_tokenize(vocab, prompt.c_str(), prompt.size(), NULL, 0, is_first, true);
|
| 108 |
+
std::vector<llama_token> prompt_tokens(n_prompt_tokens);
|
| 109 |
+
if (llama_tokenize(vocab, prompt.c_str(), prompt.size(), prompt_tokens.data(), prompt_tokens.size(), is_first, true) < 0) {
|
| 110 |
+
GGML_ABORT("failed to tokenize the prompt\n");
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
// prepare a batch for the prompt
|
| 114 |
+
llama_batch batch = llama_batch_get_one(prompt_tokens.data(), prompt_tokens.size());
|
| 115 |
+
llama_token new_token_id;
|
| 116 |
+
while (true) {
|
| 117 |
+
// check if we have enough space in the context to evaluate this batch
|
| 118 |
+
int n_ctx = llama_n_ctx(ctx);
|
| 119 |
+
int n_ctx_used = llama_memory_seq_pos_max(llama_get_memory(ctx), 0) + 1;
|
| 120 |
+
if (n_ctx_used + batch.n_tokens > n_ctx) {
|
| 121 |
+
printf("\033[0m\n");
|
| 122 |
+
fprintf(stderr, "context size exceeded\n");
|
| 123 |
+
exit(0);
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
int ret = llama_decode(ctx, batch);
|
| 127 |
+
if (ret != 0) {
|
| 128 |
+
GGML_ABORT("failed to decode, ret = %d\n", ret);
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
// sample the next token
|
| 132 |
+
new_token_id = llama_sampler_sample(smpl, ctx, -1);
|
| 133 |
+
|
| 134 |
+
// is it an end of generation?
|
| 135 |
+
if (llama_vocab_is_eog(vocab, new_token_id)) {
|
| 136 |
+
break;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
// convert the token to a string, print it and add it to the response
|
| 140 |
+
char buf[256];
|
| 141 |
+
int n = llama_token_to_piece(vocab, new_token_id, buf, sizeof(buf), 0, true);
|
| 142 |
+
if (n < 0) {
|
| 143 |
+
GGML_ABORT("failed to convert token to piece\n");
|
| 144 |
+
}
|
| 145 |
+
std::string piece(buf, n);
|
| 146 |
+
printf("%s", piece.c_str());
|
| 147 |
+
fflush(stdout);
|
| 148 |
+
response += piece;
|
| 149 |
+
|
| 150 |
+
// prepare the next batch with the sampled token
|
| 151 |
+
batch = llama_batch_get_one(&new_token_id, 1);
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
return response;
|
| 155 |
+
};
|
| 156 |
+
|
| 157 |
+
std::vector<llama_chat_message> messages;
|
| 158 |
+
std::vector<char> formatted(llama_n_ctx(ctx));
|
| 159 |
+
int prev_len = 0;
|
| 160 |
+
while (true) {
|
| 161 |
+
// get user input
|
| 162 |
+
printf("\033[32m> \033[0m");
|
| 163 |
+
std::string user;
|
| 164 |
+
std::getline(std::cin, user);
|
| 165 |
+
|
| 166 |
+
if (user.empty()) {
|
| 167 |
+
break;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
const char * tmpl = llama_model_chat_template(model, /* name */ nullptr);
|
| 171 |
+
|
| 172 |
+
// add the user input to the message list and format it
|
| 173 |
+
messages.push_back({"user", strdup(user.c_str())});
|
| 174 |
+
int new_len = llama_chat_apply_template(tmpl, messages.data(), messages.size(), true, formatted.data(), formatted.size());
|
| 175 |
+
if (new_len > (int)formatted.size()) {
|
| 176 |
+
formatted.resize(new_len);
|
| 177 |
+
new_len = llama_chat_apply_template(tmpl, messages.data(), messages.size(), true, formatted.data(), formatted.size());
|
| 178 |
+
}
|
| 179 |
+
if (new_len < 0) {
|
| 180 |
+
fprintf(stderr, "failed to apply the chat template\n");
|
| 181 |
+
return 1;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
// remove previous messages to obtain the prompt to generate the response
|
| 185 |
+
std::string prompt(formatted.begin() + prev_len, formatted.begin() + new_len);
|
| 186 |
+
|
| 187 |
+
// generate a response
|
| 188 |
+
printf("\033[33m");
|
| 189 |
+
std::string response = generate(prompt);
|
| 190 |
+
printf("\n\033[0m");
|
| 191 |
+
|
| 192 |
+
// add the response to the messages
|
| 193 |
+
messages.push_back({"assistant", strdup(response.c_str())});
|
| 194 |
+
prev_len = llama_chat_apply_template(tmpl, messages.data(), messages.size(), false, nullptr, 0);
|
| 195 |
+
if (prev_len < 0) {
|
| 196 |
+
fprintf(stderr, "failed to apply the chat template\n");
|
| 197 |
+
return 1;
|
| 198 |
+
}
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
// free resources
|
| 202 |
+
for (auto & msg : messages) {
|
| 203 |
+
free(const_cast<char *>(msg.content));
|
| 204 |
+
}
|
| 205 |
+
llama_sampler_free(smpl);
|
| 206 |
+
llama_free(ctx);
|
| 207 |
+
llama_model_free(model);
|
| 208 |
+
|
| 209 |
+
return 0;
|
| 210 |
+
}
|
backend/llama.cpp/examples/simple-cmake-pkg/.gitignore
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Prerequisites
|
| 2 |
+
*.d
|
| 3 |
+
|
| 4 |
+
# Compiled Object files
|
| 5 |
+
*.slo
|
| 6 |
+
*.lo
|
| 7 |
+
*.o
|
| 8 |
+
*.obj
|
| 9 |
+
|
| 10 |
+
# Precompiled Headers
|
| 11 |
+
*.gch
|
| 12 |
+
*.pch
|
| 13 |
+
|
| 14 |
+
# Compiled Dynamic libraries
|
| 15 |
+
*.so
|
| 16 |
+
*.dylib
|
| 17 |
+
*.dll
|
| 18 |
+
|
| 19 |
+
# Fortran module files
|
| 20 |
+
*.mod
|
| 21 |
+
*.smod
|
| 22 |
+
|
| 23 |
+
# Compiled Static libraries
|
| 24 |
+
*.lai
|
| 25 |
+
*.la
|
| 26 |
+
*.a
|
| 27 |
+
*.lib
|
| 28 |
+
|
| 29 |
+
# Executables
|
| 30 |
+
*.exe
|
| 31 |
+
*.out
|
| 32 |
+
*.app
|
| 33 |
+
|
| 34 |
+
*.gguf
|
| 35 |
+
|
| 36 |
+
*.log
|
| 37 |
+
.DS_Store
|
| 38 |
+
.build/
|
| 39 |
+
.cache/
|
| 40 |
+
.direnv/
|
| 41 |
+
.envrc
|
| 42 |
+
.swiftpm
|
| 43 |
+
.venv
|
| 44 |
+
.clang-tidy
|
| 45 |
+
.vs/
|
| 46 |
+
.vscode/
|
| 47 |
+
|
| 48 |
+
build*/
|
| 49 |
+
out/
|
| 50 |
+
tmp/
|