diff --git a/Makefile b/Makefile index bb202379..290a101e 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,8 @@ LIB_C := h3.c h3_host.c h3_safetensors.c h3_weights.c h3_text_encoder.c \ LIB_C += h3_video_vae.c h3_video_encoder.c h3_audio_vae.c h3_ffmpeg.c \ h3_terminal.c h3_vision_encoder.c h3_multimodal.c LIB_M := h3_metal.m h3_gpu.m h3_tokenizer.m -LIB_OBJ := $(LIB_C:.c=.o) $(LIB_M:.m=.o) +LIB_S := h3_shaders_embedded.S +LIB_OBJ := $(LIB_C:.c=.o) $(LIB_M:.m=.o) $(LIB_S:.S=.o) CLI_OBJ := main.o h3_cli.o linenoise.o .PHONY: all test parity real-parity clean @@ -193,6 +194,11 @@ real-parity: h3_real_prompt_test h3_real_dit_block_test %.o: %.m $(CC) $(OBJCFLAGS) -I. -c $< -o $@ +%.o: %.S + $(CC) -c $< -o $@ + +h3_shaders_embedded.o: h3_shaders.metal + tests/%.o: tests/%.c $(CC) $(CFLAGS) -I. -c $< -o $@ diff --git a/README.md b/README.md index 4750ac49..e8ba31c1 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,14 @@ mkdir -p outputs mapping all weights or generating media. Run `./h3 --help` for the complete CLI reference. +The Metal shader source is embedded in the executable and compiled at runtime, +so `h3` can be installed as a standalone binary: + +```sh +mkdir -p "$HOME/bin" +install -m 755 h3 "$HOME/bin/h3" +``` + Without `-p`, the same binary starts an Iris-style interactive session: ```sh diff --git a/h3_gpu.h b/h3_gpu.h index 3a47cc35..5a4e323b 100644 --- a/h3_gpu.h +++ b/h3_gpu.h @@ -32,6 +32,8 @@ typedef struct { double gpu_seconds; } h3_gpu_stats; +/* Pass NULL to compile the shader source embedded in the library. The default + * source filename also falls back to the embedded copy when it is absent. */ h3_gpu *h3_gpu_create(const char *shader_source_path, char *error, size_t error_size); void h3_gpu_free(h3_gpu *gpu); diff --git a/h3_gpu.m b/h3_gpu.m index c61d04c6..43ae29da 100644 --- a/h3_gpu.m +++ b/h3_gpu.m @@ -325,6 +325,17 @@ static int h3_gpu_dispatch_rows(H3GPU *gpu, NSString *name, uint32_t rows, return 1; } +extern const unsigned char h3_shaders_embedded_start[]; +extern const unsigned char h3_shaders_embedded_end[]; + +static NSString *h3_gpu_embedded_shader_source(void) { + size_t length = (size_t)(h3_shaders_embedded_end - + h3_shaders_embedded_start); + return [[NSString alloc] initWithBytes:h3_shaders_embedded_start + length:length + encoding:NSUTF8StringEncoding]; +} + h3_gpu *h3_gpu_create(const char *shader_source_path, char *error, size_t error_size) { @autoreleasepool { @@ -350,13 +361,21 @@ static int h3_gpu_dispatch_rows(H3GPU *gpu, NSString *name, uint32_t rows, "%.3f GiB\n", (double)gpu.device.currentAllocatedSize / (1024.0 * 1024.0 * 1024.0)); } - const char *source_path = shader_source_path ? shader_source_path : - "h3_shaders.metal"; - NSString *path = [NSString stringWithUTF8String:source_path]; NSError *libraryError = nil; - NSString *source = [NSString stringWithContentsOfFile:path - encoding:NSUTF8StringEncoding - error:&libraryError]; + NSString *source = nil; + NSString *sourceName = @"embedded h3_shaders.metal"; + if (shader_source_path) { + sourceName = [NSString stringWithUTF8String:shader_source_path]; + source = [NSString stringWithContentsOfFile:sourceName + encoding:NSUTF8StringEncoding + error:&libraryError]; + } + if (!source && (!shader_source_path || + !strcmp(shader_source_path, "h3_shaders.metal"))) { + source = h3_gpu_embedded_shader_source(); + sourceName = @"embedded h3_shaders.metal"; + libraryError = nil; + } if (source) { MTLCompileOptions *options = [[MTLCompileOptions alloc] init]; options.mathMode = MTLMathModeSafe; @@ -397,7 +416,8 @@ static int h3_gpu_dispatch_rows(H3GPU *gpu, NSString *name, uint32_t rows, if (error && error_size) { const char *description = libraryError.localizedDescription.UTF8String; snprintf(error, error_size, "cannot compile %s: %s", - source_path, description ? description : "unknown error"); + sourceName.UTF8String, + description ? description : "unknown error"); } return NULL; } diff --git a/h3_shaders_embedded.S b/h3_shaders_embedded.S new file mode 100644 index 00000000..6413d52f --- /dev/null +++ b/h3_shaders_embedded.S @@ -0,0 +1,9 @@ +.section __TEXT,__const +.p2align 4 + +.globl _h3_shaders_embedded_start +_h3_shaders_embedded_start: +.incbin "h3_shaders.metal" + +.globl _h3_shaders_embedded_end +_h3_shaders_embedded_end: diff --git a/tests/test_audio_gpu.c b/tests/test_audio_gpu.c index f8e9db05..92c1b83e 100644 --- a/tests/test_audio_gpu.c +++ b/tests/test_audio_gpu.c @@ -135,7 +135,7 @@ static void activation_reference(float *output, const float *input, int main(void) { test_context test = {0}; char error[512]; - test.gpu = h3_gpu_create("h3_shaders.metal", error, sizeof(error)); + test.gpu = h3_gpu_create(NULL, error, sizeof(error)); if (!test.gpu) die(error); const float input_values[] = {0.2f, -0.3f, 0.5f, 0.1f,