Text Generation
Transformers
Safetensors
GGUF
llama
chatbot
multilingual
arabic
french
tamazight
english
conversational
text-generation-inference
4-bit precision
bitsandbytes
Instructions to use kaisser/LLM-Maroc with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use kaisser/LLM-Maroc with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="kaisser/LLM-Maroc") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("kaisser/LLM-Maroc") model = AutoModelForCausalLM.from_pretrained("kaisser/LLM-Maroc", device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use kaisser/LLM-Maroc with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf kaisser/LLM-Maroc:BF16 # Run inference directly in the terminal: llama cli -hf kaisser/LLM-Maroc:BF16
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf kaisser/LLM-Maroc:BF16 # Run inference directly in the terminal: llama cli -hf kaisser/LLM-Maroc:BF16
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf kaisser/LLM-Maroc:BF16 # Run inference directly in the terminal: ./llama-cli -hf kaisser/LLM-Maroc:BF16
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf kaisser/LLM-Maroc:BF16 # Run inference directly in the terminal: ./build/bin/llama-cli -hf kaisser/LLM-Maroc:BF16
Use Docker
docker model run hf.co/kaisser/LLM-Maroc:BF16
- LM Studio
- Jan
- vLLM
How to use kaisser/LLM-Maroc with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "kaisser/LLM-Maroc" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "kaisser/LLM-Maroc", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/kaisser/LLM-Maroc:BF16
- SGLang
How to use kaisser/LLM-Maroc with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "kaisser/LLM-Maroc" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "kaisser/LLM-Maroc", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "kaisser/LLM-Maroc" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "kaisser/LLM-Maroc", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Ollama
How to use kaisser/LLM-Maroc with Ollama:
ollama run hf.co/kaisser/LLM-Maroc:BF16
- Unsloth Studio
How to use kaisser/LLM-Maroc with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for kaisser/LLM-Maroc to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for kaisser/LLM-Maroc to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for kaisser/LLM-Maroc to start chatting
- Docker Model Runner
How to use kaisser/LLM-Maroc with Docker Model Runner:
docker model run hf.co/kaisser/LLM-Maroc:BF16
- Lemonade
How to use kaisser/LLM-Maroc with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull kaisser/LLM-Maroc:BF16
Run and chat with the model
lemonade run user.LLM-Maroc-BF16
List all available models
lemonade list
- Atomic Chat
| /* Constants */ | |
| /* End Constants */ | |
| // This is a "fake" base pointer, since WebGPU buffers do not have pointers to their locations. | |
| static void * const webgpu_ptr_base = (void *)(uintptr_t) 0x1000; // NOLINT | |
| // Always returns the base offset of a tensor, regardless of views. | |
| static uint64_t webgpu_tensor_offset(const ggml_tensor * tensor) { | |
| if (tensor->view_src) { | |
| return (uint8_t *) tensor->view_src->data - (uint8_t *) webgpu_ptr_base; | |
| } | |
| return (uint8_t *) tensor->data - (uint8_t *) webgpu_ptr_base; | |
| } | |
| /* Struct definitions */ | |
| // All the base objects needed to run operations on a WebGPU device | |
| struct webgpu_context_struct { | |
| wgpu::Instance instance; | |
| wgpu::Adapter adapter; | |
| wgpu::Device device; | |
| wgpu::Queue queue; | |
| wgpu::Limits limits; | |
| wgpu::SupportedFeatures features; | |
| std::mutex mutex; | |
| bool device_initialized = false; | |
| // pipelines and parameter buffers | |
| // TODO: reuse params buffers for different pipelines when possible | |
| wgpu::ComputePipeline memset_pipeline; | |
| wgpu::Buffer memset_params_dev_buf; | |
| wgpu::Buffer memset_params_host_buf; | |
| wgpu::ComputePipeline mul_mat_pipeline; | |
| wgpu::Buffer mul_mat_params_dev_buf; | |
| wgpu::Buffer mul_mat_params_host_buf; | |
| wgpu::ComputePipeline cpy_pipeline; | |
| wgpu::Buffer cpy_params_dev_buf; | |
| wgpu::Buffer cpy_params_host_buf; | |
| size_t memset_bytes_per_thread; | |
| // Staging buffer for reading data from the GPU | |
| wgpu::Buffer get_tensor_staging_buf; | |
| }; | |
| typedef std::shared_ptr<webgpu_context_struct> webgpu_context; | |
| struct ggml_backend_webgpu_reg_context { | |
| webgpu_context webgpu_ctx; | |
| size_t device_count; | |
| const char * name; | |
| }; | |
| struct ggml_backend_webgpu_device_context { | |
| webgpu_context webgpu_ctx; | |
| std::string device_name; | |
| std::string device_desc; | |
| }; | |
| struct ggml_backend_webgpu_context { | |
| webgpu_context webgpu_ctx; | |
| std::string name; | |
| }; | |
| struct ggml_backend_webgpu_buffer_context { | |
| webgpu_context webgpu_ctx; | |
| wgpu::Buffer buffer; | |
| ggml_backend_webgpu_buffer_context(webgpu_context ctx, wgpu::Buffer buf) : | |
| webgpu_ctx(ctx), buffer(buf) { | |
| } | |
| }; | |
| /* End struct definitions */ | |
| /* WebGPU object initializations */ | |
| static void ggml_webgpu_create_pipeline(wgpu::Device &device, wgpu::ComputePipeline &pipeline, const char * shader_code, const char * label, const std::vector<wgpu::ConstantEntry> &constants = {}) { | |
| WEBGPU_LOG_DEBUG("ggml_webgpu_create_pipeline()"); | |
| wgpu::ShaderSourceWGSL shader_source; | |
| shader_source.code = shader_code; | |
| wgpu::ShaderModuleDescriptor shader_desc; | |
| shader_desc.nextInChain = &shader_source; | |
| wgpu::ShaderModule shader_module = device.CreateShaderModule(&shader_desc); | |
| wgpu::ComputePipelineDescriptor pipeline_desc; | |
| pipeline_desc.label = label; | |
| pipeline_desc.compute.module = shader_module; | |
| pipeline_desc.compute.entryPoint = "main"; // Entry point in the WGSL code | |
| pipeline_desc.layout = nullptr; // nullptr means auto layout | |
| if (constants.size() > 0) { | |
| pipeline_desc.compute.constants = constants.data(); | |
| pipeline_desc.compute.constantCount = constants.size(); | |
| } | |
| pipeline = device.CreateComputePipeline(&pipeline_desc); | |
| } | |
| static void ggml_webgpu_create_buffer(wgpu::Device &device, wgpu::Buffer &buffer, size_t size, wgpu::BufferUsage usage, const char* label) { | |
| WEBGPU_LOG_DEBUG("ggml_webgpu_create_buffer()"); | |
| wgpu::BufferDescriptor buffer_desc; | |
| buffer_desc.size = size; | |
| buffer_desc.usage = usage; | |
| buffer_desc.label = label; | |
| buffer_desc.mappedAtCreation = false; | |
| // TODO: error handling | |
| buffer = device.CreateBuffer(&buffer_desc); | |
| } | |
| /** End WebGPU object initializations */ | |
| /** WebGPU Actions */ | |
| static void ggml_backend_webgpu_map_buffer(webgpu_context ctx, wgpu::Buffer buffer, wgpu::MapMode mode, size_t offset, size_t size) { | |
| ctx->instance.WaitAny(buffer.MapAsync( | |
| mode, offset, size, wgpu::CallbackMode::WaitAnyOnly, | |
| [](wgpu::MapAsyncStatus status, wgpu::StringView message) { | |
| if (status != wgpu::MapAsyncStatus::Success) { | |
| GGML_LOG_ERROR("ggml_webgpu: Failed to map buffer: %s\n", message.data); | |
| } | |
| }), | |
| UINT64_MAX | |
| ); | |
| } | |
| static void ggml_backend_webgpu_buffer_memset(webgpu_context ctx, wgpu::Buffer buf, uint32_t value, size_t offset, size_t size) { | |
| std::lock_guard<std::mutex> lock(ctx->mutex); | |
| wgpu::Device device = ctx->device; | |
| // map the host parameters buffer | |
| ggml_backend_webgpu_map_buffer(ctx, ctx->memset_params_host_buf, wgpu::MapMode::Write, 0, ctx->memset_params_host_buf.GetSize()); | |
| uint32_t * params = (uint32_t *) ctx->memset_params_host_buf.GetMappedRange(); | |
| params[0] = (uint32_t)offset; | |
| params[1] = (uint32_t)size; | |
| params[2] = value; | |
| ctx->memset_params_host_buf.Unmap(); | |
| wgpu::BindGroupEntry entries[2]; | |
| entries[0].binding = 0; // binding for the buffer to memset | |
| entries[0].buffer = buf; | |
| entries[0].offset = 0; | |
| entries[0].size = buf.GetSize(); | |
| entries[1].binding = 1; // binding for the parameters | |
| entries[1].buffer = ctx->memset_params_dev_buf; | |
| entries[1].offset = 0; | |
| entries[1].size = ctx->memset_params_dev_buf.GetSize(); | |
| wgpu::BindGroupDescriptor bind_group_desc; | |
| bind_group_desc.layout = ctx->memset_pipeline.GetBindGroupLayout(0); | |
| bind_group_desc.entryCount = 2; | |
| bind_group_desc.label = "ggml_memset"; | |
| bind_group_desc.entries = entries; | |
| wgpu::BindGroup bind_group = device.CreateBindGroup(&bind_group_desc); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.CopyBufferToBuffer( | |
| ctx->memset_params_host_buf, 0, | |
| ctx->memset_params_dev_buf, 0, | |
| ctx->memset_params_dev_buf.GetSize() | |
| ); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetPipeline(ctx->memset_pipeline); | |
| pass.SetBindGroup(0, bind_group); | |
| size_t bytes_per_wg = ctx->limits.maxComputeWorkgroupSizeX * ctx->memset_bytes_per_thread; | |
| pass.DispatchWorkgroups(((size + 3) + bytes_per_wg - 1) / bytes_per_wg, 1, 1); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| ctx->queue.Submit(1, &commands); | |
| } | |
| static void ggml_backend_webgpu_wait_on_submission(webgpu_context ctx) { | |
| // Wait for the queue to finish processing all commands | |
| ctx->instance.WaitAny(ctx->queue.OnSubmittedWorkDone(wgpu::CallbackMode::WaitAnyOnly, | |
| [](wgpu::QueueWorkDoneStatus status, wgpu::StringView message) { | |
| if (status != wgpu::QueueWorkDoneStatus::Success) { | |
| GGML_LOG_ERROR("ggml_webgpu: Failed to wait on queue: %s\n", message.data); | |
| } | |
| }), | |
| UINT64_MAX | |
| ); | |
| } | |
| /** End WebGPU Actions */ | |
| /** GGML Backend Interface */ | |
| static const char * ggml_backend_webgpu_name(ggml_backend_t backend) { | |
| ggml_backend_webgpu_context * ctx = (ggml_backend_webgpu_context *)backend->context; | |
| return ctx->name.c_str(); | |
| } | |
| static void ggml_backend_webgpu_free(ggml_backend_t backend) { | |
| ggml_backend_webgpu_context * ctx = (ggml_backend_webgpu_context *)backend->context; | |
| WEBGPU_LOG_DEBUG("ggml_backend_webgpu_free(" << ctx->name << ")"); | |
| // TODO: cleanup | |
| GGML_UNUSED(ctx); | |
| } | |
| // Returns true if node has enqueued work into the queue, false otherwise | |
| static bool ggml_webgpu_encode_node(webgpu_context ctx, ggml_tensor * node){ | |
| if (ggml_is_empty(node)) { | |
| return false; | |
| } | |
| WEBGPU_LOG_DEBUG("ggml_webgpu_encode_node(" << node << ", " << ggml_op_name(node->op) << ")"); | |
| switch (node->op) { | |
| // no-ops | |
| case GGML_OP_NONE: | |
| case GGML_OP_VIEW: | |
| case GGML_OP_PERMUTE: | |
| return false; | |
| case GGML_OP_CPY: { | |
| std::lock_guard<std::mutex> lock(ctx->mutex); | |
| const ggml_tensor * src = node->src[0]; | |
| ggml_backend_webgpu_buffer_context * src_ctx = (ggml_backend_webgpu_buffer_context *) src->buffer->context; | |
| size_t src_offset = webgpu_tensor_offset(src) + src->view_offs; | |
| // assumes power of 2 offset alignment | |
| size_t src_misalignment = src_offset & (ctx->limits.minStorageBufferOffsetAlignment - 1); | |
| // align to minimum offset alignment | |
| src_offset &= ~(ctx->limits.minStorageBufferOffsetAlignment - 1); | |
| ggml_backend_webgpu_buffer_context * dst_ctx = (ggml_backend_webgpu_buffer_context *) node->buffer->context; | |
| size_t dst_offset = webgpu_tensor_offset(node) + node->view_offs; | |
| size_t dst_misalignment = dst_offset & (ctx->limits.minStorageBufferOffsetAlignment - 1); | |
| dst_offset &= ~(ctx->limits.minStorageBufferOffsetAlignment - 1); | |
| wgpu::Device device = ctx->device; | |
| ggml_backend_webgpu_map_buffer(ctx, ctx->cpy_params_host_buf, | |
| wgpu::MapMode::Write, 0, ctx->cpy_params_host_buf.GetSize()); | |
| uint32_t * params = (uint32_t *) ctx->cpy_params_host_buf.GetMappedRange(); | |
| uint32_t ne = (uint32_t)ggml_nelements(node); | |
| params[0] = ne; | |
| params[1] = src_misalignment/ggml_type_size(src->type); | |
| params[2] = dst_misalignment/ggml_type_size(node->type); | |
| // Convert byte-strides to element-strides | |
| params[3] = (uint32_t)src->nb[0]/ggml_type_size(src->type); | |
| params[4] = (uint32_t)src->nb[1]/ggml_type_size(src->type); | |
| params[5] = (uint32_t)src->nb[2]/ggml_type_size(src->type); | |
| params[6] = (uint32_t)src->nb[3]/ggml_type_size(src->type); | |
| params[7] = (uint32_t)node->nb[0]/ggml_type_size(node->type); | |
| params[8] = (uint32_t)node->nb[1]/ggml_type_size(node->type); | |
| params[9] = (uint32_t)node->nb[2]/ggml_type_size(node->type); | |
| params[10] = (uint32_t)node->nb[3]/ggml_type_size(node->type); | |
| // Logical shape — same for both tensors even if permuted | |
| params[11] = (uint32_t)(src->ne[0]); | |
| params[12] = (uint32_t)(src->ne[1]); | |
| params[13] = (uint32_t)(src->ne[2]); | |
| params[14] = (uint32_t)(src->ne[3]); | |
| ctx->cpy_params_host_buf.Unmap(); | |
| wgpu::BindGroupEntry entries[3]; | |
| entries[0].binding = 0; | |
| entries[0].buffer = src_ctx->buffer; | |
| entries[0].offset = src_offset; | |
| entries[0].size = (ggml_nbytes(src) + src_misalignment + WEBGPU_STORAGE_BUF_BINDING_MULT - 1) & ~(WEBGPU_STORAGE_BUF_BINDING_MULT - 1); | |
| entries[1].binding = 1; | |
| entries[1].buffer = dst_ctx->buffer; | |
| entries[1].offset = dst_offset; | |
| entries[1].size = (ggml_nbytes(node) + dst_misalignment + WEBGPU_STORAGE_BUF_BINDING_MULT - 1) & ~(WEBGPU_STORAGE_BUF_BINDING_MULT - 1); | |
| entries[2].binding = 2; | |
| entries[2].buffer = ctx->cpy_params_dev_buf; | |
| entries[2].offset = 0; | |
| entries[2].size = ctx->cpy_params_dev_buf.GetSize(); | |
| wgpu::BindGroupDescriptor bind_group_desc; | |
| bind_group_desc.layout = ctx->cpy_pipeline.GetBindGroupLayout(0); | |
| bind_group_desc.label = "ggml_op_cpy"; | |
| bind_group_desc.entryCount = 3; | |
| bind_group_desc.entries = entries; | |
| wgpu::BindGroup bind_group = device.CreateBindGroup(&bind_group_desc); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.CopyBufferToBuffer( | |
| ctx->cpy_params_host_buf, 0, | |
| ctx->cpy_params_dev_buf, 0, | |
| ctx->cpy_params_dev_buf.GetSize() | |
| ); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetPipeline(ctx->cpy_pipeline); | |
| pass.SetBindGroup(0, bind_group); | |
| size_t max_wg_size = ctx->limits.maxComputeWorkgroupSizeX; | |
| pass.DispatchWorkgroups((ne + max_wg_size - 1) / max_wg_size); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| // TODO, don't submit here, batch submissions | |
| ctx->queue.Submit(1, &commands); | |
| // TODO, don't wait on submission here | |
| ggml_backend_webgpu_wait_on_submission(ctx); | |
| return true; | |
| } | |
| case GGML_OP_MUL_MAT: | |
| { | |
| const ggml_tensor * src0 = node->src[0]; | |
| ggml_backend_webgpu_buffer_context * src0_ctx = (ggml_backend_webgpu_buffer_context *) src0->buffer->context; | |
| size_t src0_offset = webgpu_tensor_offset(src0) + src0->view_offs; | |
| const ggml_tensor * src1 = node->src[1]; | |
| ggml_backend_webgpu_buffer_context * src1_ctx = (ggml_backend_webgpu_buffer_context *) src1->buffer->context; | |
| size_t src1_offset = webgpu_tensor_offset(src1) + src1->view_offs; | |
| ggml_backend_webgpu_buffer_context * dst_ctx = (ggml_backend_webgpu_buffer_context *) node->buffer->context; | |
| size_t dst_offset = webgpu_tensor_offset(node) + node->view_offs; | |
| wgpu::Device device = ctx->device; | |
| // map the host parameters buffer | |
| ggml_backend_webgpu_map_buffer(ctx, ctx->mul_mat_params_host_buf, | |
| wgpu::MapMode::Write, 0, ctx->mul_mat_params_host_buf.GetSize()); | |
| uint32_t * params = (uint32_t *) ctx->mul_mat_params_host_buf.GetMappedRange(); | |
| params[0] = (uint32_t)node->ne[1]; // number of rows in result (M) | |
| params[1] = (uint32_t)node->ne[0]; // number of columns in result (N) | |
| params[2] = (uint32_t)src0->ne[0]; // number of columns in src0/src1 (K) | |
| params[3] = (uint32_t)src0->nb[1]/ggml_type_size(src0->type); // stride (elements) of src0 in dimension 1 | |
| params[4] = (uint32_t)src1->nb[1]/ggml_type_size(src1->type); // stride (elements) of src1 in dimension 1 | |
| params[5] = (uint32_t)src0->nb[2]/ggml_type_size(src0->type); // stride (elements) of src0 in dimension 2 | |
| params[6] = (uint32_t)src1->nb[2]/ggml_type_size(src1->type); // stride (elements) of src1 in dimension 2 | |
| params[7] = (uint32_t)src0->nb[3]/ggml_type_size(src0->type); // stride (elements) of src0 in dimension 3 | |
| params[8] = (uint32_t)src1->nb[3]/ggml_type_size(src1->type); // stride (elements) of src1 in dimension 3 | |
| params[9] = (uint32_t)src0->ne[2]; // batch size in dimension 2 | |
| params[10] = (uint32_t)src0->ne[3]; // batch size in dimension 3 | |
| params[11] = (uint32_t)(src1->ne[2]/src0->ne[2]); // broadcast in dimension 2 | |
| params[12] = (uint32_t)(src1->ne[3]/src0->ne[3]); // broadcast in dimension 3 | |
| ctx->mul_mat_params_host_buf.Unmap(); | |
| wgpu::BindGroupEntry entries[4]; | |
| entries[0].binding = 0; | |
| entries[0].buffer = src0_ctx->buffer; | |
| entries[0].offset = src0_offset; | |
| entries[0].size = ggml_nbytes(src0); | |
| entries[1].binding = 1; | |
| entries[1].buffer = src1_ctx->buffer; | |
| entries[1].offset = src1_offset; | |
| entries[1].size = ggml_nbytes(src1); | |
| entries[2].binding = 2; | |
| entries[2].buffer = dst_ctx->buffer; | |
| entries[2].offset = dst_offset; | |
| entries[2].size = ggml_nbytes(node); | |
| entries[3].binding = 3; | |
| entries[3].buffer = ctx->mul_mat_params_dev_buf; | |
| entries[3].offset = 0; | |
| entries[3].size = ctx->mul_mat_params_dev_buf.GetSize(); | |
| wgpu::BindGroupDescriptor bind_group_desc; | |
| bind_group_desc.layout = ctx->mul_mat_pipeline.GetBindGroupLayout(0); | |
| bind_group_desc.entryCount = 4; | |
| bind_group_desc.label = "ggml_op_mul_mat"; | |
| bind_group_desc.entries = entries; | |
| wgpu::BindGroup bind_group = device.CreateBindGroup(&bind_group_desc); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.CopyBufferToBuffer( | |
| ctx->mul_mat_params_host_buf, 0, | |
| ctx->mul_mat_params_dev_buf, 0, | |
| ctx->mul_mat_params_dev_buf.GetSize() | |
| ); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetPipeline(ctx->mul_mat_pipeline); | |
| pass.SetBindGroup(0, bind_group); | |
| pass.DispatchWorkgroups((node->ne[0] * node->ne[1] * node->ne[2] * node->ne[3] + WEBGPU_MUL_MAT_WG_SIZE - 1) / WEBGPU_MUL_MAT_WG_SIZE); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| // TODO, don't submit here, batch submissions | |
| ctx->queue.Submit(1, &commands); | |
| // TODO, don't wait on submission here | |
| ggml_backend_webgpu_wait_on_submission(ctx); | |
| return true; | |
| } | |
| default: | |
| return false; | |
| } | |
| } | |
| static ggml_status ggml_backend_webgpu_graph_compute(ggml_backend_t backend, struct ggml_cgraph * cgraph) { | |
| WEBGPU_LOG_DEBUG("ggml_backend_webgpu_graph_compute(" << cgraph->n_nodes << " nodes)"); | |
| ggml_backend_webgpu_context * backend_ctx = static_cast<ggml_backend_webgpu_context *>(backend->context); | |
| webgpu_context ctx = backend_ctx->webgpu_ctx; | |
| for (int i = 0; i < cgraph->n_nodes; i++) { | |
| ggml_webgpu_encode_node(ctx, cgraph->nodes[i]); | |
| } | |
| return GGML_STATUS_SUCCESS; | |
| } | |
| static ggml_backend_i ggml_backend_webgpu_i = { | |
| /* .get_name = */ ggml_backend_webgpu_name, | |
| /* .free = */ ggml_backend_webgpu_free, | |
| /* .set_tensor_async = */ NULL, | |
| /* .get_tensor_async = */ NULL, | |
| /* .cpy_tensor_async = */ NULL, | |
| /* .synchronize = */ NULL, | |
| /* .graph_plan_create = */ NULL, | |
| /* .graph_plan_free = */ NULL, | |
| /* .graph_plan_update = */ NULL, | |
| /* .graph_plan_compute = */ NULL, | |
| /* .graph_compute = */ ggml_backend_webgpu_graph_compute, | |
| /* .event_record = */ NULL, | |
| /* .event_wait = */ NULL, | |
| }; | |
| /* End GGML Backend Interface */ | |
| /* GGML Backend Buffer Interface */ | |
| static void ggml_backend_webgpu_buffer_free_buffer(ggml_backend_buffer_t buffer) { | |
| WEBGPU_LOG_DEBUG("ggml_backend_webgpu_buffer_free_buffer()"); | |
| ggml_backend_webgpu_buffer_context * ctx = static_cast<ggml_backend_webgpu_buffer_context *>(buffer->context); | |
| ctx->buffer.Destroy(); | |
| } | |
| // Returns the "fake" base pointer. | |
| static void * ggml_backend_webgpu_buffer_get_base(ggml_backend_buffer_t buffer) { | |
| GGML_UNUSED(buffer); | |
| return webgpu_ptr_base; | |
| } | |
| static void ggml_backend_webgpu_buffer_memset_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor, uint8_t value, size_t offset, size_t size) { | |
| if (size == 0) { | |
| WEBGPU_LOG_DEBUG("ggml_backend_webgpu_buffer_memset_tensor: size is zero, nothing to do."); | |
| return; | |
| } | |
| WEBGPU_LOG_DEBUG("ggml_backend_webgpu_buffer_memset_tensor(" << buffer << ", " << tensor << ", " << value << ", " << offset << ", " << size << ")"); | |
| ggml_backend_webgpu_buffer_context * buf_ctx = (ggml_backend_webgpu_buffer_context *) buffer->context; | |
| size_t total_offset = webgpu_tensor_offset(tensor) + tensor->view_offs + offset; | |
| // This is a trick to set all bytes of a u32 to the same 1 byte value. | |
| uint32_t val32 = (uint32_t)value * 0x01010101; | |
| ggml_backend_webgpu_buffer_memset(buf_ctx->webgpu_ctx, buf_ctx->buffer, val32, total_offset, size); | |
| } | |
| static void ggml_backend_webgpu_buffer_set_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor, const void * data, size_t offset, size_t size) { | |
| WEBGPU_LOG_DEBUG("ggml_backend_webgpu_buffer_set_tensor(" << buffer << ", " << tensor << ", " << data << ", " << offset << ", " << size << ")"); | |
| ggml_backend_webgpu_buffer_context * buf_ctx = (ggml_backend_webgpu_buffer_context *) buffer->context; | |
| webgpu_context webgpu_ctx = buf_ctx->webgpu_ctx; | |
| size_t total_offset = webgpu_tensor_offset(tensor) + tensor->view_offs + offset; | |
| webgpu_ctx->queue.WriteBuffer(buf_ctx->buffer, total_offset, data, (size/4)*4); | |
| if (size % 4 != 0) { | |
| // If size is not a multiple of 4, we need to memset the remaining bytes | |
| size_t remaining_size = size % 4; | |
| // pack the remaining bytes into a uint32_t | |
| uint32_t val32 = 0; | |
| for (size_t i = 0; i < remaining_size; i++) { | |
| ((uint8_t *)&val32)[i] = ((const uint8_t *)data)[size - remaining_size + i]; | |
| } | |
| // memset the remaining bytes | |
| ggml_backend_webgpu_buffer_memset(webgpu_ctx, buf_ctx->buffer, val32, total_offset + (size - remaining_size), remaining_size); | |
| } | |
| } | |
| static void ggml_backend_webgpu_buffer_get_tensor(ggml_backend_buffer_t buffer, const ggml_tensor * tensor, void * data, size_t offset, size_t size) { | |
| WEBGPU_LOG_DEBUG("ggml_backend_webgpu_buffer_get_tensor(" << buffer << ", " << tensor << ", " << data << ", " << offset << ", " << size << ")"); | |
| ggml_backend_webgpu_buffer_context * buf_ctx = (ggml_backend_webgpu_buffer_context *) buffer->context; | |
| webgpu_context webgpu_ctx = buf_ctx->webgpu_ctx; | |
| wgpu::Device device = webgpu_ctx->device; | |
| size_t total_offset = webgpu_tensor_offset(tensor) + tensor->view_offs + offset; | |
| size_t final_size = size; | |
| if (size % 4 != 0) { | |
| // If size is not a multiple of 4, we need to round it up to the next multiple of 4 | |
| final_size = size + (4 - (size % 4)); | |
| } | |
| std::lock_guard<std::mutex> lock(webgpu_ctx->mutex); | |
| if (webgpu_ctx->get_tensor_staging_buf == nullptr || | |
| webgpu_ctx->get_tensor_staging_buf.GetSize() < final_size) { | |
| // Create a new staging buffer if it doesn't exist or is too small | |
| if (webgpu_ctx->get_tensor_staging_buf) { | |
| webgpu_ctx->get_tensor_staging_buf.Destroy(); | |
| } | |
| ggml_webgpu_create_buffer(device, webgpu_ctx->get_tensor_staging_buf, final_size, | |
| wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead, "get_tensor_staging_buf"); | |
| } | |
| // Copy the data from the buffer to the staging buffer | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.CopyBufferToBuffer(buf_ctx->buffer, total_offset, webgpu_ctx->get_tensor_staging_buf, 0, final_size); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| // Submit the command buffer to the queue | |
| webgpu_ctx->queue.Submit(1, &commands); | |
| // Map the staging buffer to read the data | |
| ggml_backend_webgpu_map_buffer(webgpu_ctx, webgpu_ctx->get_tensor_staging_buf, wgpu::MapMode::Read, 0, final_size); | |
| // Must specify size here since the staging buffer might be larger than the tensor size | |
| const void * mapped_range = webgpu_ctx->get_tensor_staging_buf.GetConstMappedRange(0, final_size); | |
| // Copy the data from the mapped range to the output buffer | |
| std::memcpy(data, mapped_range, size); | |
| webgpu_ctx->get_tensor_staging_buf.Unmap(); | |
| } | |
| static void ggml_backend_webgpu_buffer_clear(ggml_backend_buffer_t buffer, uint8_t value) { | |
| WEBGPU_LOG_DEBUG("ggml_backend_webgpu_buffer_clear(" << buffer << ", " << (uint32_t) value << ")"); | |
| ggml_backend_webgpu_buffer_context * buf_ctx = (ggml_backend_webgpu_buffer_context *) buffer->context; | |
| ggml_backend_webgpu_buffer_memset(buf_ctx->webgpu_ctx, buf_ctx->buffer, value, 0, buffer->size); | |
| } | |
| static ggml_backend_buffer_i ggml_backend_webgpu_buffer_interface = { | |
| /* .free_buffer = */ ggml_backend_webgpu_buffer_free_buffer, | |
| /* .get_base = */ ggml_backend_webgpu_buffer_get_base, | |
| /* .init_tensor = */ NULL, // TODO: optional, needed? | |
| /* .memset_tensor = */ ggml_backend_webgpu_buffer_memset_tensor, | |
| /* .set_tensor = */ ggml_backend_webgpu_buffer_set_tensor, | |
| /* .get_tensor = */ ggml_backend_webgpu_buffer_get_tensor, | |
| /* .cpy_tensor = */ NULL, // TODO: optional, implement this | |
| /* .clear = */ ggml_backend_webgpu_buffer_clear, | |
| /* .reset = */ NULL, // TODO: optional, think it coordinates with .init_tensor | |
| }; | |
| /* End GGML Backend Buffer Interface */ | |
| /* GGML Backend Buffer Type Interface */ | |
| static const char * ggml_backend_webgpu_buffer_type_get_name(ggml_backend_buffer_type_t buft) { | |
| ggml_backend_webgpu_device_context * ctx = static_cast<ggml_backend_webgpu_device_context *>(buft->device->context); | |
| return ctx->device_name.c_str(); | |
| } | |
| static ggml_backend_buffer_t ggml_backend_webgpu_buffer_type_alloc_buffer(ggml_backend_buffer_type_t buft, size_t size) { | |
| WEBGPU_LOG_DEBUG("ggml_backend_webgpu_buffer_type_alloc_buffer(" << size << ")"); | |
| ggml_backend_webgpu_device_context * ctx = static_cast<ggml_backend_webgpu_device_context *>(buft->device->context); | |
| wgpu::Buffer buf; | |
| ggml_webgpu_create_buffer(ctx->webgpu_ctx->device, buf, size, | |
| wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst, "allocated_buffer"); | |
| ggml_backend_webgpu_buffer_context * buf_ctx = new ggml_backend_webgpu_buffer_context(ctx->webgpu_ctx, buf); | |
| return ggml_backend_buffer_init(buft, ggml_backend_webgpu_buffer_interface, buf_ctx, size); | |
| } | |
| static size_t ggml_backend_webgpu_buffer_type_get_alignment(ggml_backend_buffer_type_t buft) { | |
| ggml_backend_webgpu_device_context * ctx = static_cast<ggml_backend_webgpu_device_context *>(buft->device->context); | |
| return ctx->webgpu_ctx->limits.minStorageBufferOffsetAlignment; | |
| } | |
| // maxBufferSize might be larger, but you can't bind more than maxStorageBufferBindingSize to a single binding. | |
| static size_t ggml_backend_webgpu_buffer_type_get_max_size(ggml_backend_buffer_type_t buft) { | |
| ggml_backend_webgpu_device_context * ctx = static_cast<ggml_backend_webgpu_device_context *>(buft->device->context); | |
| return ctx->webgpu_ctx->limits.maxStorageBufferBindingSize; | |
| } | |
| /* End GGML Backend Buffer Type Interface */ | |
| /* GGML Backend Device Interface */ | |
| static const char * ggml_backend_webgpu_device_get_name(ggml_backend_dev_t dev) { | |
| ggml_backend_webgpu_device_context * ctx = static_cast<ggml_backend_webgpu_device_context *>(dev->context); | |
| return ctx->device_name.c_str(); | |
| } | |
| static const char * ggml_backend_webgpu_device_get_description(ggml_backend_dev_t dev) { | |
| ggml_backend_webgpu_device_context * ctx = static_cast<ggml_backend_webgpu_device_context *>(dev->context); | |
| return ctx->device_desc.c_str(); | |
| } | |
| static void ggml_backend_webgpu_device_get_memory(ggml_backend_dev_t dev, size_t * free, size_t * total) { | |
| ggml_backend_webgpu_device_context * ctx = static_cast<ggml_backend_webgpu_device_context *>(dev->context); | |
| // TODO: what do we actually want to return here? maxBufferSize might not be the full available memory. | |
| *free = ctx->webgpu_ctx->limits.maxBufferSize; | |
| *total = ctx->webgpu_ctx->limits.maxBufferSize; | |
| } | |
| static enum ggml_backend_dev_type ggml_backend_webgpu_device_get_type(ggml_backend_dev_t dev) { | |
| GGML_UNUSED(dev); | |
| return GGML_BACKEND_DEVICE_TYPE_GPU; | |
| } | |
| static void ggml_backend_webgpu_device_get_props(ggml_backend_dev_t dev, struct ggml_backend_dev_props * props) { | |
| props->name = ggml_backend_webgpu_device_get_name(dev); | |
| props->description = ggml_backend_webgpu_device_get_description(dev); | |
| props->type = ggml_backend_webgpu_device_get_type(dev); | |
| ggml_backend_webgpu_device_get_memory(dev, &props->memory_free, &props->memory_total); | |
| props->caps = { | |
| /* .async = */ false, | |
| /* .host_buffer = */ false, | |
| /* .buffer_from_host_ptr = */ false, | |
| /* .events = */ false, | |
| }; | |
| } | |
| static ggml_guid_t ggml_backend_webgpu_guid(void) { | |
| static const char * guid_str = "__ggml_webgpu :)"; | |
| return reinterpret_cast<ggml_guid_t>((void *)guid_str); | |
| } | |
| static void ggml_webgpu_init_memset_pipeline(webgpu_context webgpu_ctx) { | |
| // we use the maximum workgroup size for the memset pipeline | |
| size_t max_wg_size = webgpu_ctx->limits.maxComputeWorkgroupSizeX; | |
| size_t max_threads = max_wg_size * webgpu_ctx->limits.maxComputeWorkgroupsPerDimension; | |
| // Size the bytes_per_thread so that the largest buffer size can be handled | |
| webgpu_ctx->memset_bytes_per_thread = (webgpu_ctx->limits.maxStorageBufferBindingSize + max_threads - 1) / max_threads; | |
| std::vector<wgpu::ConstantEntry> constants(2); | |
| constants[0].key = "wg_size"; | |
| constants[0].value = max_wg_size; | |
| constants[1].key = "bytes_per_thread"; | |
| constants[1].value = webgpu_ctx->memset_bytes_per_thread; | |
| ggml_webgpu_create_pipeline(webgpu_ctx->device, webgpu_ctx->memset_pipeline, wgsl_memset, "memset", constants); | |
| ggml_webgpu_create_buffer(webgpu_ctx->device, webgpu_ctx->memset_params_dev_buf, | |
| 3 * sizeof(uint32_t), // 3 parameters: buffer size, offset, value | |
| wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst, "memset_params_dev_buf"); | |
| ggml_webgpu_create_buffer(webgpu_ctx->device, webgpu_ctx->memset_params_host_buf, | |
| 3 * sizeof(uint32_t), wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc, "memset_params_host_buf"); | |
| } | |
| static void ggml_webgpu_init_mul_mat_pipeline(webgpu_context webgpu_ctx) { | |
| ggml_webgpu_create_pipeline(webgpu_ctx->device, webgpu_ctx->mul_mat_pipeline, wgsl_mul_mat, "mul_mat"); | |
| ggml_webgpu_create_buffer(webgpu_ctx->device, webgpu_ctx->mul_mat_params_dev_buf, WEBGPU_MUL_MAT_PARAMS_SIZE, | |
| wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst, "mul_mat_params_dev_buf"); | |
| ggml_webgpu_create_buffer(webgpu_ctx->device, webgpu_ctx->mul_mat_params_host_buf, WEBGPU_MUL_MAT_PARAMS_SIZE, | |
| wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc, "mul_mat_params_host_buf"); | |
| } | |
| static void ggml_webgpu_init_cpy_pipeline(webgpu_context webgpu_ctx) { | |
| std::vector<wgpu::ConstantEntry> constants(1); | |
| constants[0].key = "wg_size"; | |
| constants[0].value = webgpu_ctx->limits.maxComputeWorkgroupSizeX; | |
| ggml_webgpu_create_pipeline(webgpu_ctx->device, webgpu_ctx->cpy_pipeline, wgsl_cpy, "cpy", constants); | |
| ggml_webgpu_create_buffer(webgpu_ctx->device, webgpu_ctx->cpy_params_dev_buf, WEBGPU_CPY_PARAMS_SIZE, | |
| wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst, "cpy_params_dev_buf"); | |
| ggml_webgpu_create_buffer(webgpu_ctx->device, webgpu_ctx->cpy_params_host_buf, WEBGPU_CPY_PARAMS_SIZE, | |
| wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc, "cpy_params_host_buf"); | |
| } | |
| // TODO: Make thread safe if multiple devices are used | |
| static ggml_backend_t ggml_backend_webgpu_device_init(ggml_backend_dev_t dev, const char * params) { | |
| GGML_UNUSED(params); | |
| WEBGPU_LOG_DEBUG("ggml_backend_webgpu_device_init()"); | |
| ggml_backend_webgpu_device_context * dev_ctx = static_cast<ggml_backend_webgpu_device_context *>(dev->context); | |
| webgpu_context webgpu_ctx = dev_ctx->webgpu_ctx; | |
| std::lock_guard<std::mutex> lock(webgpu_ctx->mutex); | |
| if (!webgpu_ctx->device_initialized) { | |
| // Initialize device | |
| wgpu::DeviceDescriptor dev_desc; | |
| dev_desc.requiredLimits = &webgpu_ctx->limits; | |
| dev_desc.requiredFeatures = webgpu_ctx->features.features; | |
| dev_desc.requiredFeatureCount = webgpu_ctx->features.featureCount; | |
| dev_desc.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, | |
| [](const wgpu::Device& device, wgpu::DeviceLostReason reason, wgpu::StringView message) { | |
| GGML_UNUSED(device); | |
| GGML_LOG_ERROR("ggml_webgpu: Device lost! Reason: %d, Message: %s\n", static_cast<int>(reason), message.data); | |
| }); | |
| dev_desc.SetUncapturedErrorCallback( | |
| [](const wgpu::Device& device, wgpu::ErrorType reason, wgpu::StringView message) { | |
| GGML_UNUSED(device); | |
| GGML_LOG_ERROR("ggml_webgpu: Device error! Reason: %d, Message: %s\n", static_cast<int>(reason), message.data); | |
| }); | |
| webgpu_ctx->instance.WaitAny(webgpu_ctx->adapter.RequestDevice(&dev_desc, wgpu::CallbackMode::WaitAnyOnly, | |
| [webgpu_ctx](wgpu::RequestDeviceStatus status, wgpu::Device device, wgpu::StringView message) { | |
| if (status != wgpu::RequestDeviceStatus::Success) { | |
| GGML_LOG_ERROR("ggml_webgpu: Failed to get a device: %s\n", message.data); | |
| return; | |
| } | |
| webgpu_ctx->device = device; | |
| }), | |
| UINT64_MAX | |
| ); | |
| GGML_ASSERT(webgpu_ctx->device != nullptr); | |
| // Initialize (compute) queue | |
| webgpu_ctx->queue = webgpu_ctx->device.GetQueue(); | |
| ggml_webgpu_init_memset_pipeline(webgpu_ctx); | |
| ggml_webgpu_init_mul_mat_pipeline(webgpu_ctx); | |
| ggml_webgpu_init_cpy_pipeline(webgpu_ctx); | |
| webgpu_ctx->device_initialized = true; | |
| } | |
| static ggml_backend_webgpu_context backend_ctx; | |
| backend_ctx.name = GGML_WEBGPU_NAME + std::string(": ") + dev_ctx->device_name; | |
| backend_ctx.webgpu_ctx = webgpu_ctx; | |
| // See GGML Backend Interface section | |
| static ggml_backend backend = { | |
| /* .guid = */ ggml_backend_webgpu_guid(), | |
| /* .interface = */ ggml_backend_webgpu_i, | |
| /* .device = */ dev, | |
| /* .context = */ &backend_ctx, | |
| }; | |
| return &backend; | |
| } | |
| static ggml_backend_buffer_type_t ggml_backend_webgpu_device_get_buffer_type(ggml_backend_dev_t dev) { | |
| // See GGML Backend Buffer Type Interface section | |
| static struct ggml_backend_buffer_type ggml_backend_webgpu_buffer_type = { | |
| /* .iface = */ { | |
| /* .get_name = */ ggml_backend_webgpu_buffer_type_get_name, | |
| /* .alloc_buffer = */ ggml_backend_webgpu_buffer_type_alloc_buffer, | |
| /* .get_alignment = */ ggml_backend_webgpu_buffer_type_get_alignment, | |
| /* .get_max_size = */ ggml_backend_webgpu_buffer_type_get_max_size, | |
| /* .get_alloc_size = */ NULL, // defaults to ggml_nbytes | |
| /* .is_host = */ NULL, // defaults to false | |
| }, | |
| /* .device = */ dev, | |
| /* .context = */ NULL, | |
| }; | |
| return &ggml_backend_webgpu_buffer_type; | |
| } | |
| static bool ggml_backend_webgpu_device_supports_buft(ggml_backend_dev_t dev, ggml_backend_buffer_type_t buft) { | |
| GGML_UNUSED(dev); | |
| return buft->iface.get_name == ggml_backend_webgpu_buffer_type_get_name; | |
| } | |
| static bool ggml_backend_webgpu_device_supports_op(ggml_backend_dev_t dev, const ggml_tensor * op) { | |
| GGML_UNUSED(dev); | |
| switch (op->op) { | |
| case GGML_OP_NONE: | |
| case GGML_OP_VIEW: | |
| case GGML_OP_PERMUTE: | |
| return true; | |
| case GGML_OP_CPY: | |
| return op->type == GGML_TYPE_F16 && op->src[0]->type == GGML_TYPE_F32; | |
| case GGML_OP_MUL_MAT: | |
| return op->src[0]->type == GGML_TYPE_F32 && op->src[1]->type == GGML_TYPE_F32; | |
| default: | |
| return false; | |
| } | |
| } | |
| static struct ggml_backend_device_i ggml_backend_webgpu_device_i = { | |
| /* .get_name = */ ggml_backend_webgpu_device_get_name, | |
| /* .get_description = */ ggml_backend_webgpu_device_get_description, | |
| /* .get_memory = */ ggml_backend_webgpu_device_get_memory, | |
| /* .get_type = */ ggml_backend_webgpu_device_get_type, | |
| /* .get_props = */ ggml_backend_webgpu_device_get_props, | |
| /* .init_backend = */ ggml_backend_webgpu_device_init, | |
| /* .get_buffer_type = */ ggml_backend_webgpu_device_get_buffer_type, | |
| /* .get_host_buffer_type = */ NULL, | |
| /* .buffer_from_host_ptr = */ NULL, | |
| /* .supports_op = */ ggml_backend_webgpu_device_supports_op, | |
| /* .supports_buft = */ ggml_backend_webgpu_device_supports_buft, | |
| /* .offload_op = */ NULL, | |
| /* .event_new = */ NULL, | |
| /* .event_free = */ NULL, | |
| /* .event_synchronize = */ NULL, | |
| }; | |
| /* End GGML Backend Device Interface */ | |
| /* GGML Backend Registration Interface */ | |
| static const char * ggml_backend_webgpu_reg_get_name(ggml_backend_reg_t reg) { | |
| ggml_backend_webgpu_reg_context * ctx = static_cast<ggml_backend_webgpu_reg_context *>(reg->context); | |
| return ctx->name; | |
| } | |
| static size_t ggml_backend_webgpu_reg_get_device_count(ggml_backend_reg_t reg) { | |
| ggml_backend_webgpu_reg_context * ctx = static_cast<ggml_backend_webgpu_reg_context *>(reg->context); | |
| return ctx->device_count; | |
| } | |
| // TODO: Does this need to be thread safe? Is it only called once? | |
| // Only one device is supported for now | |
| static ggml_backend_dev_t ggml_backend_webgpu_reg_get_device(ggml_backend_reg_t reg, size_t index) { | |
| GGML_ASSERT(index == 0); | |
| WEBGPU_LOG_DEBUG("ggml_backend_reg_get_device()"); | |
| ggml_backend_webgpu_reg_context * reg_ctx = static_cast<ggml_backend_webgpu_reg_context *>(reg->context); | |
| webgpu_context ctx = reg_ctx->webgpu_ctx; | |
| wgpu::RequestAdapterOptions options = {}; | |
| auto callback = [](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter, const char *message, void *userdata) { | |
| if (status != wgpu::RequestAdapterStatus::Success) { | |
| GGML_LOG_ERROR("ggml_webgpu: Failed to get an adapter: %s\n", message); | |
| return; | |
| } | |
| *static_cast<wgpu::Adapter *>(userdata) = adapter; | |
| }; | |
| void *userdata = &ctx->adapter; | |
| ctx->instance.WaitAny(ctx->instance.RequestAdapter(&options, wgpu::CallbackMode::WaitAnyOnly, callback, userdata), UINT64_MAX); | |
| GGML_ASSERT(ctx->adapter != nullptr); | |
| ctx->adapter.GetLimits(&ctx->limits); | |
| ctx->adapter.GetFeatures(&ctx->features); | |
| wgpu::AdapterInfo info{}; | |
| ctx->adapter.GetInfo(&info); | |
| static ggml_backend_webgpu_device_context device_ctx; | |
| device_ctx.webgpu_ctx = ctx; | |
| device_ctx.device_name = GGML_WEBGPU_NAME; | |
| device_ctx.device_desc = std::string(info.description.data); | |
| GGML_LOG_INFO("ggml_webgpu: adapter_info: vendor_id: %u | vendor: %s | architecture: %s | device_id: %u | name: %s | device_desc: %s\n", | |
| info.vendorID, info.vendor.data, info.architecture.data, info.deviceID, info.device.data, info.description.data); | |
| // See GGML Backend Device Interface section | |
| static ggml_backend_device device = { | |
| /* .iface = */ ggml_backend_webgpu_device_i, | |
| /* .reg = */ reg, | |
| /* .context = */ &device_ctx, | |
| }; | |
| return &device; | |
| } | |
| static const struct ggml_backend_reg_i ggml_backend_webgpu_reg_i = { | |
| /* .get_name = */ ggml_backend_webgpu_reg_get_name, | |
| /* .get_device_count = */ ggml_backend_webgpu_reg_get_device_count, | |
| /* .get_device = */ ggml_backend_webgpu_reg_get_device, | |
| /* .get_proc_address = */ NULL, | |
| }; | |
| /* End GGML Backend Registration Interface */ | |
| // TODO: Does this need to be thread safe? Is it only called once? | |
| ggml_backend_reg_t ggml_backend_webgpu_reg() { | |
| WEBGPU_LOG_DEBUG("ggml_backend_webgpu_reg()"); | |
| webgpu_context webgpu_ctx = std::make_shared<webgpu_context_struct>(); | |
| webgpu_ctx->device_initialized = false; | |
| static ggml_backend_webgpu_reg_context ctx; | |
| ctx.webgpu_ctx = webgpu_ctx; | |
| ctx.name = GGML_WEBGPU_NAME; | |
| ctx.device_count = 1; | |
| wgpu::InstanceDescriptor instance_descriptor{}; | |
| std::vector<wgpu::InstanceFeatureName> instance_features = {wgpu::InstanceFeatureName::TimedWaitAny}; | |
| instance_descriptor.requiredFeatures = instance_features.data(); | |
| instance_descriptor.requiredFeatureCount = instance_features.size(); | |
| webgpu_ctx->instance = wgpu::CreateInstance(&instance_descriptor); | |
| GGML_ASSERT(webgpu_ctx->instance != nullptr); | |
| static ggml_backend_reg reg = { | |
| /* .api_version = */ GGML_BACKEND_API_VERSION, | |
| /* .iface = */ ggml_backend_webgpu_reg_i, | |
| /* .context = */ &ctx, | |
| }; | |
| return ® | |
| } | |
| ggml_backend_t ggml_backend_webgpu_init(void) { | |
| ggml_backend_dev_t dev = ggml_backend_reg_dev_get(ggml_backend_webgpu_reg(), 0); | |
| return ggml_backend_webgpu_device_init(dev, nullptr); | |
| } | |
| GGML_BACKEND_DL_IMPL(ggml_backend_webgpu_reg) | |