diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2026-02-13 18:07:45 +0100 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2026-02-13 18:07:45 +0100 |
| commit | 2a6fd554998c64733e2d97aecf653f6e48e0f8b4 (patch) | |
| tree | f53c2f0b4dc42825f426ada0ebb591dbb80c89e9 | |
| parent | a1a595a3305727d30e16e856f4faf95980643e1c (diff) | |
| download | llmnpc-2a6fd554998c64733e2d97aecf653f6e48e0f8b4.tar.gz | |
Store context documents to Vector Database
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | Makefile | 13 | ||||
| -rw-r--r-- | context.c | 174 | ||||
| -rw-r--r-- | minunit.h | 391 | ||||
| -rw-r--r-- | models.h | 110 | ||||
| -rw-r--r-- | nonstd.h | 835 | ||||
| -rw-r--r-- | prompt.c | 11 | ||||
| -rw-r--r-- | vectordb.c | 118 | ||||
| -rw-r--r-- | vectordb.h | 3 |
9 files changed, 1581 insertions, 78 deletions
@@ -1,7 +1,9 @@ # Build artefacts -prompt models/ +prompt +context # Other files .DS_Store *.log +*.vdb @@ -12,24 +12,27 @@ LDFLAGS = -L$(LLAMA_DIR)/build/src -L$(LLAMA_DIR)/build/ggml/src \ help: .help -prompt: prompt.c vectordb.c models.h # Build prompt binary for testing +build/prompt: prompt.c vectordb.c models.h # Build prompt binary for testing $(CC) $(CFLAGS) prompt.c vectordb.c -o prompt $(LDFLAGS) -llamacpp: .assure # Build llama.cpp libraries +build/context: context.c vectordb.c models.h # Build context binary for testing + $(CC) $(CFLAGS) context.c vectordb.c -o context $(LDFLAGS) + +build/llama.cpp: .assure # Build llama.cpp libraries mkdir $(LLAMA_DIR)/build && \ cd $(LLAMA_DIR)/build && \ cmake ../ -DBUILD_SHARED_LIBS=OFF && \ make -j8 -fetchmodels: .assure # Fetch GGUF models +run/fetch-models: .assure # Fetch GGUF models -mkdir -p models cd models && wget -nc -i ../models.txt -docker: .assure # Runs prompt in Docker container +run/docker: .assure # Runs prompt in Docker container docker build -t promptd . docker run -it promptd -clean: # Cleans up all the build artefacts +run/clean: # Cleans up all the build artefacts -rm -f prompt cd $(LLAMA_DIR)/build && make clean -rm -Rf $(LLAMA_DIR)/build diff --git a/context.c b/context.c new file mode 100644 index 0000000..9b9770c --- /dev/null +++ b/context.c @@ -0,0 +1,174 @@ +#include "llama.h" +#include "vectordb.h" +#include "models.h" + +#define NONSTD_IMPLEMENTATION +#include "nonstd.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <getopt.h> + +#define MAX_TOKENS 512 +#define MAX_TOKEN_LEN 32 + +typedef struct { + +} Engine; + +static void llama_log_callback(enum ggml_log_level level, const char *text, void *user_data) { + (void)level; + (void)user_data; + (void)text; +} + +void list_available_models() { + printf("Model list:\n"); + ModelConfig model; + static_foreach(ModelConfig, model, models) { + printf(" - %s [ctx: %d, temp: %f]\n", model.name, model.n_ctx, model.temperature); + } +} + +static void show_help(const char *prog) { + printf("Usage: %s [OPTIONS]\n", prog); + printf("Options:\n"); + printf(" -m, --model <name> Specify model to use (default: first model)\n"); + printf(" -i, --in <file> Specify input context file\n"); + printf(" -o, --out <file> Specify output vector database file\n"); + printf(" -l, --list Lists all available models\n"); + printf(" -v, --verbose Enable verbose logging\n"); + printf(" -h, --help Show this help message\n"); +} + +int main(int argc, char **argv) { + set_log_level(LOG_DEBUG); + + const char *model_name = NULL; + const char *in_file = NULL; + const char *out_file = NULL; + int list_models = 0; + int verbose = 0; + + static struct option long_options[] = { + {"model", required_argument, 0, 'm'}, + {"in", required_argument, 0, 'i'}, + {"out", required_argument, 0, 'o'}, + {"list", no_argument, 0, 'l'}, + {"verbose", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + int opt; + int option_index = 0; + while ((opt = getopt_long(argc, argv, "m:i:o:lvh", long_options, &option_index)) != -1) { + switch (opt) { + case 'm': + model_name = optarg; + break; + case 'i': + in_file = optarg; + break; + case 'o': + out_file = optarg; + break; + case 'l': + list_models = 1; + break; + case 'v': + verbose = 1; + break; + case 'h': + show_help(argv[0]); + return 0; + default: + fprintf(stderr, "Usage: %s [-m model] [-i file] [-o file] [-lvh]\n", argv[0]); + return 1; + } + } + + if (verbose == 0) { + llama_log_set(llama_log_callback, NULL); + } + + if (list_models == 1) { + list_available_models(); + return 0; + } + + if (in_file == NULL) { + log_message(stderr, LOG_ERROR, "Input context file must be provided. Exiting..."); + return 1; + } + + if (out_file == NULL) { + log_message(stderr, LOG_ERROR, "Output vector context file must be provided. Exiting..."); + return 1; + } + + llama_backend_init(); + + const ModelConfig *cfg = NULL; + if (model_name != NULL) { + cfg = get_model_by_name(model_name); + if (cfg == NULL) { + log_message(stderr, LOG_ERROR, "Unknown model '%s'", model_name); + llama_backend_free(); + return 1; + } + } else { + cfg = &models[0]; + } + + struct llama_model *model = llama_model_load_from_file(cfg->filepath, llama_model_default_params()); + if (model == NULL) { + log_message(stderr, LOG_ERROR, "Unable to load embedding model"); + llama_backend_free(); + return 1; + } + + struct llama_context_params cparams = llama_context_default_params(); + cparams.embeddings = true; + + struct llama_context *embed_ctx = llama_init_from_model(model, cparams); + if (embed_ctx == NULL) { + log_message(stderr, LOG_ERROR, "Failed to create embedding context"); + llama_model_free(model); + llama_backend_free(); + return 1; + } + + FILE *context_fp = fopen(in_file, "r"); + if (context_fp == NULL) { + log_message(stderr, LOG_ERROR, "Unable to open context file %s", in_file); + return 1; + } + + VectorDB db; + vdb_init(&db, embed_ctx); + + char line[1024]; + while (fgets(line, sizeof(line), context_fp) != NULL) { + size_t len = strlen(line); + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) { + line[len - 1] = '\0'; + len--; + } + if (len == 0) { + continue; + } + vdb_add_document(&db, line); + } + + if (vdb_save(&db, out_file) > 0) { + log_message(stderr, LOG_ERROR, "Something went wrong saving file %s", out_file); + fclose(context_fp); + return 1; + } + + log_message(stdout, LOG_INFO, "Context vector database file %s successfully written", out_file); + fclose(context_fp); + return 0; +} diff --git a/minunit.h b/minunit.h new file mode 100644 index 0000000..5e89245 --- /dev/null +++ b/minunit.h @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2012 David SiƱuela Pastor, siu.4coders@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef MINUNIT_MINUNIT_H +#define MINUNIT_MINUNIT_H + +#ifdef __cplusplus + extern "C" { +#endif + +#if defined(_WIN32) +#include <Windows.h> +#if defined(_MSC_VER) && _MSC_VER < 1900 + #define snprintf _snprintf + #define __func__ __FUNCTION__ +#endif + +#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__)) + +/* Change POSIX C SOURCE version for pure c99 compilers */ +#if !defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 200112L +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200112L +#endif + +#include <unistd.h> /* POSIX flags */ +#include <time.h> /* clock_gettime(), time() */ +#include <sys/time.h> /* gethrtime(), gettimeofday() */ +#include <sys/resource.h> +#include <sys/times.h> +#include <string.h> + +#if defined(__MACH__) && defined(__APPLE__) +#include <mach/mach.h> +#include <mach/mach_time.h> +#endif + +#if __GNUC__ >= 5 && !defined(__STDC_VERSION__) +#define __func__ __extension__ __FUNCTION__ +#endif + +#else +#error "Unable to define timers for an unknown OS." +#endif + +#include <stdio.h> +#include <math.h> + +/* Maximum length of last message */ +#define MINUNIT_MESSAGE_LEN 1024 +/* Accuracy with which floats are compared */ +#define MINUNIT_EPSILON 1E-12 + +/* Misc. counters */ +static int minunit_run = 0; +static int minunit_assert = 0; +static int minunit_fail = 0; +static int minunit_status = 0; + +/* Timers */ +static double minunit_real_timer = 0; +static double minunit_proc_timer = 0; + +/* Last message */ +static char minunit_last_message[MINUNIT_MESSAGE_LEN]; + +/* Test setup and teardown function pointers */ +static void (*minunit_setup)(void) = NULL; +static void (*minunit_teardown)(void) = NULL; + +/* Definitions */ +#define MU_TEST(method_name) static void method_name(void) +#define MU_TEST_SUITE(suite_name) static void suite_name(void) + +#define MU__SAFE_BLOCK(block) do {\ + block\ +} while(0) + +/* Run test suite and unset setup and teardown functions */ +#define MU_RUN_SUITE(suite_name) MU__SAFE_BLOCK(\ + suite_name();\ + minunit_setup = NULL;\ + minunit_teardown = NULL;\ +) + +/* Configure setup and teardown functions */ +#define MU_SUITE_CONFIGURE(setup_fun, teardown_fun) MU__SAFE_BLOCK(\ + minunit_setup = setup_fun;\ + minunit_teardown = teardown_fun;\ +) + +/* Test runner */ +#define MU_RUN_TEST(test) MU__SAFE_BLOCK(\ + if (minunit_real_timer==0 && minunit_proc_timer==0) {\ + minunit_real_timer = mu_timer_real();\ + minunit_proc_timer = mu_timer_cpu();\ + }\ + if (minunit_setup) (*minunit_setup)();\ + minunit_status = 0;\ + test();\ + minunit_run++;\ + if (minunit_status) {\ + minunit_fail++;\ + printf("F");\ + printf("\n%s\n", minunit_last_message);\ + }\ + (void)fflush(stdout);\ + if (minunit_teardown) (*minunit_teardown)();\ +) + +/* Report */ +#define MU_REPORT() MU__SAFE_BLOCK(\ + double minunit_end_real_timer;\ + double minunit_end_proc_timer;\ + printf("\n\n%d tests, %d assertions, %d failures\n", minunit_run, minunit_assert, minunit_fail);\ + minunit_end_real_timer = mu_timer_real();\ + minunit_end_proc_timer = mu_timer_cpu();\ + printf("\nFinished in %.8f seconds (real) %.8f seconds (proc)\n\n",\ + minunit_end_real_timer - minunit_real_timer,\ + minunit_end_proc_timer - minunit_proc_timer);\ +) +#define MU_EXIT_CODE minunit_fail + +/* Assertions */ +#define mu_check(test) MU__SAFE_BLOCK(\ + minunit_assert++;\ + if (!(test)) {\ + (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, #test);\ + minunit_status = 1;\ + return;\ + } else {\ + printf(".");\ + }\ +) + +#define mu_fail(message) MU__SAFE_BLOCK(\ + minunit_assert++;\ + (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\ + minunit_status = 1;\ + return;\ +) + +#define mu_assert(test, message) MU__SAFE_BLOCK(\ + minunit_assert++;\ + if (!(test)) {\ + (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\ + minunit_status = 1;\ + return;\ + } else {\ + printf(".");\ + }\ +) + +#define mu_assert_int_eq(expected, result) MU__SAFE_BLOCK(\ + int minunit_tmp_e;\ + int minunit_tmp_r;\ + minunit_assert++;\ + minunit_tmp_e = (expected);\ + minunit_tmp_r = (result);\ + if (minunit_tmp_e != minunit_tmp_r) {\ + (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %d expected but was %d", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\ + minunit_status = 1;\ + return;\ + } else {\ + printf(".");\ + }\ +) + +#define mu_assert_double_eq(expected, result) MU__SAFE_BLOCK(\ + double minunit_tmp_e;\ + double minunit_tmp_r;\ + minunit_assert++;\ + minunit_tmp_e = (expected);\ + minunit_tmp_r = (result);\ + if (fabs(minunit_tmp_e-minunit_tmp_r) > MINUNIT_EPSILON) {\ + int minunit_significant_figures = 1 - log10(MINUNIT_EPSILON);\ + (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %.*g expected but was %.*g", __func__, __FILE__, __LINE__, minunit_significant_figures, minunit_tmp_e, minunit_significant_figures, minunit_tmp_r);\ + minunit_status = 1;\ + return;\ + } else {\ + printf(".");\ + }\ +) + +#define mu_assert_string_eq(expected, result) MU__SAFE_BLOCK(\ + const char* minunit_tmp_e = expected;\ + const char* minunit_tmp_r = result;\ + minunit_assert++;\ + if (!minunit_tmp_e) {\ + minunit_tmp_e = "<null pointer>";\ + }\ + if (!minunit_tmp_r) {\ + minunit_tmp_r = "<null pointer>";\ + }\ + if(strcmp(minunit_tmp_e, minunit_tmp_r) != 0) {\ + (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: '%s' expected but was '%s'", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\ + minunit_status = 1;\ + return;\ + } else {\ + printf(".");\ + }\ +) + +/* + * The following two functions were written by David Robert Nadeau + * from http://NadeauSoftware.com/ and distributed under the + * Creative Commons Attribution 3.0 Unported License + */ + +/** + * Returns the real time, in seconds, or -1.0 if an error occurred. + * + * Time is measured since an arbitrary and OS-dependent start time. + * The returned real time is only useful for computing an elapsed time + * between two calls to this function. + */ +static double mu_timer_real(void) +{ +#if defined(_WIN32) + /* Windows 2000 and later. ---------------------------------- */ + LARGE_INTEGER Time; + LARGE_INTEGER Frequency; + + QueryPerformanceFrequency(&Frequency); + QueryPerformanceCounter(&Time); + + Time.QuadPart *= 1000000; + Time.QuadPart /= Frequency.QuadPart; + + return (double)Time.QuadPart / 1000000.0; + +#elif (defined(__hpux) || defined(hpux)) || ((defined(__sun__) || defined(__sun) || defined(sun)) && (defined(__SVR4) || defined(__svr4__))) + /* HP-UX, Solaris. ------------------------------------------ */ + return (double)gethrtime( ) / 1000000000.0; + +#elif defined(__MACH__) && defined(__APPLE__) + /* OSX. ----------------------------------------------------- */ + static double timeConvert = 0.0; + if ( timeConvert == 0.0 ) + { + mach_timebase_info_data_t timeBase; + (void)mach_timebase_info( &timeBase ); + timeConvert = (double)timeBase.numer / + (double)timeBase.denom / + 1000000000.0; + } + return (double)mach_absolute_time( ) * timeConvert; + +#elif defined(_POSIX_VERSION) + /* POSIX. --------------------------------------------------- */ + struct timeval tm; +#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) + { + struct timespec ts; +#if defined(CLOCK_MONOTONIC_PRECISE) + /* BSD. --------------------------------------------- */ + const clockid_t id = CLOCK_MONOTONIC_PRECISE; +#elif defined(CLOCK_MONOTONIC_RAW) + /* Linux. ------------------------------------------- */ + const clockid_t id = CLOCK_MONOTONIC_RAW; +#elif defined(CLOCK_HIGHRES) + /* Solaris. ----------------------------------------- */ + const clockid_t id = CLOCK_HIGHRES; +#elif defined(CLOCK_MONOTONIC) + /* AIX, BSD, Linux, POSIX, Solaris. ----------------- */ + const clockid_t id = CLOCK_MONOTONIC; +#elif defined(CLOCK_REALTIME) + /* AIX, BSD, HP-UX, Linux, POSIX. ------------------- */ + const clockid_t id = CLOCK_REALTIME; +#else + const clockid_t id = (clockid_t)-1; /* Unknown. */ +#endif /* CLOCK_* */ + if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 ) + return (double)ts.tv_sec + + (double)ts.tv_nsec / 1000000000.0; + /* Fall thru. */ + } +#endif /* _POSIX_TIMERS */ + + /* AIX, BSD, Cygwin, HP-UX, Linux, OSX, POSIX, Solaris. ----- */ + gettimeofday( &tm, NULL ); + return (double)tm.tv_sec + (double)tm.tv_usec / 1000000.0; +#else + return -1.0; /* Failed. */ +#endif +} + +/** + * Returns the amount of CPU time used by the current process, + * in seconds, or -1.0 if an error occurred. + */ +static double mu_timer_cpu(void) +{ +#if defined(_WIN32) + /* Windows -------------------------------------------------- */ + FILETIME createTime; + FILETIME exitTime; + FILETIME kernelTime; + FILETIME userTime; + + /* This approach has a resolution of 1/64 second. Unfortunately, Windows' API does not offer better */ + if ( GetProcessTimes( GetCurrentProcess( ), + &createTime, &exitTime, &kernelTime, &userTime ) != 0 ) + { + ULARGE_INTEGER userSystemTime; + memcpy(&userSystemTime, &userTime, sizeof(ULARGE_INTEGER)); + return (double)userSystemTime.QuadPart / 10000000.0; + } + +#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__)) + /* AIX, BSD, Cygwin, HP-UX, Linux, OSX, and Solaris --------- */ + +#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) + /* Prefer high-res POSIX timers, when available. */ + { + clockid_t id; + struct timespec ts; +#if _POSIX_CPUTIME > 0 + /* Clock ids vary by OS. Query the id, if possible. */ + if ( clock_getcpuclockid( 0, &id ) == -1 ) +#endif +#if defined(CLOCK_PROCESS_CPUTIME_ID) + /* Use known clock id for AIX, Linux, or Solaris. */ + id = CLOCK_PROCESS_CPUTIME_ID; +#elif defined(CLOCK_VIRTUAL) + /* Use known clock id for BSD or HP-UX. */ + id = CLOCK_VIRTUAL; +#else + id = (clockid_t)-1; +#endif + if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 ) + return (double)ts.tv_sec + + (double)ts.tv_nsec / 1000000000.0; + } +#endif + +#if defined(RUSAGE_SELF) + { + struct rusage rusage; + if ( getrusage( RUSAGE_SELF, &rusage ) != -1 ) + return (double)rusage.ru_utime.tv_sec + + (double)rusage.ru_utime.tv_usec / 1000000.0; + } +#endif + +#if defined(_SC_CLK_TCK) + { + const double ticks = (double)sysconf( _SC_CLK_TCK ); + struct tms tms; + if ( times( &tms ) != (clock_t)-1 ) + return (double)tms.tms_utime / ticks; + } +#endif + +#if defined(CLOCKS_PER_SEC) + { + clock_t cl = clock( ); + if ( cl != (clock_t)-1 ) + return (double)cl / (double)CLOCKS_PER_SEC; + } +#endif + +#endif + + return -1; /* Failed. */ +} + +#ifdef __cplusplus +} +#endif + +#endif /* MINUNIT_MINUNIT_H */ @@ -6,64 +6,64 @@ #include <string.h> typedef struct { - const char *name; - const char *filepath; - int n_gpu_layers; - bool use_mmap; - int n_ctx; - int n_batch; - bool embeddings; - float temperature; - float min_p; - uint32_t seed; -} model_config; + const char *name; + const char *filepath; + int n_gpu_layers; + bool use_mmap; + int n_ctx; + int n_batch; + bool embeddings; + float temperature; + float min_p; + uint32_t seed; +} ModelConfig; -model_config models[] = { - { - .name = "flan-t5-small", - .filepath = "models/flan-t5-small.F16.gguf", - .n_gpu_layers = 0, - .use_mmap = false, - .n_ctx = 512, - .n_batch = 512, - .embeddings = false, - .temperature = 0.8f, - .min_p = 0.05f, - .seed = LLAMA_DEFAULT_SEED, - }, - { - .name = "phi-4-mini-instruct", - .filepath = "models/Phi-4-mini-instruct.Q2_K.gguf", - .n_gpu_layers = 0, - .use_mmap = false, - .n_ctx = 131072, - .n_batch = 4096, - .embeddings = false, - .temperature = 0.8f, - .min_p = 0.05f, - .seed = LLAMA_DEFAULT_SEED, - }, - { - .name = "tinyllama-1", - .filepath = "models/TinyLlama-1.1B-intermediate-step-1431k-3T-Q2_K.gguf", - .n_gpu_layers = 0, - .use_mmap = false, - .n_ctx = 2048, - .n_batch = 4096, - .embeddings = false, - .temperature = 0.8f, - .min_p = 0.05f, - .seed = LLAMA_DEFAULT_SEED, - }, +ModelConfig models[] = { + { + .name = "tinyllama-1", + .filepath = "models/TinyLlama-1.1B-intermediate-step-1431k-3T-Q2_K.gguf", + .n_gpu_layers = 0, + .use_mmap = false, + .n_ctx = 2048, + .n_batch = 4096, + .embeddings = false, + .temperature = 0.8f, + .min_p = 0.05f, + .seed = LLAMA_DEFAULT_SEED, + }, + { + .name = "flan-t5-small", + .filepath = "models/flan-t5-small.F16.gguf", + .n_gpu_layers = 0, + .use_mmap = false, + .n_ctx = 512, + .n_batch = 512, + .embeddings = false, + .temperature = 0.8f, + .min_p = 0.05f, + .seed = LLAMA_DEFAULT_SEED, + }, + { + .name = "phi-4-mini-instruct", + .filepath = "models/Phi-4-mini-instruct.Q2_K.gguf", + .n_gpu_layers = 0, + .use_mmap = false, + .n_ctx = 131072, + .n_batch = 4096, + .embeddings = false, + .temperature = 0.8f, + .min_p = 0.05f, + .seed = LLAMA_DEFAULT_SEED, + }, }; -const model_config *get_model_by_name(const char *name) { - for (size_t i = 0; i < sizeof(models) / sizeof(models[0]); i++) { - if (models[i].name != NULL && strcmp(models[i].name, name) == 0) { - return &models[i]; - } - } - return NULL; +const ModelConfig *get_model_by_name(const char *name) { + for (size_t i = 0; i < sizeof(models) / sizeof(models[0]); i++) { + if (models[i].name != NULL && strcmp(models[i].name, name) == 0) { + return &models[i]; + } + } + return NULL; } #endif diff --git a/nonstd.h b/nonstd.h new file mode 100644 index 0000000..146b12d --- /dev/null +++ b/nonstd.h @@ -0,0 +1,835 @@ +// nonstd.h +// A collection of useful functions and macros. +// This library is licensed under the BSD 2-Clause License. +// +// This file provides both the interface and the implementation. +// To instantiate the implementation, +// #define NONSTD_IMPLEMENTATION +// before including this file. + +#ifdef NONSTD_IMPLEMENTATION +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#endif + +#ifndef NONSTD_H +#define NONSTD_H + +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <time.h> +#include <unistd.h> + +#ifndef NONSTD_DEF +#ifdef NONSTD_STATIC +#define NONSTD_DEF static +#else +#define NONSTD_DEF extern +#endif +#endif + +typedef int8_t i8; +typedef uint8_t u8; +typedef int16_t i16; +typedef uint16_t u16; +typedef int32_t i32; +typedef uint32_t u32; +typedef int64_t i64; +typedef uint64_t u64; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef intptr_t isize; +typedef uintptr_t usize; +typedef char c8; + +#define countof(a) (sizeof(a) / sizeof((a)[0])) + +#define static_foreach(type, var, array) \ + for (size_t _i_##var = 0, _n_##var = countof(array); \ + _i_##var < _n_##var && ((var) = (array)[_i_##var], 1); ++_i_##var) + +#define ALLOC(type, n) ((type *)safe_malloc(sizeof(type), (n))) +#define REALLOC(ptr, type, n) ((type *)safe_realloc((ptr), sizeof(type), (n))) +#define FREE(ptr) \ + do { \ + free(ptr); \ + ptr = NULL; \ + } while (0) + +NONSTD_DEF void *safe_malloc(size_t item_size, size_t count); +NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count); + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define CLAMP(x, lo, hi) (MIN((hi), MAX((lo), (x)))) + +// From https://github.com/tsoding/nob.h/blob/e2c9a46f01d052ab740140e74453665dc3334832/nob.h#L205-L206. +#define UNUSED(value) (void)(value) +#define TODO(message) \ + do { \ + fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); \ + abort(); \ + } while (0) +#define UNREACHABLE(message) \ + do { \ + fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); \ + abort(); \ + } while (0) + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +#define STATIC_ASSERT(expr, msg) _Static_assert((expr), msg) +#else +#define STATIC_ASSERT(expr, msg) \ + typedef char static_assertion_##msg[(expr) ? 1 : -1] +#endif + +// String view - read-only, non-owning reference to a string +typedef struct { + const char *data; + size_t length; +} stringv; + +NONSTD_DEF stringv sv_from_cstr(const char *s); +NONSTD_DEF stringv sv_from_parts(const char *data, size_t length); +NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end); +NONSTD_DEF int sv_equals(stringv a, stringv b); +NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix); +NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix); + +// String builder - owning, mutable, dynamically growing string buffer +typedef struct { + char *data; + size_t length; + size_t capacity; +} stringb; + +NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap); +NONSTD_DEF void sb_free(stringb *sb); +NONSTD_DEF void sb_ensure(stringb *sb, size_t additional); +NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s); +NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv); +NONSTD_DEF void sb_append_char(stringb *sb, char c); +NONSTD_DEF stringv sb_as_sv(const stringb *sb); + +// Slice - generic non-owning view into an array +// Usage: SLICE_DEF(int); slice(int) view = ...; +#define SLICE_DEF(T) \ + typedef struct { \ + T *data; \ + size_t length; \ + } slice_##T + +#define slice(T) slice_##T + +#define make_slice(T, ptr, len) ((slice(T)){.data = (ptr), .length = (len)}) + +#define array_as_slice(T, arr) \ + ((slice(T)){.data = (arr).data, .length = (arr).length}) + +// Dynamic array - generic type-safe growable array using macros +// Usage: array(int) numbers; array_init(numbers); +#define array(T) \ + struct { \ + T *data; \ + size_t length; \ + size_t capacity; \ + } + +#define array_init(arr) \ + do { \ + (arr).capacity = 0; \ + (arr).data = NULL; \ + (arr).length = 0; \ + } while (0) + +#define array_init_cap(arr, initial_cap) \ + do { \ + (arr).capacity = (initial_cap) ? (initial_cap) : 16; \ + (arr).data = ALLOC(__typeof__(*(arr).data), (arr).capacity); \ + (arr).length = 0; \ + } while (0) + +#define array_free(arr) \ + do { \ + FREE((arr).data); \ + (arr).length = 0; \ + (arr).capacity = 0; \ + } while (0) + +#define array_ensure(arr, additional) \ + do { \ + size_t _needed = (arr).length + (additional); \ + if (_needed > (arr).capacity) { \ + size_t _new_cap = (arr).capacity ? (arr).capacity : 16; \ + while (_new_cap < _needed) { \ + if (_new_cap > SIZE_MAX / 2) { \ + _new_cap = SIZE_MAX; \ + break; \ + } \ + _new_cap *= 2; \ + } \ + if (_new_cap < _needed) { /* Overflow or OOM */ \ + break; \ + } \ + void *_new_data = \ + safe_realloc((arr).data, sizeof(*(arr).data), _new_cap); \ + if (_new_data) { \ + (arr).data = _new_data; \ + (arr).capacity = _new_cap; \ + } \ + } \ + } while (0) + +#define array_push(arr, value) \ + do { \ + array_ensure((arr), 1); \ + if ((arr).length < (arr).capacity) { \ + (arr).data[(arr).length++] = (value); \ + } \ + } while (0) + +#define array_pop(arr) ((arr).length > 0 ? (arr).data[--(arr).length] : 0) + +#define array_get(arr, index) ((arr).data[index]) + +#define array_set(arr, index, value) \ + do { \ + if ((index) < (arr).length) { \ + (arr).data[index] = (value); \ + } \ + } while (0) + +#define array_insert(arr, index, value) \ + do { \ + if ((index) <= (arr).length) { \ + array_ensure((arr), 1); \ + if ((arr).length < (arr).capacity) { \ + for (size_t _i = (arr).length; _i > (index); --_i) { \ + (arr).data[_i] = (arr).data[_i - 1]; \ + } \ + (arr).data[index] = (value); \ + (arr).length++; \ + } \ + } \ + } while (0) + +#define array_remove(arr, index) \ + do { \ + if ((index) < (arr).length) { \ + for (size_t _i = (index); _i < (arr).length - 1; ++_i) { \ + (arr).data[_i] = (arr).data[_i + 1]; \ + } \ + (arr).length--; \ + } \ + } while (0) + +#define array_clear(arr) \ + do { \ + (arr).length = 0; \ + } while (0) + +#define array_reserve(arr, new_capacity) \ + do { \ + if ((new_capacity) > (arr).capacity) { \ + void *_new_data = \ + safe_realloc((arr).data, sizeof(*(arr).data), (new_capacity)); \ + if (_new_data) { \ + (arr).data = _new_data; \ + (arr).capacity = (new_capacity); \ + } \ + } \ + } while (0) + +#define array_foreach(arr, var) \ + for (size_t _i_##var = 0; \ + _i_##var < (arr).length && ((var) = (arr).data[_i_##var], 1); \ + ++_i_##var) + +#define array_foreach_idx(arr, var, index) \ + for (size_t index = 0; \ + index < (arr).length && ((var) = (arr).data[index], 1); ++index) + +// Arena - block-based memory allocator +typedef struct { + char *ptr; + char *end; + array(char *) blocks; +} Arena; + +#define ARENA_DEFAULT_BLOCK_SIZE (4096) + +NONSTD_DEF Arena arena_make(void); +NONSTD_DEF void arena_grow(Arena *a, size_t min_size); +NONSTD_DEF void *arena_alloc(Arena *a, size_t size); +NONSTD_DEF void arena_free(Arena *a); + +// Image - simple RGB image structure +typedef struct { + u8 r, g, b; +} Color; + +typedef struct { + u32 width; + u32 height; + Color *pixels; +} Canvas; + +#define COLOR_RGB(r, g, b) ((Color){(u8)(r), (u8)(g), (u8)(b)}) +#define COLOR_HEX(hex) ((Color){(u8)(((hex) >> 16) & 0xFF), (u8)(((hex) >> 8) & 0xFF), (u8)((hex) & 0xFF)}) + +#define COLOR_BLACK COLOR_RGB(0, 0, 0) +#define COLOR_WHITE COLOR_RGB(255, 255, 255) +#define COLOR_RED COLOR_RGB(255, 0, 0) +#define COLOR_GREEN COLOR_RGB(0, 255, 0) +#define COLOR_BLUE COLOR_RGB(0, 0, 255) +#define COLOR_YELLOW COLOR_RGB(255, 255, 0) +#define COLOR_MAGENTA COLOR_RGB(255, 0, 255) +#define COLOR_CYAN COLOR_RGB(0, 255, 255) + +NONSTD_DEF Canvas ppm_init(u32 width, u32 height); +NONSTD_DEF void ppm_free(Canvas *img); +NONSTD_DEF void ppm_set_pixel(Canvas *img, u32 x, u32 y, Color color); +NONSTD_DEF Color ppm_get_pixel(const Canvas *img, u32 x, u32 y); +NONSTD_DEF int ppm_save(const Canvas *img, const char *filename); +NONSTD_DEF Canvas ppm_read(const char *filename); +NONSTD_DEF void ppm_fill(Canvas *canvas, Color color); +NONSTD_DEF void ppm_draw_rect(Canvas *canvas, u32 x, u32 y, u32 w, u32 h, Color color); +NONSTD_DEF void ppm_draw_line(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, Color color); +NONSTD_DEF void ppm_draw_circle(Canvas *canvas, i32 x, i32 y, i32 r, Color color); +NONSTD_DEF void ppm_draw_triangle(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, Color color); + +// File I/O helpers +NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size); +NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size); +NONSTD_DEF stringb read_entire_file_sb(const char *filepath); +NONSTD_DEF int write_file_sv(const char *filepath, stringv sv); +NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb); + +// Logging +typedef enum { + LOG_ERROR, + LOG_WARN, + LOG_INFO, + LOG_DEBUG, +} LogLevel; + +NONSTD_DEF void set_log_level(LogLevel level); +NONSTD_DEF LogLevel get_log_level_from_env(void); +NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...); + +#define LOG_INFO_MSG(...) log_message(stdout, LOG_INFO, __VA_ARGS__) +#define LOG_DEBUG_MSG(...) log_message(stdout, LOG_DEBUG, __VA_ARGS__) +#define LOG_WARN_MSG(...) log_message(stderr, LOG_WARN, __VA_ARGS__) +#define LOG_ERROR_MSG(...) log_message(stderr, LOG_ERROR, __VA_ARGS__) + +#define COLOR_RESET "\033[0m" +#define COLOR_INFO "\033[32m" +#define COLOR_DEBUG "\033[36m" +#define COLOR_WARNING "\033[33m" +#define COLOR_ERROR "\033[31m" + +#endif // NONSTD_H + +#ifdef NONSTD_IMPLEMENTATION + +NONSTD_DEF void *safe_malloc(size_t item_size, size_t count) { + if (count != 0 && item_size > SIZE_MAX / count) { + return NULL; + } + return malloc(item_size * count); +} + +NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count) { + if (count != 0 && item_size > SIZE_MAX / count) { + return NULL; + } + return realloc(ptr, item_size * count); +} + +// String View Implementation + +NONSTD_DEF stringv sv_from_cstr(const char *s) { + return (stringv){.data = s, .length = s ? strlen(s) : 0}; +} + +NONSTD_DEF stringv sv_from_parts(const char *data, size_t length) { + return (stringv){.data = data, .length = length}; +} + +NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end) { + if (start > sv.length) { + start = sv.length; + } + if (end > sv.length) { + end = sv.length; + } + if (start > end) { + start = end; + } + return (stringv){.data = sv.data + start, .length = end - start}; +} + +NONSTD_DEF int sv_equals(stringv a, stringv b) { + return a.length == b.length && (a.length == 0 || memcmp(a.data, b.data, a.length) == 0); +} + +NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix) { + return sv.length >= prefix.length && memcmp(sv.data, prefix.data, prefix.length) == 0; +} + +NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix) { + return sv.length >= suffix.length && memcmp(sv.data + sv.length - suffix.length, suffix.data, suffix.length) == 0; +} + +// String Builder Implementation + +NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap) { + sb->capacity = initial_cap ? initial_cap : 16; + sb->data = ALLOC(char, sb->capacity); + sb->length = 0; + if (sb->data) { + sb->data[0] = '\0'; + } +} + +NONSTD_DEF void sb_free(stringb *sb) { + FREE(sb->data); + sb->length = 0; + sb->capacity = 0; +} + +NONSTD_DEF void sb_ensure(stringb *sb, size_t additional) { + size_t needed = sb->length + additional + 1; + size_t new_cap = sb->capacity; + + if (needed > new_cap) { + while (new_cap < needed) { + if (new_cap > SIZE_MAX / 2) { + new_cap = SIZE_MAX; + break; + } + new_cap *= 2; + } + if (new_cap < needed) + return; // Overflow + + char *new_data = safe_realloc(sb->data, sizeof(char), new_cap); + if (new_data) { + sb->data = new_data; + sb->capacity = new_cap; + } + } +} + +NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s) { + if (!s) { + return; + } + size_t slength = strlen(s); + sb_ensure(sb, slength); + if (sb->length + slength + 1 <= sb->capacity) { + memcpy(sb->data + sb->length, s, slength); + sb->length += slength; + sb->data[sb->length] = '\0'; + } +} + +NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv) { + if (!sv.data || sv.length == 0) { + return; + } + sb_ensure(sb, sv.length); + if (sb->length + sv.length + 1 <= sb->capacity) { + memcpy(sb->data + sb->length, sv.data, sv.length); + sb->length += sv.length; + sb->data[sb->length] = '\0'; + } +} + +NONSTD_DEF void sb_append_char(stringb *sb, char c) { + sb_ensure(sb, 1); + if (sb->length + 2 <= sb->capacity) { + sb->data[sb->length++] = c; + sb->data[sb->length] = '\0'; + } +} + +NONSTD_DEF stringv sb_as_sv(const stringb *sb) { + return (stringv){.data = sb->data, .length = sb->length}; +} + +NONSTD_DEF Arena arena_make(void) { + Arena a = {0}; + array_init(a.blocks); + return a; +} + +// Arena Implementation + +NONSTD_DEF void arena_grow(Arena *a, size_t min_size) { + size_t size = MAX(ARENA_DEFAULT_BLOCK_SIZE, min_size); + char *block = ALLOC(char, size); + a->ptr = block; + a->end = block + size; + array_push(a->blocks, block); +} + +NONSTD_DEF void *arena_alloc(Arena *a, size_t size) { + // Align to 8 bytes basically + size_t align = sizeof(void *); + uintptr_t current = (uintptr_t)a->ptr; + uintptr_t aligned = (current + align - 1) & ~(align - 1); + uintptr_t end = (uintptr_t)a->end; + + // Check for overflow (aligned wrapped around) or out of bounds (aligned >= end) + // or not enough space ((end - aligned) < size) + if (aligned < current || aligned >= end || (end - aligned) < size) { + arena_grow(a, size); + current = (uintptr_t)a->ptr; + aligned = (current + align - 1) & ~(align - 1); + end = (uintptr_t)a->end; + } + + // Double check after grow (in case grow failed or size is just too huge) + if (aligned < current || aligned >= end || (end - aligned) < size) { + return NULL; + } + + a->ptr = (char *)(aligned + size); + return (void *)aligned; +} + +NONSTD_DEF void arena_free(Arena *a) { + char *block; + array_foreach(a->blocks, block) { FREE(block); } + array_free(a->blocks); + a->ptr = NULL; + a->end = NULL; +} + +// File I/O Implementation + +NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size) { + FILE *f = fopen(filepath, "rb"); + if (!f) { + return NULL; + } + + fseek(f, 0, SEEK_END); + long length = ftell(f); + fseek(f, 0, SEEK_SET); + + if (length < 0) { + fclose(f); + return NULL; + } + + size_t size = (size_t)length; + char *buffer = ALLOC(char, size + 1); + if (!buffer) { + fclose(f); + return NULL; + } + + size_t read = fread(buffer, 1, size, f); + fclose(f); + + if (read != size) { + FREE(buffer); + return NULL; + } + + buffer[size] = '\0'; + if (out_size) { + *out_size = size; + } + + return buffer; +} + +NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size) { + FILE *f = fopen(filepath, "wb"); + if (!f) { + return 0; + } + + size_t written = fwrite(data, 1, size, f); + fclose(f); + + return written == size; +} + +NONSTD_DEF stringb read_entire_file_sb(const char *filepath) { + size_t size = 0; + char *data = read_entire_file(filepath, &size); + stringb sb = {0}; + if (data) { + sb.data = data; + sb.length = size; + sb.capacity = size + 1; + } + return sb; +} + +NONSTD_DEF int write_file_sv(const char *filepath, stringv sv) { + return write_entire_file(filepath, sv.data, sv.length); +} + +NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb) { + return write_entire_file(filepath, sb->data, sb->length); +} + +// Logging Implementation + +static LogLevel max_level = LOG_INFO; + +static const char *level_strings[] = { + "ERROR", + "WARN", + "INFO", + "DEBUG", +}; + +static const char *level_colors[] = { + COLOR_ERROR, + COLOR_WARNING, + COLOR_INFO, + COLOR_DEBUG, +}; + +NONSTD_DEF void set_log_level(LogLevel level) { + max_level = level; +} + +NONSTD_DEF LogLevel get_log_level_from_env(void) { + const char *env = getenv("LOG_LEVEL"); + if (env) { + int level = atoi(env); + if (level >= 0 && level <= 3) { + return (LogLevel)level; + } + } + + return max_level; +} + +NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...) { + if (max_level < level) + return; + + struct timeval tv; + gettimeofday(&tv, NULL); + struct tm *tm_info = localtime(&tv.tv_sec); + + char time_str[24]; + strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info); + + const char *color = isatty(fileno(stream)) ? level_colors[level] : ""; + const char *reset = isatty(fileno(stream)) ? COLOR_RESET : ""; + + const char *log_format = "%s[%s.%03d] [%-5s] "; + fprintf(stream, log_format, color, time_str, (int)(tv.tv_usec / 1000), level_strings[level]); + + va_list args; + va_start(args, format); + vfprintf(stream, format, args); + va_end(args); + + fprintf(stream, "%s\n", reset); + fflush(stream); +} + +// PPM Image Implementation + +NONSTD_DEF Canvas ppm_init(u32 width, u32 height) { + Canvas img = {0}; + img.width = width; + img.height = height; + img.pixels = ALLOC(Color, width * height); + if (img.pixels) { + memset(img.pixels, 0, sizeof(Color) * width * height); + } + return img; +} + +NONSTD_DEF void ppm_free(Canvas *img) { + if (img->pixels) { + FREE(img->pixels); + } + img->width = 0; + img->height = 0; +} + +NONSTD_DEF void ppm_set_pixel(Canvas *img, u32 x, u32 y, Color color) { + if (x < img->width && y < img->height) { + img->pixels[y * img->width + x] = color; + } +} + +NONSTD_DEF Color ppm_get_pixel(const Canvas *img, u32 x, u32 y) { + if (x < img->width && y < img->height) { + return img->pixels[y * img->width + x]; + } + return (Color){0, 0, 0}; +} + +NONSTD_DEF int ppm_save(const Canvas *img, const char *filename) { + FILE *f = fopen(filename, "w"); + if (!f) { + return 0; + } + + fprintf(f, "P3\n%u %u\n255\n", img->width, img->height); + for (u32 y = 0; y < img->height; ++y) { + for (u32 x = 0; x < img->width; ++x) { + Color c = ppm_get_pixel(img, x, y); + fprintf(f, "%d %d %d ", c.r, c.g, c.b); + } + fprintf(f, "\n"); + } + + fclose(f); + return 1; +} + +NONSTD_DEF Canvas ppm_read(const char *filename) { + Canvas img = {0}; + FILE *f = fopen(filename, "r"); + if (!f) { + return img; + } + + char magic[3]; + if (fscanf(f, "%2s", magic) != 1 || strcmp(magic, "P3") != 0) { + fclose(f); + return img; + } + + u32 w, h, max_val; + if (fscanf(f, "%u %u %u", &w, &h, &max_val) != 3) { + fclose(f); + return img; + } + + img = ppm_init(w, h); + if (!img.pixels) { + fclose(f); + return img; + } + + for (u32 i = 0; i < w * h; ++i) { + int r, g, b; + if (fscanf(f, "%d %d %d", &r, &g, &b) != 3) { + ppm_free(&img); + fclose(f); + return (Canvas){0}; + } + img.pixels[i] = (Color){(u8)r, (u8)g, (u8)b}; + } + + fclose(f); + return img; +} + +NONSTD_DEF void ppm_fill(Canvas *canvas, Color color) { + for (u32 i = 0; i < canvas->width * canvas->height; ++i) { + canvas->pixels[i] = color; + } +} + +NONSTD_DEF void ppm_draw_rect(Canvas *canvas, u32 x, u32 y, u32 w, u32 h, Color color) { + if (w == 0 || h == 0) { + return; + } + for (u32 i = x; i < x + w; ++i) { + ppm_set_pixel(canvas, i, y, color); + ppm_set_pixel(canvas, i, y + h - 1, color); + } + for (u32 j = y; j < y + h; ++j) { + ppm_set_pixel(canvas, x, j, color); + ppm_set_pixel(canvas, x + w - 1, j, color); + } +} + +NONSTD_DEF void ppm_draw_line(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, Color color) { + i32 dx = abs(x1 - x0); + i32 dy = -abs(y1 - y0); + i32 sx = x0 < x1 ? 1 : -1; + i32 sy = y0 < y1 ? 1 : -1; + i32 err = dx + dy; + + while (1) { + ppm_set_pixel(canvas, (u32)x0, (u32)y0, color); + if (x0 == x1 && y0 == y1) { + break; + } + + i32 e2 = 2 * err; + if (e2 >= dy) { + err += dy; + x0 += sx; + } + if (e2 <= dx) { + err += dx; + y0 += sy; + } + } +} + +NONSTD_DEF void ppm_draw_circle(Canvas *canvas, i32 xm, i32 ym, i32 r, Color color) { + i32 x = -r, y = 0, err = 2 - 2 * r; + do { + ppm_set_pixel(canvas, (u32)(xm - x), (u32)(ym + y), color); + ppm_set_pixel(canvas, (u32)(xm - y), (u32)(ym - x), color); + ppm_set_pixel(canvas, (u32)(xm + x), (u32)(ym - y), color); + ppm_set_pixel(canvas, (u32)(xm + y), (u32)(ym + x), color); + r = err; + if (r <= y) { + err += ++y * 2 + 1; + } + if (r > x || err > y) { + err += ++x * 2 + 1; + } + } while (x < 0); +} + +NONSTD_DEF void ppm_draw_triangle(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, Color color) { + ppm_draw_line(canvas, x0, y0, x1, y1, color); + ppm_draw_line(canvas, x1, y1, x2, y2, color); + ppm_draw_line(canvas, x2, y2, x0, y0, color); +} + +#endif // NONSTD_IMPLEMENTATION + +/* +BSD 2-Clause License + +Copyright (c) 2026, Mitja Felicijan <mitja.felicijan@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/
\ No newline at end of file @@ -11,6 +11,10 @@ #define MAX_TOKENS 512 #define MAX_TOKEN_LEN 32 +typedef struct { + +} Engine; + static const char *refusal_text = "I don't have that information."; static void llama_log_callback(enum ggml_log_level level, const char *text, void *user_data) { @@ -58,7 +62,7 @@ static int collect_tokens(const char *text, char tokens[MAX_TOKENS][MAX_TOKEN_LE buf[len] = '\0'; if (len >= 4 && !is_stopword(buf, (size_t)len)) { if (!token_exists(tokens, count, buf) && count < MAX_TOKENS) { - strncpy(tokens[count], buf, MAX_TOKEN_LEN - 1); + memcpy(tokens[count], buf, (size_t)len + 1); tokens[count][MAX_TOKEN_LEN - 1] = '\0'; count++; } @@ -412,12 +416,16 @@ static void show_help(const char *prog) { printf("Options:\n"); printf(" -m, --model <name> Specify model to use (default: first model)\n"); printf(" -p, --prompt <text> Specify prompt text (default: \"What is 2+2?\")\n"); + printf(" -b, --build <file> Specify context file\n"); printf(" -c, --context <text> Specify context file\n"); printf(" -v, --verbose Enable verbose logging\n"); printf(" -h, --help Show this help message\n"); } int main(int argc, char **argv) { + /* Engine engine = {}; */ + + const char *model_name = NULL; const char *prompt = NULL; const char *context_file = NULL; @@ -429,6 +437,7 @@ int main(int argc, char **argv) { {"model", required_argument, 0, 'm'}, {"prompt", required_argument, 0, 'p'}, {"context", required_argument, 0, 'c'}, + {"build", required_argument, 0, 'b'}, {"verbose", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} @@ -1,33 +1,38 @@ #include <stdio.h> #include <string.h> #include <math.h> +#include <stdint.h> #include "llama.h" #include "vectordb.h" +#include "nonstd.h" + +#define VDB_MAGIC 0x31424456u /* "VDB1" */ +#define VDB_VERSION 1u + +typedef struct { + uint32_t magic; + uint32_t version; + uint32_t embed_size; + uint32_t max_text; + uint32_t count; +} VdbFileHeader; static float cosine_similarity(float *a, float *b, int n) { - float dot = 0, normA = 0, normB = 0; + float dot = 0, norm_a = 0, norm_b = 0; for (int i = 0; i < n; i++) { dot += a[i] * b[i]; - normA += a[i] * a[i]; - normB += b[i] * b[i]; + norm_a += a[i] * a[i]; + norm_b += b[i] * b[i]; } - return dot / (sqrtf(normA) * sqrtf(normB) + 1e-8f); + return dot / (sqrtf(norm_a) * sqrtf(norm_b) + 1e-8f); } static void embed_text(struct llama_context *ctx, const char *text, float *out) { llama_token tokens[512]; const struct llama_model *model = llama_get_model(ctx); const struct llama_vocab *vocab = llama_model_get_vocab(model); - int n_tokens = llama_tokenize( - vocab, - text, - strlen(text), - tokens, - 512, - true, - true - ); + int n_tokens = llama_tokenize(vocab, text, strlen(text), tokens, 512, true, true); if (n_tokens < 0) { return; } @@ -46,12 +51,12 @@ void vdb_init(VectorDB *db, struct llama_context *embed_ctx) { } void vdb_free(VectorDB *db) { - (void)db; // nothing yet (future persistence etc.) + (void)db; } void vdb_add_document(VectorDB *db, const char *text) { if (db->count >= VDB_MAX_DOCS) { - printf("VectorDB full!\n"); + log_message(stdout, LOG_INFO, "Vector database full"); return; } @@ -59,7 +64,7 @@ void vdb_add_document(VectorDB *db, const char *text) { strncpy(doc->text, text, VDB_MAX_TEXT - 1); doc->text[VDB_MAX_TEXT - 1] = 0; - printf("Embedding doc %d...\n", db->count); + log_message(stdout, LOG_INFO, "Embedding doc %d...", db->count); embed_text(db->embed_ctx, text, doc->embedding); } @@ -90,3 +95,84 @@ void vdb_search(VectorDB *db, float *query, int top_k, int *results) { } } } + +int vdb_save(const VectorDB *db, const char *path) { + FILE *fp = fopen(path, "wb"); + if (!fp) { + return 1; + } + + VdbFileHeader header = { + .magic = VDB_MAGIC, + .version = VDB_VERSION, + .embed_size = VDB_EMBED_SIZE, + .max_text = VDB_MAX_TEXT, + .count = (uint32_t)db->count, + }; + + if (fwrite(&header, sizeof(header), 1, fp) != 1) { + fclose(fp); + return 2; + } + + if (db->count > 0) { + size_t wrote = fwrite(db->docs, sizeof(VectorDoc), (size_t)db->count, fp); + if (wrote != (size_t)db->count) { + fclose(fp); + return 3; + } + } + + if (fclose(fp) != 0) { + return 4; + } + + return 0; +} + +int vdb_load(VectorDB *db, const char *path) { + struct llama_context *ctx = db->embed_ctx; + FILE *fp = fopen(path, "rb"); + if (!fp) { + return -1; + } + + VdbFileHeader header = {0}; + if (fread(&header, sizeof(header), 1, fp) != 1) { + fclose(fp); + return -2; + } + + if (header.magic != VDB_MAGIC || header.version != VDB_VERSION) { + fclose(fp); + return -3; + } + + if (header.embed_size != VDB_EMBED_SIZE || header.max_text != VDB_MAX_TEXT) { + fclose(fp); + return -4; + } + + if (header.count > VDB_MAX_DOCS) { + fclose(fp); + return -5; + } + + memset(db, 0, sizeof(VectorDB)); + db->embed_ctx = ctx; + db->count = (int)header.count; + + if (db->count > 0) { + size_t read = fread(db->docs, sizeof(VectorDoc), (size_t)db->count, fp); + if (read != (size_t)db->count) { + fclose(fp); + return -6; + } + } + + if (fclose(fp) != 0) { + return -7; + } + + return 0; +} @@ -26,4 +26,7 @@ void vdb_add_document(VectorDB *db, const char *text); void vdb_embed_query(VectorDB *db, const char *text, float *out_embedding); void vdb_search(VectorDB *db, float *query_embedding, int top_k, int *results); +int vdb_save(const VectorDB *db, const char *path); +int vdb_load(VectorDB *db, const char *path); + #endif |
