mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-07-17 11:21:45 +02:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 32e789fdfd | |||
| a8dc0e3269 | |||
| a55a8c5266 | |||
| 79bba02a67 | |||
| 3f08ef2c51 | |||
| 8ee54c8b32 | |||
| c7d8722922 | |||
| 5839ba3524 | |||
| a320cbfcb7 | |||
| 56d6e9dde2 | |||
| 3dafb585f8 | |||
| 602f828b4d | |||
| 505b1ed15c | |||
| 32beb244f5 | |||
| 3b53219361 | |||
| aff6eb6e75 | |||
| c3d47e696b | |||
| f6f12e43fa | |||
| 956973c764 | |||
| a582222290 | |||
| a05df0a81a | |||
| a3e5b96ac5 | |||
| c81029373d | |||
| b3c9d1b846 | |||
| f955e394bf | |||
| 33a75f41c3 | |||
| d3fba0c79d | |||
| ae9291e16b | |||
| 22b208b1ca | |||
| 0e148a573f | |||
| 32b741c336 | |||
| 12127defda | |||
| 00fa7cb284 | |||
| a4ce2595c5 | |||
| c71854292f | |||
| bf2c86ddc0 | |||
| 6e52db5b72 | |||
| 236ab574e0 | |||
| dfba90db63 | |||
| 00e79f6fb1 | |||
| 17a05e451f | |||
| 7f575c39d6 | |||
| 7cbd61002d | |||
| 8ff8c4299d | |||
| a7312ae94f | |||
| 657e01125a | |||
| 47a39665e7 |
@@ -6,7 +6,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
paths: [
|
||||
'.github/workflows/build.yml',
|
||||
'.github/workflows/build-self-hosted.yml',
|
||||
'**/CMakeLists.txt',
|
||||
'**/.cmake',
|
||||
'**/*.h',
|
||||
@@ -48,6 +48,8 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# note: this is dud token to avoid rate limiting (https://github.com/ggml-org/llama.cpp/pull/25706#issuecomment-4979941302)
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
|
||||
GGML_NLOOP: 3
|
||||
GGML_N_THREADS: 1
|
||||
LLAMA_ARG_LOG_COLORS: 1
|
||||
|
||||
@@ -1651,6 +1651,9 @@ jobs:
|
||||
|
||||
</details>
|
||||
|
||||
**Website:**
|
||||
- <https://llama.app>
|
||||
|
||||
**macOS/iOS:**
|
||||
- [macOS Apple Silicon (arm64)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-macos-arm64.tar.gz)
|
||||
- macOS Apple Silicon (arm64, KleidiAI enabled) [DISABLED](https://github.com/ggml-org/llama.cpp/pull/23780)
|
||||
|
||||
@@ -29,6 +29,8 @@ on:
|
||||
]
|
||||
|
||||
env:
|
||||
# note: this is dud token to avoid rate limiting (https://github.com/ggml-org/llama.cpp/pull/25706#issuecomment-4979941302)
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
|
||||
LLAMA_ARG_LOG_COLORS: 1
|
||||
LLAMA_ARG_LOG_PREFIX: 1
|
||||
LLAMA_ARG_LOG_TIMESTAMPS: 1
|
||||
@@ -141,6 +143,24 @@ jobs:
|
||||
export LLAMA_ARG_BACKEND_SAMPLING=1
|
||||
pytest -v -x -m "not slow"
|
||||
|
||||
- name: Tests (GPUx2)
|
||||
id: server_integration_tests_gpu2
|
||||
if: ${{ !github.event.pull_request }}
|
||||
run: |
|
||||
cd tools/server/tests
|
||||
source venv/bin/activate
|
||||
export GGML_CUDA_DEVICES=2
|
||||
pytest -v -x -m "not slow"
|
||||
|
||||
- name: Tests (GPUx2, backend-sampling)
|
||||
id: server_integration_tests_gpu2_backend_sampling
|
||||
if: ${{ !github.event.pull_request }}
|
||||
run: |
|
||||
cd tools/server/tests
|
||||
source venv/bin/activate
|
||||
export GGML_CUDA_DEVICES=2 LLAMA_ARG_BACKEND_SAMPLING=1
|
||||
pytest -v -x -m "not slow"
|
||||
|
||||
server-kleidiai:
|
||||
runs-on: ah-ubuntu_22_04-c8g_8x
|
||||
|
||||
|
||||
+91
-10
@@ -697,7 +697,7 @@ static bool common_params_parse_ex(int argc, char ** argv, common_params_context
|
||||
}
|
||||
};
|
||||
|
||||
// parse the first time to get -hf option (used for remote preset)
|
||||
// parse all CLI args now, so that -hf is available below for remote preset resolution
|
||||
parse_cli_args();
|
||||
|
||||
postprocess_cpu_params(params.cpuparams, nullptr);
|
||||
@@ -748,6 +748,11 @@ static bool common_params_parse_ex(int argc, char ** argv, common_params_context
|
||||
params.kv_overrides.back().key[0] = 0;
|
||||
}
|
||||
|
||||
if (!params.server_tools.empty() && !params.cors_origins_explicit) {
|
||||
LOG_WRN("server tools are enabled, using localhost as default CORS origin (change via --cors-origins)\n");
|
||||
params.cors_origins = "localhost";
|
||||
}
|
||||
|
||||
// pad tensor_buft_overrides for llama_params_fit:
|
||||
const size_t ntbo = llama_max_tensor_buft_overrides();
|
||||
while (params.tensor_buft_overrides.size() < ntbo) {
|
||||
@@ -1179,6 +1184,8 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
params.sampling.temp = 0.2; // lower temp by default for better quality
|
||||
} else if (ex == LLAMA_EXAMPLE_SERVER) {
|
||||
params.n_parallel = -1; // auto by default
|
||||
} else if (ex == LLAMA_EXAMPLE_TOKENIZE) {
|
||||
params.parse_special = true; // parse special tokens by default, like the old tokenize tool
|
||||
}
|
||||
|
||||
params.use_color = tty_can_use_colors();
|
||||
@@ -2746,14 +2753,14 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
[](common_params & params, const std::string & value) {
|
||||
params.model.path = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_EXPORT_LORA, LLAMA_EXAMPLE_DOWNLOAD}).set_env("LLAMA_ARG_MODEL"));
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_EXPORT_LORA, LLAMA_EXAMPLE_DOWNLOAD, LLAMA_EXAMPLE_TOKENIZE}).set_env("LLAMA_ARG_MODEL"));
|
||||
add_opt(common_arg(
|
||||
{"-mu", "--model-url"}, "MODEL_URL",
|
||||
"model download url (default: unused)",
|
||||
[](common_params & params, const std::string & value) {
|
||||
params.model.url = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_DOWNLOAD}).set_env("LLAMA_ARG_MODEL_URL"));
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_DOWNLOAD, LLAMA_EXAMPLE_TOKENIZE}).set_env("LLAMA_ARG_MODEL_URL"));
|
||||
add_opt(common_arg(
|
||||
{ "-dr", "--docker-repo" }, "[<repo>/]<model>[:quant]",
|
||||
"Docker Hub model repository. repo is optional, default to ai/. quant is optional, default to :latest.\n"
|
||||
@@ -2762,7 +2769,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
[](common_params & params, const std::string & value) {
|
||||
params.model.docker_repo = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_DOWNLOAD}).set_env("LLAMA_ARG_DOCKER_REPO"));
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_DOWNLOAD, LLAMA_EXAMPLE_TOKENIZE}).set_env("LLAMA_ARG_DOCKER_REPO"));
|
||||
add_opt(common_arg(
|
||||
{"-hf", "-hfr", "--hf-repo"}, "<user>/<model>[:quant]",
|
||||
"Hugging Face model repository; quant is optional, case-insensitive, default to Q4_K_M, or falls back to the first file in the repo if Q4_K_M doesn't exist.\n"
|
||||
@@ -2772,14 +2779,14 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
[](common_params & params, const std::string & value) {
|
||||
params.model.hf_repo = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_DOWNLOAD}).set_env("LLAMA_ARG_HF_REPO"));
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_DOWNLOAD, LLAMA_EXAMPLE_TOKENIZE}).set_env("LLAMA_ARG_HF_REPO"));
|
||||
add_opt(common_arg(
|
||||
{"-hff", "--hf-file"}, "FILE",
|
||||
"Hugging Face model file. If specified, it will override the quant in --hf-repo (default: unused)",
|
||||
[](common_params & params, const std::string & value) {
|
||||
params.model.hf_file = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_DOWNLOAD}).set_env("LLAMA_ARG_HF_FILE"));
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_DOWNLOAD, LLAMA_EXAMPLE_TOKENIZE}).set_env("LLAMA_ARG_HF_FILE"));
|
||||
add_opt(common_arg(
|
||||
{"-hfv", "-hfrv", "--hf-repo-v"}, "<user>/<model>[:quant]",
|
||||
"Hugging Face model repository for the vocoder model (default: unused)",
|
||||
@@ -2800,7 +2807,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
[](common_params & params, const std::string & value) {
|
||||
params.hf_token = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_DOWNLOAD}).set_env("HF_TOKEN"));
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_DOWNLOAD, LLAMA_EXAMPLE_TOKENIZE}).set_env("HF_TOKEN"));
|
||||
add_opt(common_arg(
|
||||
{"--mtp"},
|
||||
"also download the multi-token prediction (MTP) head, if available (default: unused)",
|
||||
@@ -2916,6 +2923,41 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
params.parse_special = true;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_IMATRIX}));
|
||||
add_opt(common_arg(
|
||||
{"--ids"},
|
||||
string_format("only print the token IDs, in a Python-parseable list form like [1, 2, 3] (default: %s)", params.tokenize_ids ? "true" : "false"),
|
||||
[](common_params & params) {
|
||||
params.tokenize_ids = true;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_TOKENIZE}));
|
||||
add_opt(common_arg(
|
||||
{"--stdin"},
|
||||
string_format("read the prompt from stdin (takes precedence over -f/--file and -p/--prompt) (default: %s)", params.tokenize_stdin ? "true" : "false"),
|
||||
[](common_params & params) {
|
||||
params.tokenize_stdin = true;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_TOKENIZE}));
|
||||
add_opt(common_arg(
|
||||
{"--no-bos"},
|
||||
string_format("do not add a BOS token to the prompt, even if the model normally uses one (default: %s)", params.tokenize_no_bos ? "true" : "false"),
|
||||
[](common_params & params) {
|
||||
params.tokenize_no_bos = true;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_TOKENIZE}));
|
||||
add_opt(common_arg(
|
||||
{"--no-parse-special"},
|
||||
string_format("do not parse special tokens (chat, tool, etc) (default: %s)", !params.parse_special ? "true" : "false"),
|
||||
[](common_params & params) {
|
||||
params.parse_special = false;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_TOKENIZE}));
|
||||
add_opt(common_arg(
|
||||
{"--show-count"},
|
||||
string_format("print the total number of tokens (default: %s)", params.tokenize_show_count ? "true" : "false"),
|
||||
[](common_params & params) {
|
||||
params.tokenize_show_count = true;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_TOKENIZE}));
|
||||
add_opt(common_arg(
|
||||
{"-pps"},
|
||||
string_format("is the prompt shared across parallel sequences (default: %s)", params.is_pp_shared ? "true" : "false"),
|
||||
@@ -3010,6 +3052,42 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
params.public_path = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_STATIC_PATH"));
|
||||
add_opt(common_arg(
|
||||
{"--cors-origins"}, "ORIGINS",
|
||||
string_format(
|
||||
"comma-separated list of allowed origins for CORS (default: %s)\n"
|
||||
"if set to special value 'localhost', reflect the Origin header only if it is localhost",
|
||||
params.cors_origins.c_str()),
|
||||
[](common_params & params, const std::string & value) {
|
||||
params.cors_origins = value;
|
||||
params.cors_origins_explicit = true;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_CORS_ORIGINS"));
|
||||
add_opt(common_arg(
|
||||
{"--cors-methods"}, "METHODS",
|
||||
string_format("comma-separated list of allowed methods for CORS (default: %s)", params.cors_methods.c_str()),
|
||||
[](common_params & params, const std::string & value) {
|
||||
params.cors_methods = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_CORS_METHODS"));
|
||||
add_opt(common_arg(
|
||||
{"--cors-headers"}, "HEADERS",
|
||||
string_format("comma-separated list of allowed headers for CORS (default: %s)", params.cors_headers.c_str()),
|
||||
[](common_params & params, const std::string & value) {
|
||||
params.cors_headers = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_CORS_HEADERS"));
|
||||
add_opt(common_arg(
|
||||
{"--cors-credentials"},
|
||||
{"--no-cors-credentials"},
|
||||
string_format(
|
||||
"whether to allow credentials for CORS (default: %s)\n"
|
||||
"note: if this is enabled and --cors-origins is set to * (default), the Origin header will be echoed back, and credentials will always be allowed",
|
||||
params.cors_credentials ? "enabled" : "disabled"),
|
||||
[](common_params & params, bool value) {
|
||||
params.cors_credentials = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_CORS_CREDENTIALS"));
|
||||
add_opt(common_arg(
|
||||
{"--api-prefix"}, "PREFIX",
|
||||
string_format("prefix path the server serves from, without the trailing slash (default: %s)", params.api_prefix.c_str()),
|
||||
@@ -3043,7 +3121,8 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
{"--tools"}, "TOOL1,TOOL2,...",
|
||||
"experimental: whether to enable built-in tools for AI agents - do not enable in untrusted environments (default: no tools)\n"
|
||||
"specify \"all\" to enable all tools\n"
|
||||
"available tools: read_file, file_glob_search, grep_search, exec_shell_command, write_file, edit_file, get_datetime",
|
||||
"available tools: read_file, file_glob_search, grep_search, exec_shell_command, write_file, edit_file, get_datetime\n"
|
||||
"note: for security reasons, this will limit --cors-origins to localhost by default",
|
||||
[](common_params & params, const std::string & value) {
|
||||
params.server_tools = parse_csv_row(value);
|
||||
}
|
||||
@@ -3051,7 +3130,8 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
add_opt(common_arg(
|
||||
{"-ag", "--agent"},
|
||||
{"-no-ag", "--no-agent"},
|
||||
"whether to enable CORS proxy and all built-in tools - do not enable in untrusted environments (default: disabled)",
|
||||
"whether to enable CORS proxy and all built-in tools - do not enable in untrusted environments (default: disabled)\n"
|
||||
"note: for security reasons, this will limit --cors-origins to localhost by default",
|
||||
[](common_params & params, bool value) {
|
||||
if (value) {
|
||||
params.server_tools = {"all"};
|
||||
@@ -3060,6 +3140,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
params.server_tools.clear();
|
||||
params.ui_mcp_proxy = false;
|
||||
}
|
||||
// note: do not modify cors_origins here, as the options are not evaluated in order (user may explicitly set --cors-origins before --agent)
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_AGENT"));
|
||||
add_opt(common_arg(
|
||||
@@ -3506,7 +3587,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
[](common_params & params) {
|
||||
params.offline = true;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_DOWNLOAD}).set_env("LLAMA_ARG_OFFLINE"));
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_DOWNLOAD, LLAMA_EXAMPLE_TOKENIZE}).set_env("LLAMA_ARG_OFFLINE"));
|
||||
add_opt(common_arg(
|
||||
{"-lv", "--verbosity", "--log-verbosity"}, "N",
|
||||
string_format("Set the verbosity threshold. Messages with a higher verbosity will be ignored. Values:\n"
|
||||
|
||||
@@ -105,6 +105,7 @@ enum llama_example {
|
||||
LLAMA_EXAMPLE_RESULTS,
|
||||
LLAMA_EXAMPLE_EXPORT_GRAPH_OPS,
|
||||
LLAMA_EXAMPLE_DOWNLOAD,
|
||||
LLAMA_EXAMPLE_TOKENIZE,
|
||||
|
||||
LLAMA_EXAMPLE_COUNT,
|
||||
};
|
||||
@@ -630,6 +631,14 @@ struct common_params {
|
||||
std::string api_prefix = ""; // NOLINT
|
||||
std::string chat_template = ""; // NOLINT
|
||||
bool use_jinja = true; // NOLINT
|
||||
|
||||
// server CORS params
|
||||
std::string cors_origins = "*";
|
||||
std::string cors_methods = "GET, POST, DELETE, OPTIONS";
|
||||
std::string cors_headers = "*";
|
||||
bool cors_credentials = true;
|
||||
bool cors_origins_explicit = false; // for --agent option
|
||||
|
||||
bool enable_chat_template = true;
|
||||
bool force_pure_content_parser = false;
|
||||
common_reasoning_format reasoning_format = COMMON_REASONING_FORMAT_DEEPSEEK;
|
||||
@@ -716,6 +725,12 @@ struct common_params {
|
||||
// batched-bench params
|
||||
bool batched_bench_output_jsonl = false;
|
||||
|
||||
// tokenize params
|
||||
bool tokenize_ids = false; // if true, only print the token IDs
|
||||
bool tokenize_stdin = false; // if true, read the prompt from stdin
|
||||
bool tokenize_no_bos = false; // if true, do not add the BOS token
|
||||
bool tokenize_show_count = false; // if true, print the total token count
|
||||
|
||||
// common params
|
||||
std::string out_file; // output filename for all example programs
|
||||
// optional callback for model loading progress and cancellation:
|
||||
|
||||
@@ -260,7 +260,10 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {
|
||||
bool process(const llama_batch & batch) override {
|
||||
auto * ctx_dft = params.ctx_dft;
|
||||
|
||||
const int ret = llama_decode(ctx_dft, batch);
|
||||
llama_batch batch_dft = batch;
|
||||
batch_dft.logits = nullptr;
|
||||
|
||||
const int ret = llama_decode(ctx_dft, batch_dft);
|
||||
|
||||
if (ret != 0) {
|
||||
SPC_ERR("failed to decode draft batch, ret = %d\n", ret);
|
||||
|
||||
+15
-1
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from typing import Any, Callable, Iterable, TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
@@ -641,7 +643,19 @@ class DFlashModel(Qwen3Model):
|
||||
logger.info(f"DFlash: Using tokenizer from target model: {self.target_model_dir}")
|
||||
original_dir = self.dir_model
|
||||
self.dir_model = self.target_model_dir
|
||||
super().set_vocab()
|
||||
|
||||
# Reuse the target model's own vocab handler (e.g. Gemma-4 needs its
|
||||
# own tokenizer logic, not the Qwen default).
|
||||
from . import get_model_class
|
||||
with open(self.target_model_dir / "config.json", "r", encoding="utf-8") as f:
|
||||
target_arch = json.load(f)["architectures"][0]
|
||||
target_cls = get_model_class(target_arch)
|
||||
|
||||
if target_cls is not type(self):
|
||||
target_cls.set_vocab(self) # ty: ignore[unresolved-attribute]
|
||||
else:
|
||||
super().set_vocab()
|
||||
|
||||
self.dir_model = original_dir
|
||||
|
||||
mask_token_id = self.hparams.get("dflash_config", {}).get("mask_token_id")
|
||||
|
||||
+1
-1
@@ -120,4 +120,4 @@ Legend:
|
||||
| TRI | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| TRUNC | ❌ | ❌ | ✅ | 🟡 | 🟡 | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| UPSCALE | ❌ | 🟡 | ✅ | ✅ | ❌ | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| XIELU | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ |
|
||||
| XIELU | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
|
||||
+4
-4
@@ -11600,10 +11600,10 @@ zjy 2
|
||||
"SYCL0","CUMSUM","type=f32,ne=[242004,1,1,1]","support","1","yes","SYCL"
|
||||
"SYCL0","CUMSUM","type=f32,ne=[375960,1,1,1]","support","1","yes","SYCL"
|
||||
"SYCL0","CUMSUM","type=f32,ne=[20481,4,1,1]","support","1","yes","SYCL"
|
||||
"SYCL0","XIELU","type=f32,ne=[10,5,4,3]","support","0","no","SYCL"
|
||||
"SYCL0","XIELU","type=f16,ne=[10,5,4,3]","support","0","no","SYCL"
|
||||
"SYCL0","XIELU","type=f32,ne=[512,16,1,1]","support","0","no","SYCL"
|
||||
"SYCL0","XIELU","type=f16,ne=[512,16,1,1]","support","0","no","SYCL"
|
||||
"SYCL0","XIELU","type=f32,ne=[10,5,4,3]","support","1","yes","SYCL"
|
||||
"SYCL0","XIELU","type=f16,ne=[10,5,4,3]","support","1","yes","SYCL"
|
||||
"SYCL0","XIELU","type=f32,ne=[512,16,1,1]","support","1","yes","SYCL"
|
||||
"SYCL0","XIELU","type=f16,ne=[512,16,1,1]","support","1","yes","SYCL"
|
||||
"SYCL0","TRI","type=f32,ne=[10,10,4,3],tri_type=3","support","1","yes","SYCL"
|
||||
"SYCL0","TRI","type=f32,ne=[10,10,4,3],tri_type=2","support","1","yes","SYCL"
|
||||
"SYCL0","TRI","type=f32,ne=[10,10,4,3],tri_type=1","support","1","yes","SYCL"
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -780,6 +780,10 @@ extern "C" {
|
||||
GGML_API bool ggml_is_contiguous_1(const struct ggml_tensor * tensor); // contiguous for dims >= 1
|
||||
GGML_API bool ggml_is_contiguous_2(const struct ggml_tensor * tensor); // contiguous for dims >= 2
|
||||
|
||||
GGML_API bool ggml_is_contiguous_to_1(const struct ggml_tensor * tensor); // contiguous for dims < 1
|
||||
GGML_API bool ggml_is_contiguous_to_2(const struct ggml_tensor * tensor); // contiguous for dims < 2
|
||||
GGML_API bool ggml_is_contiguous_to_3(const struct ggml_tensor * tensor); // contiguous for dims < 3
|
||||
|
||||
// returns whether the tensor elements are allocated as one contiguous block of memory (no gaps, but permutation ok)
|
||||
GGML_API bool ggml_is_contiguously_allocated(const struct ggml_tensor * tensor);
|
||||
|
||||
|
||||
@@ -2859,7 +2859,14 @@ struct ggml_cplan ggml_graph_plan(
|
||||
} break;
|
||||
case GGML_OP_OUT_PROD:
|
||||
{
|
||||
if (ggml_is_quantized(node->src[0]->type)) {
|
||||
if (ggml_is_quantized(node->src[0]->type) ||
|
||||
node->src[0]->type == GGML_TYPE_F16) {
|
||||
cur = ggml_type_size(GGML_TYPE_F32) * node->src[0]->ne[0] * n_tasks;
|
||||
}
|
||||
} break;
|
||||
case GGML_OP_SET_ROWS:
|
||||
{
|
||||
if (node->src[0]->type == GGML_TYPE_F16 && node->type != GGML_TYPE_F16) {
|
||||
cur = ggml_type_size(GGML_TYPE_F32) * node->src[0]->ne[0] * n_tasks;
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -462,11 +462,12 @@ static bool ggml_backend_cpu_device_supports_op(ggml_backend_dev_t dev, const st
|
||||
return max_bias == 0.0f;
|
||||
}
|
||||
case GGML_OP_IM2COL_BACK:
|
||||
return src0->type == GGML_TYPE_F32 && src1->type == GGML_TYPE_F32;
|
||||
return src0->type == GGML_TYPE_F32 && (src1->type == GGML_TYPE_F32 || src1->type == GGML_TYPE_F16);
|
||||
case GGML_OP_GET_ROWS_BACK:
|
||||
return src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_F16;
|
||||
case GGML_OP_OUT_PROD:
|
||||
return (src0->type == GGML_TYPE_F32 || (ggml_is_quantized(src0->type) && src0->ne[2] == src1->ne[2] && src0->ne[3] == src1->ne[3])) &&
|
||||
return (src0->type == GGML_TYPE_F32 ||
|
||||
((src0->type == GGML_TYPE_F16 || ggml_is_quantized(src0->type)) && src0->ne[2] == src1->ne[2] && src0->ne[3] == src1->ne[3])) &&
|
||||
src1->type == GGML_TYPE_F32 && op->type == GGML_TYPE_F32;
|
||||
default:
|
||||
return true;
|
||||
|
||||
+85
-20
@@ -2081,8 +2081,8 @@ void ggml_compute_forward_concat(
|
||||
const ggml_tensor * src1 = dst->src[1];
|
||||
|
||||
if (ggml_is_quantized(src0->type)) {
|
||||
GGML_ASSERT(ggml_is_contiguous(src0));
|
||||
GGML_ASSERT(ggml_is_contiguous(src1));
|
||||
GGML_ASSERT(ggml_is_contiguous_rows(src0));
|
||||
GGML_ASSERT(ggml_is_contiguous_rows(src1));
|
||||
GGML_ASSERT(src0->ne[0] % ggml_blck_size(src0->type) == 0);
|
||||
GGML_ASSERT(src1->ne[0] % ggml_blck_size(src1->type) == 0);
|
||||
}
|
||||
@@ -4449,6 +4449,70 @@ static void ggml_compute_forward_out_prod_q_f32(
|
||||
}
|
||||
}
|
||||
|
||||
static void ggml_compute_forward_out_prod_f16_f32(
|
||||
const ggml_compute_params * params,
|
||||
ggml_tensor * dst) {
|
||||
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
const ggml_tensor * src1 = dst->src[1];
|
||||
|
||||
GGML_TENSOR_BINARY_OP_LOCALS;
|
||||
|
||||
const int ith = params->ith;
|
||||
const int nth = params->nth;
|
||||
|
||||
GGML_ASSERT(src0->type == GGML_TYPE_F16);
|
||||
GGML_ASSERT(src1->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(dst->type == GGML_TYPE_F32);
|
||||
|
||||
GGML_ASSERT(ne02 == ne12);
|
||||
GGML_ASSERT(ne03 == ne13);
|
||||
GGML_ASSERT(ne2 == ne12);
|
||||
GGML_ASSERT(ne3 == ne13);
|
||||
|
||||
GGML_ASSERT(nb00 == sizeof(ggml_fp16_t));
|
||||
GGML_ASSERT(nb0 == sizeof(float));
|
||||
|
||||
GGML_ASSERT(ne0 == ne00);
|
||||
GGML_ASSERT(ne1 == ne10);
|
||||
GGML_ASSERT(ne2 == ne02);
|
||||
GGML_ASSERT(ne3 == ne03);
|
||||
|
||||
if (ith == 0) {
|
||||
ggml_vec_set_f32(ne0*ne1*ne2*ne3, (float *)dst->data, 0);
|
||||
}
|
||||
ggml_barrier(params->threadpool);
|
||||
|
||||
const int64_t nr = ne1*ne2*ne3;
|
||||
const int64_t dr = (nr + nth - 1)/nth;
|
||||
const int64_t ir0 = dr*ith;
|
||||
const int64_t ir1 = MIN(ir0 + dr, nr);
|
||||
|
||||
float * wdata = (float *) params->wdata + (ne0 + CACHE_LINE_SIZE_F32) * ith;
|
||||
|
||||
for (int64_t ir = ir0; ir < ir1; ++ir) {
|
||||
const int64_t i3 = ir/(ne2*ne1);
|
||||
const int64_t i2 = (ir - i3*ne2*ne1)/ne1;
|
||||
const int64_t i1 = (ir - i3*ne2*ne1 - i2*ne1);
|
||||
|
||||
const int64_t i02 = i2;
|
||||
const int64_t i03 = i3;
|
||||
|
||||
const int64_t i12 = i2;
|
||||
const int64_t i13 = i3;
|
||||
|
||||
float * d = (float *) ((char *) dst->data + (i1*nb1 + i2*nb2 + i3*nb3));
|
||||
|
||||
for (int64_t i01 = 0; i01 < ne01; ++i01) {
|
||||
const int64_t i11 = i01;
|
||||
ggml_fp16_t * s0 = (ggml_fp16_t *) ((char *) src0->data + (i01*nb01 + i02*nb02 + i03*nb03));
|
||||
float * s1 = (float *) ((char *) src1->data + (i1*nb10 + i11*nb11 + i12*nb12 + i13*nb13));
|
||||
ggml_fp16_to_fp32_row(s0, wdata, ne0);
|
||||
ggml_vec_mad_f32(ne0, d, wdata, *s1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ggml_compute_forward_out_prod(
|
||||
const ggml_compute_params * params,
|
||||
ggml_tensor * dst) {
|
||||
@@ -4486,9 +4550,8 @@ void ggml_compute_forward_out_prod(
|
||||
} break;
|
||||
case GGML_TYPE_F16:
|
||||
{
|
||||
GGML_ABORT("fatal error"); // todo
|
||||
// ggml_compute_forward_out_prod_f16_f32(params, dst);
|
||||
}
|
||||
ggml_compute_forward_out_prod_f16_f32(params, dst);
|
||||
} break;
|
||||
case GGML_TYPE_F32:
|
||||
{
|
||||
ggml_compute_forward_out_prod_f32(params, dst);
|
||||
@@ -5041,7 +5104,7 @@ static void ggml_compute_forward_set_rows_impl(
|
||||
assert(ne0 == nc);
|
||||
assert(ne2 == ne02);
|
||||
assert(ne3 == ne03);
|
||||
GGML_ASSERT(src0->type == GGML_TYPE_F32 || (src0->type == GGML_TYPE_F16 && dst->type == GGML_TYPE_F16));
|
||||
GGML_ASSERT(src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_F16);
|
||||
assert(ne02 % ne11 == 0);
|
||||
assert(ne03 % ne12 == 0);
|
||||
|
||||
@@ -5075,10 +5138,19 @@ static void ggml_compute_forward_set_rows_impl(
|
||||
(const float *) ((char *) src0->data + i*nb01 + i02*nb02 + i03*nb03),
|
||||
((char *) dst->data + i1*nb1 + i02*nb2 + i03*nb3), nc);
|
||||
} else if constexpr (std::is_same_v<src_t, ggml_fp16_t>) {
|
||||
memcpy(
|
||||
if (dst->type == GGML_TYPE_F16) {
|
||||
memcpy(
|
||||
((char *) dst->data + i1*nb1 + i02*nb2 + i03*nb3),
|
||||
((char *) src0->data + i*nb01 + i02*nb02 + i03*nb03),
|
||||
rs);
|
||||
} else {
|
||||
float * wdata = (float *) params->wdata + (nc + CACHE_LINE_SIZE_F32) * ith;
|
||||
ggml_fp16_to_fp32_row(
|
||||
(const ggml_fp16_t *) ((char *) src0->data + i*nb01 + i02*nb02 + i03*nb03),
|
||||
wdata, nc);
|
||||
from_float(wdata,
|
||||
((char *) dst->data + i1*nb1 + i02*nb2 + i03*nb3), nc);
|
||||
}
|
||||
} else {
|
||||
GGML_ABORT("src0->type = %d (%s) not supported", src0->type, ggml_type_name(src0->type));
|
||||
}
|
||||
@@ -5107,16 +5179,12 @@ void ggml_compute_forward_set_rows(
|
||||
} break;
|
||||
case GGML_TYPE_F16:
|
||||
{
|
||||
if (dst->type == GGML_TYPE_F16) {
|
||||
if (src1->type == GGML_TYPE_I64) {
|
||||
ggml_compute_forward_set_rows_impl<ggml_fp16_t, int64_t>(params, dst);
|
||||
} else if (src1->type == GGML_TYPE_I32) {
|
||||
ggml_compute_forward_set_rows_impl<ggml_fp16_t, int32_t>(params, dst);
|
||||
} else {
|
||||
GGML_ABORT("src1->type = %d (%s) not supported", src1->type, ggml_type_name(src1->type));
|
||||
}
|
||||
if (src1->type == GGML_TYPE_I64) {
|
||||
ggml_compute_forward_set_rows_impl<ggml_fp16_t, int64_t>(params, dst);
|
||||
} else if (src1->type == GGML_TYPE_I32) {
|
||||
ggml_compute_forward_set_rows_impl<ggml_fp16_t, int32_t>(params, dst);
|
||||
} else {
|
||||
GGML_ABORT("dst->type = %d (%s) not supported with src0->type = %d (%s)", dst->type, ggml_type_name(dst->type), src0->type, ggml_type_name(src0->type));
|
||||
GGML_ABORT("src1->type = %d (%s) not supported", src1->type, ggml_type_name(src1->type));
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
@@ -6362,7 +6430,6 @@ static void ggml_compute_forward_im2col_f16(
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
const ggml_tensor * src1 = dst->src[1];
|
||||
|
||||
GGML_ASSERT(src0->type == GGML_TYPE_F16);
|
||||
GGML_ASSERT(src1->type == GGML_TYPE_F16 || src1->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT( dst->type == GGML_TYPE_F16);
|
||||
|
||||
@@ -6393,7 +6460,6 @@ static void ggml_compute_forward_im2col_f16(
|
||||
int ofs0 = is_2D ? nb13 : nb12;
|
||||
int ofs1 = is_2D ? nb12 : nb11;
|
||||
|
||||
GGML_ASSERT(nb00 == sizeof(ggml_fp16_t));
|
||||
GGML_ASSERT(nb10 == ggml_type_size(src1->type));
|
||||
|
||||
// im2col: [N, IC, IH, IW] => [N, OH, OW, IC*KH*KW]
|
||||
@@ -6466,7 +6532,7 @@ void ggml_compute_forward_im2col_back_f32(
|
||||
const ggml_tensor * src1 = dst->src[1]; // convolution kernel
|
||||
|
||||
GGML_ASSERT(src0->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(src1->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(src1->type == GGML_TYPE_F32 || src1->type == GGML_TYPE_F16);
|
||||
GGML_ASSERT( dst->type == GGML_TYPE_F32);
|
||||
|
||||
GGML_TENSOR_BINARY_OP_LOCALS;
|
||||
@@ -6563,7 +6629,6 @@ static void ggml_compute_forward_im2col_3d_f16(
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
const ggml_tensor * src1 = dst->src[1];
|
||||
|
||||
GGML_ASSERT(src0->type == GGML_TYPE_F16);
|
||||
GGML_ASSERT(src1->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT( dst->type == GGML_TYPE_F16);
|
||||
|
||||
|
||||
@@ -1115,7 +1115,8 @@ struct ggml_cuda_type_traits<GGML_TYPE_IQ3_S> {
|
||||
//////////////////////
|
||||
|
||||
struct ggml_cuda_device_info {
|
||||
int device_count;
|
||||
int device_count; // number of (possibly virtual) devices exposed to the rest of ggml
|
||||
int physical_device_count; // number of physical CUDA devices actually present
|
||||
|
||||
struct cuda_device_info {
|
||||
int cc; // compute capability
|
||||
@@ -1128,6 +1129,9 @@ struct ggml_cuda_device_info {
|
||||
size_t total_vram;
|
||||
int warp_size; // Number of threads in a dispatch
|
||||
bool supports_cooperative_launch; // whether cooperative launch is supported
|
||||
int physical_device; // backing physical CUDA device for this (virtual) device
|
||||
int physical_share_count; // number of (virtual) devices sharing this device's physical GPU
|
||||
int virtual_index; // index of this (virtual) device among those sharing its physical GPU
|
||||
};
|
||||
|
||||
cuda_device_info devices[GGML_CUDA_MAX_DEVICES] = {};
|
||||
|
||||
@@ -141,27 +141,25 @@ static __global__ void __launch_bounds__(CUDA_CONCAT_BLOCK_SIZE)
|
||||
|
||||
template <typename T>
|
||||
static void concat_cuda(const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst, int dim, cudaStream_t stream) {
|
||||
if (ggml_is_contiguous(src0) && ggml_is_contiguous(src1)) {
|
||||
if (dim != 3 && ggml_is_contiguous_to_3(src0) && ggml_is_contiguous_to_3(src1)) {
|
||||
const T * src0_d = (const T *) src0->data;
|
||||
const T * src1_d = (const T *) src1->data;
|
||||
T * dst_d = (T *) dst->data;
|
||||
|
||||
if (dim != 3) {
|
||||
for (int64_t i3 = 0; i3 < dst->ne[3]; i3++) {
|
||||
concat_cont_cuda(
|
||||
src0_d + i3*(src0->nb[3] / sizeof(T)),
|
||||
src1_d + i3*(src1->nb[3] / sizeof(T)),
|
||||
dst_d + i3*( dst->nb[3] / sizeof(T)),
|
||||
ggml_row_size(src0->type, src0->ne[0])/sizeof(T), src0->ne[1], src0->ne[2],
|
||||
ggml_row_size(dst->type, dst->ne[0])/sizeof(T), dst->ne[1], dst->ne[2], dim, stream);
|
||||
}
|
||||
} else {
|
||||
const size_t size0 = ggml_nbytes(src0);
|
||||
const size_t size1 = ggml_nbytes(src1);
|
||||
|
||||
CUDA_CHECK(cudaMemcpyAsync((char *) dst->data, src0->data, size0, cudaMemcpyDeviceToDevice, stream));
|
||||
CUDA_CHECK(cudaMemcpyAsync((char *) dst->data + size0, src1->data, size1, cudaMemcpyDeviceToDevice, stream));
|
||||
for (int64_t i3 = 0; i3 < dst->ne[3]; i3++) {
|
||||
concat_cont_cuda(
|
||||
src0_d + i3*(src0->nb[3] / sizeof(T)),
|
||||
src1_d + i3*(src1->nb[3] / sizeof(T)),
|
||||
dst_d + i3*( dst->nb[3] / sizeof(T)),
|
||||
ggml_row_size(src0->type, src0->ne[0])/sizeof(T), src0->ne[1], src0->ne[2],
|
||||
ggml_row_size(dst->type, dst->ne[0])/sizeof(T), dst->ne[1], dst->ne[2], dim, stream);
|
||||
}
|
||||
} else if (dim == 3 && ggml_is_contiguous(src0) && ggml_is_contiguous(src1)) {
|
||||
const size_t size0 = ggml_nbytes(src0);
|
||||
const size_t size1 = ggml_nbytes(src1);
|
||||
|
||||
CUDA_CHECK(cudaMemcpyAsync((char *) dst->data, src0->data, size0, cudaMemcpyDeviceToDevice, stream));
|
||||
CUDA_CHECK(cudaMemcpyAsync((char *) dst->data + size0, src1->data, size1, cudaMemcpyDeviceToDevice, stream));
|
||||
} else {
|
||||
GGML_ASSERT(!ggml_is_quantized(src0->type));
|
||||
|
||||
@@ -208,12 +206,17 @@ void ggml_cuda_op_concat(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
GGML_ASSERT(dst->type == src0->type);
|
||||
|
||||
if (ggml_is_quantized(src0->type)) {
|
||||
GGML_ASSERT(ggml_is_contiguous(src0));
|
||||
GGML_ASSERT(ggml_is_contiguous(src1));
|
||||
if (dim == 3) {
|
||||
GGML_ASSERT(ggml_is_contiguous(src0));
|
||||
GGML_ASSERT(ggml_is_contiguous(src1));
|
||||
} else {
|
||||
GGML_ASSERT(ggml_is_contiguous_to_3(src0));
|
||||
GGML_ASSERT(ggml_is_contiguous_to_3(src1));
|
||||
}
|
||||
GGML_ASSERT(src0->ne[0] % ggml_blck_size(src0->type) == 0);
|
||||
GGML_ASSERT(src1->ne[0] % ggml_blck_size(src1->type) == 0);
|
||||
|
||||
// if tensors are contiguous and ne[0] is multiple of the block size we can concat both tensors as byte tensors
|
||||
// if first 3 dimensions are contiguous and ne[0] is multiple of the block size we can concat both tensors as byte tensors
|
||||
concat_cuda<uint8_t>(src0, src1, dst, dim, stream);
|
||||
} else {
|
||||
GGML_ASSERT(ggml_blck_size(src0->type) == 1);
|
||||
|
||||
+173
-42
@@ -65,6 +65,7 @@
|
||||
#include "ggml-cuda/tri.cuh"
|
||||
#include "ggml-cuda/cumsum.cuh"
|
||||
#include "ggml-cuda/fill.cuh"
|
||||
#include "ggml-cuda/lightning-indexer.cuh"
|
||||
#include "ggml.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -104,17 +105,27 @@ void ggml_cuda_error(const char * stmt, const char * func, const char * file, in
|
||||
GGML_ABORT(GGML_CUDA_NAME " error");
|
||||
}
|
||||
|
||||
// map a (possibly virtual) device id to the physical CUDA device that backs it
|
||||
static int ggml_cuda_get_physical_device(int device) {
|
||||
const ggml_cuda_device_info & info = ggml_cuda_info();
|
||||
GGML_ASSERT(device >= 0 && device < info.device_count);
|
||||
return info.devices[device].physical_device;
|
||||
}
|
||||
|
||||
// this is faster on Windows
|
||||
// probably because the Windows CUDA libraries forget to make this check before invoking the drivers
|
||||
void ggml_cuda_set_device(int device) {
|
||||
// translate the (possibly virtual) device id to the physical CUDA device that backs it
|
||||
const int physical_device = ggml_cuda_get_physical_device(device);
|
||||
|
||||
int current_device;
|
||||
CUDA_CHECK(cudaGetDevice(¤t_device));
|
||||
|
||||
if (device == current_device) {
|
||||
if (physical_device == current_device) {
|
||||
return;
|
||||
}
|
||||
|
||||
CUDA_CHECK(cudaSetDevice(device));
|
||||
CUDA_CHECK(cudaSetDevice(physical_device));
|
||||
}
|
||||
|
||||
int ggml_cuda_get_device() {
|
||||
@@ -205,56 +216,102 @@ static int ggml_cuda_parse_id(char devName[]) {
|
||||
static ggml_cuda_device_info ggml_cuda_init() {
|
||||
ggml_cuda_device_info info = {};
|
||||
|
||||
cudaError_t err = cudaGetDeviceCount(&info.device_count);
|
||||
cudaError_t err = cudaGetDeviceCount(&info.physical_device_count);
|
||||
if (err != cudaSuccess) {
|
||||
GGML_LOG_ERROR("%s: failed to initialize " GGML_CUDA_NAME ": %s\n", __func__, cudaGetErrorString(err));
|
||||
return info;
|
||||
}
|
||||
|
||||
GGML_ASSERT(info.device_count <= GGML_CUDA_MAX_DEVICES);
|
||||
GGML_ASSERT(info.physical_device_count <= GGML_CUDA_MAX_DEVICES);
|
||||
|
||||
// by default expose exactly the physical devices; GGML_CUDA_DEVICES can request a different
|
||||
// number of (virtual) devices to emulate multi-GPU systems on a machine with fewer GPUs
|
||||
info.device_count = info.physical_device_count;
|
||||
|
||||
const char * devices_env = getenv("GGML_CUDA_DEVICES");
|
||||
if (devices_env != nullptr && info.physical_device_count > 0) {
|
||||
const int requested = atoi(devices_env);
|
||||
if (requested > 0) {
|
||||
info.device_count = requested;
|
||||
} else {
|
||||
GGML_LOG_WARN("%s: ignoring invalid GGML_CUDA_DEVICES=\"%s\"\n", __func__, devices_env);
|
||||
}
|
||||
}
|
||||
|
||||
if (info.device_count > GGML_CUDA_MAX_DEVICES) {
|
||||
GGML_LOG_WARN("%s: requested %d devices, clamping to GGML_CUDA_MAX_DEVICES=%d\n",
|
||||
__func__, info.device_count, GGML_CUDA_MAX_DEVICES);
|
||||
info.device_count = GGML_CUDA_MAX_DEVICES;
|
||||
}
|
||||
|
||||
// map each (virtual) device to a backing physical device (round-robin), assign each its index
|
||||
// among the (virtual) devices sharing that physical GPU, and store the per-physical share count
|
||||
int physical_share_count[GGML_CUDA_MAX_DEVICES] = {};
|
||||
GGML_ASSERT(info.device_count == 0 || info.physical_device_count > 0);
|
||||
for (int id = 0; id < info.device_count; ++id) {
|
||||
info.devices[id].physical_device = id % info.physical_device_count;
|
||||
info.devices[id].virtual_index = physical_share_count[info.devices[id].physical_device]++;
|
||||
}
|
||||
|
||||
int64_t total_vram = 0;
|
||||
for (int id = 0; id < info.device_count; ++id) {
|
||||
for (int id = 0; id < info.physical_device_count; ++id) {
|
||||
cudaDeviceProp prop;
|
||||
CUDA_CHECK(cudaGetDeviceProperties(&prop, id));
|
||||
total_vram += prop.totalGlobalMem;
|
||||
}
|
||||
GGML_LOG_INFO("%s: found %d " GGML_CUDA_NAME " devices (Total VRAM: %zu MiB):\n",
|
||||
__func__, info.device_count, (size_t)(total_vram / (1024 * 1024)));
|
||||
__func__, info.physical_device_count, (size_t)(total_vram / (1024 * 1024)));
|
||||
if (info.device_count != info.physical_device_count) {
|
||||
GGML_LOG_INFO("%s: emulating %d virtual device(s) on %d physical device(s) (GGML_CUDA_DEVICES)\n",
|
||||
__func__, info.device_count, info.physical_device_count);
|
||||
}
|
||||
total_vram = 0;
|
||||
|
||||
std::vector<std::pair<int, std::string>> turing_devices_without_mma;
|
||||
for (int id = 0; id < info.device_count; ++id) {
|
||||
const int physical_id = info.devices[id].physical_device;
|
||||
|
||||
int device_vmm = 0;
|
||||
|
||||
#if defined(GGML_USE_VMM)
|
||||
CUdevice device;
|
||||
CU_CHECK(cuDeviceGet(&device, id));
|
||||
CU_CHECK(cuDeviceGet(&device, physical_id));
|
||||
CU_CHECK(cuDeviceGetAttribute(&device_vmm, CU_DEVICE_ATTRIBUTE_VIRTUAL_MEMORY_MANAGEMENT_SUPPORTED, device));
|
||||
|
||||
if (device_vmm) {
|
||||
CUmemAllocationProp alloc_prop = {};
|
||||
alloc_prop.type = CU_MEM_ALLOCATION_TYPE_PINNED;
|
||||
alloc_prop.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
|
||||
alloc_prop.location.id = id;
|
||||
alloc_prop.location.id = physical_id;
|
||||
CU_CHECK(cuMemGetAllocationGranularity(&info.devices[id].vmm_granularity, &alloc_prop, CU_MEM_ALLOC_GRANULARITY_RECOMMENDED));
|
||||
}
|
||||
#endif // defined(GGML_USE_VMM)
|
||||
info.devices[id].vmm = !!device_vmm;
|
||||
|
||||
cudaDeviceProp prop;
|
||||
CUDA_CHECK(cudaGetDeviceProperties(&prop, id));
|
||||
CUDA_CHECK(cudaGetDeviceProperties(&prop, physical_id));
|
||||
|
||||
// a virtual device owns only a share of its physical GPU's memory; report that share so the
|
||||
// logged per-device VRAM sums to the physical total above.
|
||||
GGML_ASSERT(physical_share_count[physical_id] > 0);
|
||||
info.devices[id].physical_share_count = physical_share_count[physical_id];
|
||||
const size_t device_vram = prop.totalGlobalMem / info.devices[id].physical_share_count;
|
||||
const size_t device_vram_mib = device_vram / (1024 * 1024);
|
||||
|
||||
info.default_tensor_split[id] = total_vram;
|
||||
total_vram += prop.totalGlobalMem;
|
||||
total_vram += device_vram;
|
||||
#if defined(GGML_USE_HIP)
|
||||
info.devices[id].integrated = prop.integrated;
|
||||
#else
|
||||
info.devices[id].integrated = false; // Temporarily disabled due to issues with corrupted output (e.g. #15034)
|
||||
#endif
|
||||
info.devices[id].nsm = prop.multiProcessorCount;
|
||||
info.devices[id].smpb = prop.sharedMemPerBlock;
|
||||
info.devices[id].warp_size = prop.warpSize;
|
||||
|
||||
#ifndef GGML_USE_MUSA
|
||||
int supports_coop_launch = 0;
|
||||
CUDA_CHECK(cudaDeviceGetAttribute(&supports_coop_launch, cudaDevAttrCooperativeLaunch, id));
|
||||
CUDA_CHECK(cudaDeviceGetAttribute(&supports_coop_launch, cudaDevAttrCooperativeLaunch, physical_id));
|
||||
info.devices[id].supports_cooperative_launch = !!supports_coop_launch;
|
||||
#else
|
||||
info.devices[id].supports_cooperative_launch = false;
|
||||
@@ -277,7 +334,7 @@ static ggml_cuda_device_info ggml_cuda_init() {
|
||||
GGML_LOG_INFO(" Device %d: %s, %s (0x%x), VMM: %s, Wave Size: %d, VRAM: %zu MiB\n",
|
||||
id, prop.name, prop.gcnArchName, info.devices[id].cc & 0xffff,
|
||||
device_vmm ? "yes" : "no", prop.warpSize,
|
||||
(size_t)(prop.totalGlobalMem / (1024 * 1024)));
|
||||
device_vram_mib);
|
||||
#elif defined(GGML_USE_MUSA)
|
||||
// FIXME: Ensure compatibility with varying warp sizes across different MUSA archs.
|
||||
info.devices[id].warp_size = 32;
|
||||
@@ -286,13 +343,13 @@ static ggml_cuda_device_info ggml_cuda_init() {
|
||||
info.devices[id].cc += prop.minor * 0x10;
|
||||
GGML_LOG_INFO(" Device %d: %s, compute capability %d.%d, VMM: %s, VRAM: %zu MiB\n",
|
||||
id, prop.name, prop.major, prop.minor, device_vmm ? "yes" : "no",
|
||||
(size_t)(prop.totalGlobalMem / (1024 * 1024)));
|
||||
device_vram_mib);
|
||||
#else
|
||||
info.devices[id].smpbo = prop.sharedMemPerBlockOptin;
|
||||
info.devices[id].cc = 100*prop.major + 10*prop.minor;
|
||||
GGML_LOG_INFO(" Device %d: %s, compute capability %d.%d, VMM: %s, VRAM: %zu MiB\n",
|
||||
id, prop.name, prop.major, prop.minor, device_vmm ? "yes" : "no",
|
||||
(size_t)(prop.totalGlobalMem / (1024 * 1024)));
|
||||
device_vram_mib);
|
||||
std::string device_name(prop.name);
|
||||
if (device_name == "NVIDIA GeForce MX450") {
|
||||
turing_devices_without_mma.push_back({ id, device_name });
|
||||
@@ -307,7 +364,7 @@ static ggml_cuda_device_info ggml_cuda_init() {
|
||||
// TODO: Check for future drivers the default scheduling strategy and
|
||||
// remove this call again when cudaDeviceScheduleSpin is default.
|
||||
if (prop.major == 12 && prop.minor == 1) {
|
||||
CUDA_CHECK(cudaSetDevice(id));
|
||||
CUDA_CHECK(cudaSetDevice(physical_id));
|
||||
CUDA_CHECK(cudaSetDeviceFlags(cudaDeviceScheduleSpin));
|
||||
}
|
||||
|
||||
@@ -332,9 +389,9 @@ static ggml_cuda_device_info ggml_cuda_init() {
|
||||
// CUBLAS_CHECK(cublasLoggerConfigure(1, 1, 0, nullptr));
|
||||
|
||||
if (getenv("GGML_CUDA_P2P") != nullptr) {
|
||||
for (int id = 0; id < info.device_count; ++id) {
|
||||
ggml_cuda_set_device(id);
|
||||
for (int id_other = 0; id_other < info.device_count; ++id_other) {
|
||||
for (int id = 0; id < info.physical_device_count; ++id) {
|
||||
CUDA_CHECK(cudaSetDevice(id));
|
||||
for (int id_other = 0; id_other < info.physical_device_count; ++id_other) {
|
||||
if (id == id_other) {
|
||||
continue;
|
||||
}
|
||||
@@ -479,6 +536,7 @@ struct ggml_cuda_pool_vmm : public ggml_cuda_pool {
|
||||
static const size_t CUDA_POOL_VMM_MAX_SIZE = 1ull << 35; // 32 GB
|
||||
|
||||
int device;
|
||||
int physical_device;
|
||||
CUdeviceptr pool_addr = 0;
|
||||
size_t pool_used = 0;
|
||||
size_t pool_size = 0;
|
||||
@@ -489,6 +547,7 @@ struct ggml_cuda_pool_vmm : public ggml_cuda_pool {
|
||||
|
||||
explicit ggml_cuda_pool_vmm(int device) :
|
||||
device(device),
|
||||
physical_device(ggml_cuda_get_physical_device(device)),
|
||||
granularity(ggml_cuda_info().devices[device].vmm_granularity) {
|
||||
}
|
||||
|
||||
@@ -524,7 +583,7 @@ struct ggml_cuda_pool_vmm : public ggml_cuda_pool {
|
||||
CUmemAllocationProp prop = {};
|
||||
prop.type = CU_MEM_ALLOCATION_TYPE_PINNED;
|
||||
prop.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
|
||||
prop.location.id = device;
|
||||
prop.location.id = physical_device;
|
||||
CUmemGenericAllocationHandle handle;
|
||||
CU_CHECK(cuMemCreate(&handle, reserve_size, &prop, 0));
|
||||
|
||||
@@ -553,20 +612,28 @@ struct ggml_cuda_pool_vmm : public ggml_cuda_pool {
|
||||
// NCCL implicitly enables peer access (cudaDeviceEnablePeerAccess), and
|
||||
// GGML_CUDA_P2P enables it explicitly. Unlike cudaMalloc buffers, VMM
|
||||
// allocations do not become peer-accessible from that alone, so access
|
||||
// must be granted explicitly here.
|
||||
// must be granted explicitly here. With virtual devices, grant access
|
||||
// on the backing *physical* devices (deduplicated, since several
|
||||
// virtual devices can map to the same physical GPU).
|
||||
std::vector<CUmemAccessDesc> access_descs;
|
||||
bool physical_seen[GGML_CUDA_MAX_DEVICES] = {};
|
||||
const int device_count = ggml_cuda_info().device_count;
|
||||
for (int id = 0; id < device_count; ++id) {
|
||||
if (id != device) {
|
||||
const int id_physical = ggml_cuda_get_physical_device(id);
|
||||
if (id_physical != physical_device) {
|
||||
int can_access_peer = 0;
|
||||
CUDA_CHECK(cudaDeviceCanAccessPeer(&can_access_peer, id, device));
|
||||
CUDA_CHECK(cudaDeviceCanAccessPeer(&can_access_peer, id_physical, physical_device));
|
||||
if (!can_access_peer) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (physical_seen[id_physical]) {
|
||||
continue;
|
||||
}
|
||||
physical_seen[id_physical] = true;
|
||||
CUmemAccessDesc access = {};
|
||||
access.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
|
||||
access.location.id = id;
|
||||
access.location.id = id_physical;
|
||||
access.flags = CU_MEM_ACCESS_FLAGS_PROT_READWRITE;
|
||||
access_descs.push_back(access);
|
||||
}
|
||||
@@ -575,7 +642,7 @@ struct ggml_cuda_pool_vmm : public ggml_cuda_pool {
|
||||
// set access for non P2P
|
||||
CUmemAccessDesc access = {};
|
||||
access.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
|
||||
access.location.id = device;
|
||||
access.location.id = physical_device;
|
||||
access.flags = CU_MEM_ACCESS_FLAGS_PROT_READWRITE;
|
||||
CU_CHECK(cuMemSetAccess(start_ptr, reserve_size, &access, 1));
|
||||
}
|
||||
@@ -751,13 +818,17 @@ static bool ggml_backend_cuda_buffer_cpy_tensor(ggml_backend_buffer_t buffer, co
|
||||
if (ggml_backend_buffer_is_cuda(src->buffer)) {
|
||||
ggml_backend_cuda_buffer_context * src_ctx = (ggml_backend_cuda_buffer_context *)src->buffer->context;
|
||||
ggml_backend_cuda_buffer_context * dst_ctx = (ggml_backend_cuda_buffer_context *)dst->buffer->context;
|
||||
if (src_ctx->device == dst_ctx->device) {
|
||||
// compare the backing physical devices: distinct virtual devices may share one physical GPU,
|
||||
// in which case a same-device copy (not a peer copy) is required
|
||||
const int src_physical = ggml_cuda_get_physical_device(src_ctx->device);
|
||||
const int dst_physical = ggml_cuda_get_physical_device(dst_ctx->device);
|
||||
if (src_physical == dst_physical) {
|
||||
CUDA_CHECK(cudaMemcpyAsync(dst->data, src->data, ggml_nbytes(src), cudaMemcpyDeviceToDevice, cudaStreamPerThread));
|
||||
} else {
|
||||
#ifdef GGML_CUDA_NO_PEER_COPY
|
||||
return false;
|
||||
#else
|
||||
CUDA_CHECK(cudaMemcpyPeerAsync(dst->data, dst_ctx->device, src->data, src_ctx->device, ggml_nbytes(src), cudaStreamPerThread));
|
||||
CUDA_CHECK(cudaMemcpyPeerAsync(dst->data, dst_physical, src->data, src_physical, ggml_nbytes(src), cudaStreamPerThread));
|
||||
#endif
|
||||
}
|
||||
CUDA_CHECK(cudaStreamSynchronize(cudaStreamPerThread));
|
||||
@@ -1099,6 +1170,15 @@ static void ggml_backend_cuda_comm_init_internal(ggml_backend_cuda_comm_context
|
||||
|
||||
static void ggml_backend_cuda_comm_init_nccl(ggml_backend_cuda_comm_context * ret) {
|
||||
#ifdef GGML_USE_NCCL
|
||||
// Disabling NCCL path when CUDA virtual devices are in use since NCCL requires one distinct physical GPU per rank.
|
||||
const ggml_cuda_device_info & info = ggml_cuda_info();
|
||||
if (info.device_count > info.physical_device_count) {
|
||||
GGML_LOG_WARN("NCCL disabled: virtual devices in use; "
|
||||
"falling back to internal AllReduce\n");
|
||||
ggml_backend_cuda_comm_init_internal(ret);
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t n = ret->dev_ids.size();
|
||||
ret->comms.resize(n);
|
||||
ncclResult_t rc = ncclCommInitAll(ret->comms.data(), (int) n, ret->dev_ids.data());
|
||||
@@ -2257,6 +2337,9 @@ static bool ggml_cuda_compute_forward(ggml_backend_cuda_context & ctx, struct gg
|
||||
case GGML_OP_FILL:
|
||||
ggml_cuda_op_fill(ctx, dst);
|
||||
break;
|
||||
case GGML_OP_LIGHTNING_INDEXER:
|
||||
ggml_cuda_lightning_indexer(ctx, dst);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2355,13 +2438,17 @@ static bool ggml_backend_cuda_cpy_tensor_async(ggml_backend_t backend_src, ggml_
|
||||
|
||||
if (backend_src != backend_dst) {
|
||||
// copy on src stream
|
||||
if (cuda_ctx_src->device == cuda_ctx_dst->device) {
|
||||
// compare the backing physical devices: distinct virtual devices may share one physical GPU,
|
||||
// in which case a same-device copy (not a peer copy) is required
|
||||
const int src_physical = ggml_cuda_get_physical_device(cuda_ctx_src->device);
|
||||
const int dst_physical = ggml_cuda_get_physical_device(cuda_ctx_dst->device);
|
||||
if (src_physical == dst_physical) {
|
||||
CUDA_CHECK(cudaMemcpyAsync(dst->data, src->data, ggml_nbytes(dst), cudaMemcpyDeviceToDevice, cuda_ctx_src->stream()));
|
||||
} else {
|
||||
#ifdef GGML_CUDA_NO_PEER_COPY
|
||||
return false;
|
||||
#else
|
||||
CUDA_CHECK(cudaMemcpyPeerAsync(dst->data, cuda_ctx_dst->device, src->data, cuda_ctx_src->device, ggml_nbytes(dst), cuda_ctx_src->stream()));
|
||||
CUDA_CHECK(cudaMemcpyPeerAsync(dst->data, dst_physical, src->data, src_physical, ggml_nbytes(dst), cuda_ctx_src->stream()));
|
||||
#endif // GGML_CUDA_NO_PEER_COPY
|
||||
}
|
||||
|
||||
@@ -3974,7 +4061,7 @@ static bool ggml_cuda_graph_set_enabled(ggml_backend_cuda_context * cuda_ctx, co
|
||||
ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key);
|
||||
|
||||
if (graph->graph == nullptr) {
|
||||
if (ggml_cuda_info().devices[cuda_ctx->device].cc < GGML_CUDA_CC_AMPERE) {
|
||||
if (ggml_cuda_info().devices[cuda_ctx->device].cc < GGML_CUDA_CC_VOLTA) {
|
||||
if (!graph->disable_due_to_gpu_arch) {
|
||||
GGML_LOG_DEBUG("%s: disabling CUDA graphs due to GPU architecture\n", __func__);
|
||||
}
|
||||
@@ -4346,16 +4433,38 @@ int ggml_backend_cuda_get_device_count() {
|
||||
return ggml_cuda_info().device_count;
|
||||
}
|
||||
|
||||
void ggml_backend_cuda_get_device_description(int device, char * description, size_t description_size) {
|
||||
static std::string ggml_cuda_device_description(int device) {
|
||||
cudaDeviceProp prop;
|
||||
CUDA_CHECK(cudaGetDeviceProperties(&prop, device));
|
||||
snprintf(description, description_size, "%s", prop.name);
|
||||
CUDA_CHECK(cudaGetDeviceProperties(&prop, ggml_cuda_get_physical_device(device)));
|
||||
|
||||
const ggml_cuda_device_info & info = ggml_cuda_info();
|
||||
std::string description = prop.name;
|
||||
if (info.device_count > info.physical_device_count) {
|
||||
description += " (physical device " + std::to_string(info.devices[device].physical_device) +
|
||||
", virtual device " + std::to_string(info.devices[device].virtual_index) + ")";
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
void ggml_backend_cuda_get_device_description(int device, char * description, size_t description_size) {
|
||||
snprintf(description, description_size, "%s", ggml_cuda_device_description(device).c_str());
|
||||
}
|
||||
|
||||
static int ggml_cuda_physical_device_share_count(int device) {
|
||||
const ggml_cuda_device_info & info = ggml_cuda_info();
|
||||
GGML_ASSERT(device >= 0 && device < info.device_count);
|
||||
return info.devices[device].physical_share_count;
|
||||
}
|
||||
|
||||
void ggml_backend_cuda_get_device_memory(int device, size_t * free, size_t * total) {
|
||||
ggml_cuda_set_device(device);
|
||||
|
||||
CUDA_CHECK(cudaMemGetInfo(free, total));
|
||||
|
||||
// virtual devices sharing one physical GPU share its memory pool; split it between them
|
||||
const int share_count = ggml_cuda_physical_device_share_count(device);
|
||||
*free /= share_count;
|
||||
*total /= share_count;
|
||||
}
|
||||
|
||||
bool ggml_backend_cuda_register_host_buffer(void * buffer, size_t size) {
|
||||
@@ -4506,7 +4615,7 @@ static void ggml_backend_cuda_device_get_memory(ggml_backend_dev_t dev, size_t *
|
||||
#if defined(__linux__)
|
||||
// Check if this is a UMA (Unified Memory Architecture) system
|
||||
cudaDeviceProp prop;
|
||||
CUDA_CHECK(cudaGetDeviceProperties(&prop, ctx->device));
|
||||
CUDA_CHECK(cudaGetDeviceProperties(&prop, ggml_cuda_get_physical_device(ctx->device)));
|
||||
|
||||
// Check if UMA is explicitly enabled via environment variable
|
||||
bool uma_env = getenv("GGML_CUDA_ENABLE_UNIFIED_MEMORY") != nullptr;
|
||||
@@ -4525,13 +4634,17 @@ static void ggml_backend_cuda_device_get_memory(ggml_backend_dev_t dev, size_t *
|
||||
}
|
||||
#endif // defined(__linux__)
|
||||
|
||||
// virtual devices sharing one physical GPU share its memory pool; split it between them
|
||||
const int share_count = ggml_cuda_physical_device_share_count(ctx->device);
|
||||
*free /= share_count;
|
||||
*total /= share_count;
|
||||
}
|
||||
|
||||
static enum ggml_backend_dev_type ggml_backend_cuda_device_get_type(ggml_backend_dev_t dev) {
|
||||
ggml_backend_cuda_device_context * ctx = (ggml_backend_cuda_device_context *) dev->context;
|
||||
|
||||
cudaDeviceProp prop;
|
||||
CUDA_CHECK(cudaGetDeviceProperties(&prop, ctx->device));
|
||||
CUDA_CHECK(cudaGetDeviceProperties(&prop, ggml_cuda_get_physical_device(ctx->device)));
|
||||
|
||||
return prop.integrated
|
||||
? GGML_BACKEND_DEVICE_TYPE_IGPU
|
||||
@@ -4816,13 +4929,23 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
|
||||
{
|
||||
ggml_type src0_type = op->src[0]->type;
|
||||
ggml_type src1_type = op->src[1]->type;
|
||||
const int32_t dim = op->op_params[0];
|
||||
return src0_type == src1_type &&
|
||||
src0_type == op->type &&
|
||||
(
|
||||
(
|
||||
ggml_is_quantized(src0_type) &&
|
||||
ggml_is_contiguous(op->src[0]) &&
|
||||
ggml_is_contiguous(op->src[1]) &&
|
||||
(
|
||||
(
|
||||
dim == 3 &&
|
||||
ggml_is_contiguous(op->src[0]) &&
|
||||
ggml_is_contiguous(op->src[1])
|
||||
) || (
|
||||
dim != 3 &&
|
||||
ggml_is_contiguous_to_3(op->src[0]) &&
|
||||
ggml_is_contiguous_to_3(op->src[1])
|
||||
)
|
||||
) &&
|
||||
op->src[0]->ne[0] % ggml_blck_size(src0_type) == 0 &&
|
||||
op->src[1]->ne[0] % ggml_blck_size(src0_type) == 0
|
||||
) || (
|
||||
@@ -4977,6 +5100,8 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
|
||||
case GGML_OP_DIAG:
|
||||
case GGML_OP_SOLVE_TRI:
|
||||
return true;
|
||||
case GGML_OP_LIGHTNING_INDEXER:
|
||||
return ggml_cuda_lightning_indexer_supported(dev_ctx->device, op);
|
||||
|
||||
default:
|
||||
return false;
|
||||
@@ -5179,18 +5304,24 @@ ggml_backend_reg_t ggml_backend_cuda_reg() {
|
||||
ggml_backend_cuda_reg_context * ctx = new ggml_backend_cuda_reg_context;
|
||||
const int min_batch_size = getenv("GGML_OP_OFFLOAD_MIN_BATCH") ? atoi(getenv("GGML_OP_OFFLOAD_MIN_BATCH")) : 32;
|
||||
|
||||
for (int i = 0; i < ggml_cuda_info().device_count; i++) {
|
||||
const ggml_cuda_device_info & info = ggml_cuda_info();
|
||||
const bool virtual_devices = info.device_count > info.physical_device_count;
|
||||
|
||||
for (int i = 0; i < info.device_count; i++) {
|
||||
const int physical_id = info.devices[i].physical_device;
|
||||
|
||||
ggml_backend_cuda_device_context * dev_ctx = new ggml_backend_cuda_device_context;
|
||||
dev_ctx->device = i;
|
||||
dev_ctx->name = GGML_CUDA_NAME + std::to_string(i);
|
||||
|
||||
cudaDeviceProp prop;
|
||||
CUDA_CHECK(cudaGetDeviceProperties(&prop, i));
|
||||
dev_ctx->description = prop.name;
|
||||
dev_ctx->description = ggml_cuda_device_description(i);
|
||||
|
||||
char pci_bus_id[32] = {};
|
||||
CUDA_CHECK(cudaDeviceGetPCIBusId(pci_bus_id, sizeof(pci_bus_id), i));
|
||||
CUDA_CHECK(cudaDeviceGetPCIBusId(pci_bus_id, sizeof(pci_bus_id), physical_id));
|
||||
dev_ctx->pci_bus_id = pci_bus_id;
|
||||
if (virtual_devices) {
|
||||
// make the pci bus id unique for virtual devices
|
||||
dev_ctx->pci_bus_id += "-v" + std::to_string(i);
|
||||
}
|
||||
for (char & c : dev_ctx->pci_bus_id) {
|
||||
c = std::tolower(c);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,588 @@
|
||||
#include "common.cuh"
|
||||
#include "lightning-indexer.cuh"
|
||||
#include "fattn-common.cuh"
|
||||
#include "convert.cuh"
|
||||
|
||||
#if !defined(GGML_USE_HIP) && !defined(GGML_USE_MUSA)
|
||||
#if defined(TURING_MMA_AVAILABLE)
|
||||
|
||||
typedef union {
|
||||
int2 i2;
|
||||
half2 h2[2];
|
||||
} half4;
|
||||
|
||||
// TODO add support for AMD cards via rocWMMA
|
||||
#include <mma.h>
|
||||
namespace wmma = nvcuda::wmma;
|
||||
|
||||
template <int WARPS_PER_BLOCK, int K_VECS_PER_BLOCK, int64_t N_EMBD, int64_t N_HEAD, ggml_type TYPE_K>
|
||||
static __global__ void lightning_indexer_kernel_wmma(
|
||||
const float * Q, const char * K, const float * W, const half * M, float * dst,
|
||||
int64_t n_stream, int64_t n_batch, int64_t n_kv,
|
||||
size_t nb1, size_t nb2, size_t nb3,
|
||||
size_t nbq1, size_t nbq2, size_t nbq3,
|
||||
size_t nbk1, size_t nbk2, size_t nbk3,
|
||||
size_t nbw1, size_t nbw2, size_t nbw3,
|
||||
size_t nbm1, size_t nbm2, size_t nbm3,
|
||||
int64_t nem3
|
||||
) {
|
||||
|
||||
constexpr int THREADS_PER_BLOCK = WARPS_PER_BLOCK * WARP_SIZE;
|
||||
constexpr int HEADS_PER_INNER_LOOP = 8;
|
||||
constexpr int K_EMBD_PER_INNER_LOOP = 16;
|
||||
constexpr int N_EMBD_PADDED = N_EMBD + 8;
|
||||
|
||||
const int i_batch = blockIdx.y;
|
||||
const int i_stream = blockIdx.z;
|
||||
const int i_warp = threadIdx.y;
|
||||
const int i_lane = threadIdx.x;
|
||||
const int tid = i_warp * WARP_SIZE + i_lane;
|
||||
|
||||
// each block processes K_VECS_PER_BLOCK K vectors
|
||||
const int start_kv = blockIdx.x * K_VECS_PER_BLOCK;
|
||||
|
||||
const char * q_base = (const char *) Q + i_batch*nbq2 + i_stream*nbq3;
|
||||
const float * w_base = (const float *) ((const char *) W + i_batch*nbw1 + i_stream*nbw3);
|
||||
|
||||
// phase 1 - load weights and first Q tile to shared memory
|
||||
|
||||
__shared__ float w_shared[N_HEAD];
|
||||
__shared__ int2 q_shared_h[HEADS_PER_INNER_LOOP][N_EMBD_PADDED / 4];
|
||||
|
||||
if (tid < N_HEAD) {
|
||||
w_shared[tid] = w_base[tid];
|
||||
}
|
||||
|
||||
// total number of half4 elements in HEADS_PER_INNER_LOOP x N_EMBD Q tile
|
||||
constexpr int N_Q_TILE = HEADS_PER_INNER_LOOP * (N_EMBD / 4);
|
||||
// number of registers needed in each thread to store Q tile in thread block
|
||||
constexpr int N_Q_NEXT = (N_Q_TILE + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK;
|
||||
|
||||
#pragma unroll
|
||||
for (int i_q = tid; i_q < N_Q_TILE; i_q += THREADS_PER_BLOCK) {
|
||||
const int i_head = i_q / (N_EMBD / 4);
|
||||
const int i_embd = i_q % (N_EMBD / 4);
|
||||
const float4 q = *(const float4 *) (q_base + i_head*nbq1 + i_embd*sizeof(float4));
|
||||
half4 q_packed;
|
||||
q_packed.h2[0] = __float22half2_rn(make_float2(q.x, q.y));
|
||||
q_packed.h2[1] = __float22half2_rn(make_float2(q.z, q.w));
|
||||
q_shared_h[i_head][i_embd] = q_packed.i2;
|
||||
}
|
||||
|
||||
// phase 2 - load (and dequantize if needed) K to shared mem
|
||||
|
||||
__shared__ half2 k_shared_h[K_VECS_PER_BLOCK][N_EMBD_PADDED / 4][2];
|
||||
|
||||
constexpr int n_k = K_VECS_PER_BLOCK * (N_EMBD / 4);
|
||||
|
||||
if constexpr (TYPE_K == GGML_TYPE_F16) {
|
||||
#pragma unroll
|
||||
for (int i_k = tid; i_k < n_k; i_k += THREADS_PER_BLOCK) {
|
||||
const int i_k_vec = i_k / (N_EMBD / 4);
|
||||
const int i_embd = i_k % (N_EMBD / 4);
|
||||
const int i_kv = start_kv + i_k_vec;
|
||||
if (i_kv < n_kv) {
|
||||
const int2 * k_base = (const int2 *) ((const char *) K + i_kv*nbk2 + i_stream*nbk3);
|
||||
*(int2*) &k_shared_h[i_k_vec][i_embd] = k_base[i_embd];
|
||||
} else {
|
||||
*(int2*) &k_shared_h[i_k_vec][i_embd] = make_int2(0, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
constexpr dequantize_V_t dequantize_k = get_dequantize_V<TYPE_K, half, 4>();
|
||||
#pragma unroll
|
||||
for (int i_k = tid; i_k < n_k; i_k += THREADS_PER_BLOCK) {
|
||||
const int i_k_vec = i_k / (N_EMBD / 4);
|
||||
const int i_embd = i_k % (N_EMBD / 4);
|
||||
const int i_kv = start_kv + i_k_vec;
|
||||
if (i_kv < n_kv) {
|
||||
const void * k_base = (const void *) ((const char *) K + i_kv*nbk2 + i_stream*nbk3);
|
||||
dequantize_k(k_base, &k_shared_h[i_k_vec][i_embd][0], i_embd * 4);
|
||||
} else {
|
||||
*(int2*) &k_shared_h[i_k_vec][i_embd] = make_int2(0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// phase 3 - calculate lightning indexer scores
|
||||
|
||||
__shared__ float qk_shared[WARPS_PER_BLOCK][HEADS_PER_INNER_LOOP][K_VECS_PER_BLOCK];
|
||||
|
||||
// load K fragment
|
||||
wmma::fragment<wmma::matrix_b, HEADS_PER_INNER_LOOP, K_VECS_PER_BLOCK, K_EMBD_PER_INNER_LOOP, half, wmma::col_major> frag_k;
|
||||
wmma::load_matrix_sync(frag_k, (half*) &k_shared_h[0][i_warp * K_EMBD_PER_INNER_LOOP / 4], N_EMBD_PADDED);
|
||||
|
||||
float score_k = 0.0f;
|
||||
|
||||
for (int i_head_0 = 0; i_head_0 < N_HEAD; i_head_0 += HEADS_PER_INNER_LOOP) {
|
||||
const int i_head_next = i_head_0 + HEADS_PER_INNER_LOOP;
|
||||
|
||||
// we don't use accumulator for anything, fill it with zeros
|
||||
wmma::fragment<wmma::accumulator, HEADS_PER_INNER_LOOP, K_VECS_PER_BLOCK, K_EMBD_PER_INNER_LOOP, float> frag_acc;
|
||||
wmma::fill_fragment(frag_acc, 0.0f);
|
||||
|
||||
// load Q fragment
|
||||
wmma::fragment<wmma::matrix_a, HEADS_PER_INNER_LOOP, K_VECS_PER_BLOCK, K_EMBD_PER_INNER_LOOP, half, wmma::row_major> frag_q;
|
||||
wmma::load_matrix_sync(frag_q, (half*) &q_shared_h[0][i_warp * K_EMBD_PER_INNER_LOOP / 4], N_EMBD_PADDED);
|
||||
|
||||
// preload next Q tile to registers during matrix multiplication
|
||||
float4 q_next[N_Q_NEXT];
|
||||
|
||||
if (i_head_next < N_HEAD) {
|
||||
#pragma unroll
|
||||
for (int i_q = tid, i_q_next = 0; i_q < N_Q_TILE; i_q += THREADS_PER_BLOCK) {
|
||||
const int i_head = i_head_next + i_q / (N_EMBD / 4);
|
||||
const int i_embd = i_q % (N_EMBD / 4);
|
||||
q_next[i_q_next++] = *(const float4 *) (q_base + i_head*nbq1 + i_embd*sizeof(float4));
|
||||
}
|
||||
}
|
||||
|
||||
// perform matrix multiplication
|
||||
wmma::mma_sync(frag_acc, frag_q, frag_k, frag_acc);
|
||||
wmma::store_matrix_sync((float*) &qk_shared[i_warp][0][0], frag_acc, K_VECS_PER_BLOCK, wmma::mem_row_major);
|
||||
|
||||
// make sure all threads finished using q_shared_h so we can store next tile
|
||||
__syncthreads();
|
||||
|
||||
// write preloaded Q tile to shared memory
|
||||
if (i_head_next < N_HEAD) {
|
||||
#pragma unroll
|
||||
for (int i_q = tid, i_q_next = 0; i_q < N_Q_TILE; i_q += THREADS_PER_BLOCK) {
|
||||
const int i_head = i_q / (N_EMBD / 4);
|
||||
const int i_embd = i_q % (N_EMBD / 4);
|
||||
half4 q_packed;
|
||||
q_packed.h2[0] = __float22half2_rn(make_float2(q_next[i_q_next].x, q_next[i_q_next].y));
|
||||
q_packed.h2[1] = __float22half2_rn(make_float2(q_next[i_q_next].z, q_next[i_q_next].w));
|
||||
q_shared_h[i_head][i_embd] = q_packed.i2;
|
||||
++i_q_next;
|
||||
}
|
||||
}
|
||||
|
||||
// accumulate QK multiplication results from all block warps
|
||||
// (there are 256 threads in block and 256 matmul outputs)
|
||||
// TODO it will break if WARP_SIZE is not 32
|
||||
const int h = tid / K_VECS_PER_BLOCK;
|
||||
const int k = tid % K_VECS_PER_BLOCK;
|
||||
const float w_val = w_shared[i_head_0 + h];
|
||||
|
||||
float sum = 0.0f;
|
||||
#pragma unroll
|
||||
for (int w = 0; w < WARPS_PER_BLOCK; ++w) {
|
||||
sum += qk_shared[w][h][k];
|
||||
}
|
||||
|
||||
// ReLU, weight
|
||||
sum = sum > 0.0f ? sum : 0.0f;
|
||||
sum *= w_val;
|
||||
|
||||
// wait until qk_shared[0] is no longer used
|
||||
__syncthreads();
|
||||
|
||||
// reuse qk_shared[0] for storing partial results
|
||||
qk_shared[0][h][k] = sum;
|
||||
|
||||
// wait until all threads write their results
|
||||
__syncthreads();
|
||||
|
||||
// accumulate result over heads
|
||||
if (tid < K_VECS_PER_BLOCK) {
|
||||
#pragma unroll
|
||||
for (int i_head = 0; i_head < HEADS_PER_INNER_LOOP; ++i_head) {
|
||||
score_k += qk_shared[0][i_head][tid];
|
||||
}
|
||||
}
|
||||
|
||||
// make sure all threads finished using qk_shared
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
// phase 4 - store output to VRAM
|
||||
|
||||
if (tid < K_VECS_PER_BLOCK) {
|
||||
const int i_kv = start_kv + tid;
|
||||
if (i_kv < n_kv) {
|
||||
const half * m_base = (const half *) ((const char *) M + i_batch*nbm1 + (i_stream%nem3)*nbm3);
|
||||
float * dst_base = (float *) ((char *) dst + i_batch*nb1 + i_stream*nb3);
|
||||
dst_base[i_kv] = score_k + __half2float(m_base[i_kv]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else // defined(TURING_MMA_AVAILABLE)
|
||||
|
||||
template <int WARPS_PER_BLOCK, int K_VECS_PER_BLOCK, int64_t N_EMBD, int64_t N_HEAD, ggml_type TYPE_K>
|
||||
static __global__ void lightning_indexer_kernel_wmma(
|
||||
const float * Q, const char * K, const float * W, const half * M, float * dst,
|
||||
int64_t n_stream, int64_t n_batch, int64_t n_kv,
|
||||
size_t nb1, size_t nb2, size_t nb3,
|
||||
size_t nbq1, size_t nbq2, size_t nbq3,
|
||||
size_t nbk1, size_t nbk2, size_t nbk3,
|
||||
size_t nbw1, size_t nbw2, size_t nbw3,
|
||||
size_t nbm1, size_t nbm2, size_t nbm3,
|
||||
int64_t nem3
|
||||
) {
|
||||
GGML_UNUSED_VARS(Q, K, W, M, dst,
|
||||
n_stream, n_batch, n_kv,
|
||||
nb1, nb2, nb3,
|
||||
nbq1, nbq2, nbq3,
|
||||
nbk1, nbk2, nbk3,
|
||||
nbw1, nbw2, nbw3,
|
||||
nem3);
|
||||
NO_DEVICE_CODE;
|
||||
}
|
||||
|
||||
#endif // defined(TURING_MMA_AVAILABLE)
|
||||
#endif // !defined(GGML_USE_HIP) && !defined(GGML_USE_MUSA)
|
||||
|
||||
// TODO there is one ugly assumption used in this kernel - that WARP_SIZE is equal to 32
|
||||
// thanks to that one warp operating on float4 processes whole indexer K/Q vectors
|
||||
// 32 * 4 = 128 (N_EMBD)
|
||||
|
||||
template <int WARPS_PER_BLOCK, int K_VECS_PER_BLOCK, int64_t N_EMBD, int64_t N_HEAD, ggml_type TYPE_K>
|
||||
static __global__ void lightning_indexer_kernel_vec(
|
||||
const float * Q, const char * K, const float * W, const half * M, float * dst,
|
||||
int64_t n_stream, int64_t n_batch, int64_t n_kv,
|
||||
size_t nb1, size_t nb2, size_t nb3,
|
||||
size_t nbq1, size_t nbq2, size_t nbq3,
|
||||
size_t nbk1, size_t nbk2, size_t nbk3,
|
||||
size_t nbw1, size_t nbw2, size_t nbw3,
|
||||
size_t nbm1, size_t nbm2, size_t nbm3,
|
||||
int64_t nem3
|
||||
) {
|
||||
|
||||
constexpr int K_VECS_PER_WARP = K_VECS_PER_BLOCK / WARPS_PER_BLOCK;
|
||||
constexpr int THREADS_PER_BLOCK = WARPS_PER_BLOCK * WARP_SIZE;
|
||||
|
||||
const int i_batch = blockIdx.y;
|
||||
const int i_stream = blockIdx.z;
|
||||
const int i_warp = threadIdx.y;
|
||||
const int i_lane = threadIdx.x;
|
||||
const int tid = i_warp * WARP_SIZE + i_lane;
|
||||
|
||||
// each warp processes K_VECS_PER_WARP K vectors
|
||||
const int start_kv_block = blockIdx.x * K_VECS_PER_BLOCK;
|
||||
const int start_kv = start_kv_block + i_warp * K_VECS_PER_WARP;
|
||||
|
||||
const char * q_base = (const char *) Q + i_batch*nbq2 + i_stream*nbq3;
|
||||
const float * w_base = (const float *) ((const char *) W + i_batch*nbw1 + i_stream*nbw3);
|
||||
|
||||
// phase 1 - load (and dequantize if needed) K to registers
|
||||
|
||||
float4 k_reg_f[K_VECS_PER_WARP];
|
||||
|
||||
if constexpr (TYPE_K == GGML_TYPE_F32) {
|
||||
// direct copy of float4
|
||||
#pragma unroll
|
||||
for (int k = 0; k < K_VECS_PER_WARP; ++k) {
|
||||
int i_kv = start_kv + k;
|
||||
if (i_kv < n_kv) {
|
||||
const float4 * k_base = (const float4 *) ((const char *) K + i_kv*nbk2 + i_stream*nbk3);
|
||||
k_reg_f[k] = k_base[i_lane];
|
||||
} else {
|
||||
k_reg_f[k] = make_float4(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// dequantize remaining types to float
|
||||
constexpr dequantize_V_t dequantize_k = get_dequantize_V<TYPE_K, float, 4>();
|
||||
#pragma unroll
|
||||
for (int k = 0; k < K_VECS_PER_WARP; ++k) {
|
||||
int i_kv = start_kv + k;
|
||||
if (i_kv < n_kv) {
|
||||
const void * k_base = (const void *) ((const char *) K + i_kv*nbk2 + i_stream*nbk3);
|
||||
dequantize_k(k_base, &k_reg_f[k], i_lane * 4);
|
||||
} else {
|
||||
k_reg_f[k] = make_float4(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float score_k[K_VECS_PER_WARP] = { 0.0f };
|
||||
|
||||
// load weights and Q only for N_HEAD_INNER heads at once to reduce shared memory usage
|
||||
constexpr int N_HEAD_INNER = N_HEAD / 4;
|
||||
|
||||
for (int i_head_0 = 0; i_head_0 < N_HEAD; i_head_0 += N_HEAD_INNER) {
|
||||
// phase 2 - load weights and Q to shared memory
|
||||
|
||||
__shared__ float w_shared[N_HEAD_INNER];
|
||||
__shared__ float4 q_shared_f[N_HEAD_INNER][N_EMBD / 4];
|
||||
|
||||
if (tid < N_HEAD_INNER) {
|
||||
w_shared[tid] = w_base[i_head_0 + tid];
|
||||
}
|
||||
|
||||
constexpr int n_q = N_HEAD_INNER * (N_EMBD / 4);
|
||||
#pragma unroll
|
||||
for (int i_q = tid; i_q < n_q; i_q += THREADS_PER_BLOCK) {
|
||||
const int i_head_inner = i_q / (N_EMBD / 4);
|
||||
const int i_head = i_head_0 + i_head_inner;
|
||||
const int i_embd = i_q % (N_EMBD / 4);
|
||||
q_shared_f[i_head_inner][i_embd] = *(const float4 *) (q_base + i_head*nbq1 + i_embd*sizeof(float4));
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// phase 3 - calculate lightning indexer scores
|
||||
|
||||
for (int i_head_inner = 0; i_head_inner < N_HEAD_INNER; ++i_head_inner) {
|
||||
const float w_val = w_shared[i_head_inner];
|
||||
float qk[K_VECS_PER_WARP] = { 0.0f };
|
||||
|
||||
// dot product of floats
|
||||
const float4 q_vec = q_shared_f[i_head_inner][i_lane];
|
||||
|
||||
#pragma unroll
|
||||
for (int k = 0; k < K_VECS_PER_WARP; ++k) {
|
||||
ggml_cuda_mad(qk[k], q_vec.x, k_reg_f[k].x);
|
||||
ggml_cuda_mad(qk[k], q_vec.y, k_reg_f[k].y);
|
||||
ggml_cuda_mad(qk[k], q_vec.z, k_reg_f[k].z);
|
||||
ggml_cuda_mad(qk[k], q_vec.w, k_reg_f[k].w);
|
||||
}
|
||||
|
||||
#pragma unroll
|
||||
for (int k = 0; k < K_VECS_PER_WARP; ++k) {
|
||||
float sum = warp_reduce_sum(qk[k]);
|
||||
|
||||
// ReLU, weight
|
||||
if (i_lane == 0) {
|
||||
sum = (sum > 0.0f) ? sum : 0.0f;
|
||||
score_k[k] += sum * w_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
// phase 4 - store outputs to shared memory
|
||||
|
||||
__shared__ float dst_shared[K_VECS_PER_BLOCK];
|
||||
|
||||
if (i_lane == 0) {
|
||||
#pragma unroll
|
||||
for (int k = 0; k < K_VECS_PER_WARP; ++k) {
|
||||
dst_shared[i_warp * K_VECS_PER_WARP + k] = score_k[k];
|
||||
}
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// phase 5 - write from shared memory to VRAM in coalesced manner
|
||||
|
||||
if (tid < K_VECS_PER_BLOCK) {
|
||||
int i_kv = start_kv_block + tid;
|
||||
if (i_kv < n_kv) {
|
||||
const half * m_base = (const half *) ((const char *) M + i_batch*nbm1 + (i_stream%nem3)*nbm3);
|
||||
float * dst_base = (float *) ((char *) dst + i_batch*nb1 + i_stream*nb3);
|
||||
dst_base[i_kv] = dst_shared[tid] + __half2float(m_base[i_kv]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define LIGHTNING_INDEXER_CASE(lightning_indexer_kernel, n_embd, n_head, K, type_K) \
|
||||
if (K->type == (type_K)) { \
|
||||
lightning_indexer_kernel<WARPS_PER_BLOCK, K_VECS_PER_BLOCK, n_embd, n_head, type_K> \
|
||||
<<<grid, block, 0, ctx.stream()>>>( \
|
||||
q_d, k_d, w_d, m_d, dst_d, \
|
||||
n_stream, n_batch, n_kv, \
|
||||
nb1, nb2, nb3, \
|
||||
nbq1, nbq2, nbq3, \
|
||||
nbk1, nbk2, nbk3, \
|
||||
nbw1, nbw2, nbw3, \
|
||||
nbm1, nbm2, nbm3, \
|
||||
nem3 \
|
||||
); \
|
||||
} else
|
||||
|
||||
void ggml_cuda_lightning_indexer(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
const ggml_tensor * q = dst->src[0];
|
||||
const ggml_tensor * k = dst->src[1];
|
||||
const ggml_tensor * w = dst->src[2]; // weights
|
||||
const ggml_tensor * m = dst->src[3]; // mask
|
||||
|
||||
GGML_ASSERT(dst->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT( q->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT( w->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT( m->type == GGML_TYPE_F16);
|
||||
|
||||
GGML_TENSOR_LOCALS(int64_t, neq, q, ne)
|
||||
GGML_TENSOR_LOCALS(size_t, nbq, q, nb)
|
||||
GGML_TENSOR_LOCALS(int64_t, nek, k, ne)
|
||||
GGML_TENSOR_LOCALS(size_t, nbk, k, nb)
|
||||
GGML_TENSOR_LOCALS(int64_t, new, w, ne)
|
||||
GGML_TENSOR_LOCALS(size_t, nbw, w, nb)
|
||||
GGML_TENSOR_LOCALS(int64_t, nem, m, ne)
|
||||
GGML_TENSOR_LOCALS(size_t, nbm, m, nb)
|
||||
GGML_TENSOR_LOCALS(int64_t, ne, dst, ne)
|
||||
GGML_TENSOR_LOCALS(size_t, nb, dst, nb)
|
||||
|
||||
// input tensor rows must be contiguous
|
||||
GGML_ASSERT(nbq0 == ggml_type_size(q->type));
|
||||
GGML_ASSERT(nbk0 == ggml_type_size(k->type));
|
||||
GGML_ASSERT(nbw0 == ggml_type_size(w->type));
|
||||
GGML_ASSERT(nbm0 == ggml_type_size(m->type));
|
||||
|
||||
// dst cannot be transposed or permuted
|
||||
GGML_ASSERT(nb0 == sizeof(float));
|
||||
GGML_ASSERT(nb0 <= nb1);
|
||||
GGML_ASSERT(nb1 <= nb2);
|
||||
GGML_ASSERT(nb2 <= nb3);
|
||||
|
||||
const int n_embd = q->ne[0];
|
||||
const int n_head = q->ne[1];
|
||||
const int n_batch = q->ne[2];
|
||||
const int n_stream = q->ne[3];
|
||||
const int n_kv = k->ne[2];
|
||||
|
||||
const float * q_d = (const float *) q->data;
|
||||
const char * k_d = (const char *) k->data;
|
||||
const float * w_d = (const float *) w->data;
|
||||
const half * m_d = (const half *) m->data;
|
||||
float * dst_d = ( float *) dst->data;
|
||||
|
||||
const int device = ggml_cuda_get_device();
|
||||
const int cc = ggml_cuda_info().devices[device].cc;
|
||||
|
||||
if (n_embd == 128 && n_head == 64) {
|
||||
#if !defined(GGML_USE_HIP) && !defined(GGML_USE_MUSA)
|
||||
if (GGML_CUDA_CC_IS_NVIDIA(cc) && turing_mma_available(cc) && k->type != GGML_TYPE_F32 && k->type != GGML_TYPE_BF16) {
|
||||
// use wmma kernel
|
||||
constexpr int K_VECS_PER_BLOCK = 32;
|
||||
constexpr int WARPS_PER_BLOCK = 8;
|
||||
|
||||
dim3 block(32, WARPS_PER_BLOCK);
|
||||
int num_kv_blocks = (n_kv + (K_VECS_PER_BLOCK) - 1) / (K_VECS_PER_BLOCK);
|
||||
dim3 grid(num_kv_blocks, n_batch, n_stream);
|
||||
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_wmma, 128, 64, k, GGML_TYPE_F16)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_wmma, 128, 64, k, GGML_TYPE_Q4_0)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_wmma, 128, 64, k, GGML_TYPE_Q4_1)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_wmma, 128, 64, k, GGML_TYPE_Q5_0)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_wmma, 128, 64, k, GGML_TYPE_Q5_1)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_wmma, 128, 64, k, GGML_TYPE_Q8_0)
|
||||
GGML_ABORT("fatal error");
|
||||
} else {
|
||||
#else // !defined(GGML_USE_HIP) && !defined(GGML_USE_MUSA)
|
||||
{
|
||||
#endif // !defined(GGML_USE_HIP) && !defined(GGML_USE_MUSA)
|
||||
// use vector kernel
|
||||
constexpr int K_VECS_PER_WARP = 8;
|
||||
constexpr int WARPS_PER_BLOCK = 8;
|
||||
constexpr int K_VECS_PER_BLOCK = K_VECS_PER_WARP * WARPS_PER_BLOCK;
|
||||
|
||||
dim3 block(32, WARPS_PER_BLOCK);
|
||||
int num_kv_blocks = (n_kv + (K_VECS_PER_BLOCK) - 1) / (K_VECS_PER_BLOCK);
|
||||
dim3 grid(num_kv_blocks, n_batch, n_stream);
|
||||
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 64, k, GGML_TYPE_F16)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 64, k, GGML_TYPE_Q4_0)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 64, k, GGML_TYPE_Q4_1)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 64, k, GGML_TYPE_Q5_0)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 64, k, GGML_TYPE_Q5_1)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 64, k, GGML_TYPE_Q8_0)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 64, k, GGML_TYPE_BF16)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 64, k, GGML_TYPE_F32)
|
||||
GGML_ABORT("fatal error");
|
||||
}
|
||||
} else if (n_embd == 128 && n_head == 32) {
|
||||
#if !defined(GGML_USE_HIP) && !defined(GGML_USE_MUSA)
|
||||
if (GGML_CUDA_CC_IS_NVIDIA(cc) && turing_mma_available(cc) && k->type != GGML_TYPE_F32 && k->type != GGML_TYPE_BF16) {
|
||||
// use wmma kernel
|
||||
constexpr int K_VECS_PER_BLOCK = 32;
|
||||
constexpr int WARPS_PER_BLOCK = 8;
|
||||
|
||||
dim3 block(32, WARPS_PER_BLOCK);
|
||||
int num_kv_blocks = (n_kv + (K_VECS_PER_BLOCK) - 1) / (K_VECS_PER_BLOCK);
|
||||
dim3 grid(num_kv_blocks, n_batch, n_stream);
|
||||
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_wmma, 128, 32, k, GGML_TYPE_F16)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_wmma, 128, 32, k, GGML_TYPE_Q4_0)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_wmma, 128, 32, k, GGML_TYPE_Q4_1)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_wmma, 128, 32, k, GGML_TYPE_Q5_0)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_wmma, 128, 32, k, GGML_TYPE_Q5_1)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_wmma, 128, 32, k, GGML_TYPE_Q8_0)
|
||||
GGML_ABORT("fatal error");
|
||||
} else {
|
||||
#else // !defined(GGML_USE_HIP) && !defined(GGML_USE_MUSA)
|
||||
{
|
||||
#endif // !defined(GGML_USE_HIP) && !defined(GGML_USE_MUSA)
|
||||
// use vector kernel
|
||||
constexpr int K_VECS_PER_WARP = 8;
|
||||
constexpr int WARPS_PER_BLOCK = 8;
|
||||
constexpr int K_VECS_PER_BLOCK = K_VECS_PER_WARP * WARPS_PER_BLOCK;
|
||||
|
||||
dim3 block(32, WARPS_PER_BLOCK);
|
||||
int num_kv_blocks = (n_kv + (K_VECS_PER_BLOCK) - 1) / (K_VECS_PER_BLOCK);
|
||||
dim3 grid(num_kv_blocks, n_batch, n_stream);
|
||||
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 32, k, GGML_TYPE_F16)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 32, k, GGML_TYPE_Q4_0)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 32, k, GGML_TYPE_Q4_1)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 32, k, GGML_TYPE_Q5_0)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 32, k, GGML_TYPE_Q5_1)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 32, k, GGML_TYPE_Q8_0)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 32, k, GGML_TYPE_BF16)
|
||||
LIGHTNING_INDEXER_CASE(lightning_indexer_kernel_vec, 128, 32, k, GGML_TYPE_F32)
|
||||
GGML_ABORT("fatal error");
|
||||
}
|
||||
} else {
|
||||
GGML_ABORT("fatal error");
|
||||
}
|
||||
}
|
||||
|
||||
bool ggml_cuda_lightning_indexer_supported(int device, const ggml_tensor * dst) {
|
||||
GGML_UNUSED(device);
|
||||
|
||||
const ggml_tensor * q = dst->src[0];
|
||||
const ggml_tensor * k = dst->src[1];
|
||||
const ggml_tensor * w = dst->src[2]; // weights
|
||||
const ggml_tensor * m = dst->src[3]; // mask
|
||||
|
||||
GGML_TENSOR_LOCALS(int64_t, neq, q, ne)
|
||||
GGML_TENSOR_LOCALS(size_t, nbq, q, nb)
|
||||
GGML_TENSOR_LOCALS(int64_t, nek, k, ne)
|
||||
GGML_TENSOR_LOCALS(size_t, nbk, k, nb)
|
||||
GGML_TENSOR_LOCALS(int64_t, new, w, ne)
|
||||
GGML_TENSOR_LOCALS(size_t, nbw, w, nb)
|
||||
GGML_TENSOR_LOCALS(int64_t, nem, m, ne)
|
||||
GGML_TENSOR_LOCALS(size_t, nbm, m, nb)
|
||||
GGML_TENSOR_LOCALS(int64_t, ne, dst, ne)
|
||||
GGML_TENSOR_LOCALS(size_t, nb, dst, nb)
|
||||
|
||||
if (neq0 != 128) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (neq1 != 64 && neq1 != 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// alignment checks
|
||||
for (const ggml_tensor * t : {q, k}) {
|
||||
if (ggml_is_quantized(t->type)) {
|
||||
continue;
|
||||
}
|
||||
for (size_t i = 1; i < GGML_MAX_DIMS; ++i) {
|
||||
if (t->nb[i] % 16 != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch(k->type) {
|
||||
case GGML_TYPE_F32:
|
||||
case GGML_TYPE_BF16:
|
||||
case GGML_TYPE_F16:
|
||||
case GGML_TYPE_Q8_0:
|
||||
case GGML_TYPE_Q5_1:
|
||||
case GGML_TYPE_Q5_0:
|
||||
case GGML_TYPE_Q4_1:
|
||||
case GGML_TYPE_Q4_0:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#include "common.cuh"
|
||||
|
||||
void ggml_cuda_lightning_indexer(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
bool ggml_cuda_lightning_indexer_supported(int device, const ggml_tensor * dst);
|
||||
@@ -85,7 +85,7 @@ void ggml_cuda_mul_mat_f(ggml_backend_cuda_context & ctx, const ggml_tensor * sr
|
||||
GGML_ASSERT(sis1 > 0);
|
||||
|
||||
ggml_cuda_launch_mm_ids_helper(ids_d, ids_src_compact_dev.get(), ids_dst_compact_dev.get(), expert_bounds_dev.get(),
|
||||
static_cast<int>(n_experts), static_cast<int>(n_tokens), static_cast<int>(n_expert_used), static_cast<int>(ne11), si1, sis1, ctx.stream());
|
||||
static_cast<int>(n_experts), static_cast<int>(n_tokens), static_cast<int>(n_expert_used), static_cast<int>(ne11), si1, sis1, /*write_inverse =*/ false, ctx.stream());
|
||||
CUDA_CHECK(cudaGetLastError());
|
||||
|
||||
ids_info.ids_src_compact = ids_src_compact_dev.get();
|
||||
|
||||
+18
-13
@@ -27,7 +27,7 @@ template <int n_expert_used_template>
|
||||
__launch_bounds__(ggml_cuda_get_physical_warp_size(), 1)
|
||||
static __global__ void mm_ids_helper(
|
||||
const int32_t * __restrict__ ids, int32_t * __restrict__ ids_src1, int32_t * __restrict__ ids_dst, int32_t * __restrict__ expert_bounds,
|
||||
const int n_tokens, const int n_expert_used_var, const int nchannels_y, const int si1, const int sis1) {
|
||||
const int n_tokens, const int n_expert_used_var, const int nchannels_y, const int si1, const int sis1, const bool write_inverse) {
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
const int n_expert_used = n_expert_used_template == 0 ? n_expert_used_var : n_expert_used_template;
|
||||
const int expert = blockIdx.x;
|
||||
@@ -98,8 +98,13 @@ static __global__ void mm_ids_helper(
|
||||
const mm_ids_helper_store store_it = store[itc];
|
||||
const int it = store_it.it();
|
||||
const int iex_used = store_it.iex_used();
|
||||
ids_src1[nex_prev + itc] = it*sis1 + iex_used % nchannels_y;
|
||||
ids_dst [nex_prev + itc] = it*n_expert_used + iex_used;
|
||||
ids_dst[nex_prev + itc] = it*n_expert_used + iex_used;
|
||||
// ids_src1 holds the forward map, or the inverse map (token slot -> compact row) for quant dedup
|
||||
if (write_inverse) {
|
||||
ids_src1[it*n_expert_used + iex_used] = nex_prev + itc;
|
||||
} else {
|
||||
ids_src1[nex_prev + itc] = it*sis1 + iex_used % nchannels_y;
|
||||
}
|
||||
}
|
||||
|
||||
if (threadIdx.x != 0) {
|
||||
@@ -118,7 +123,7 @@ static __global__ void mm_ids_helper(
|
||||
template <int n_expert_used_template>
|
||||
static void launch_mm_ids_helper(
|
||||
const int32_t * __restrict__ ids, int32_t * __restrict__ ids_src1, int32_t * __restrict__ ids_dst, int32_t * __restrict__ expert_bounds,
|
||||
const int n_experts, const int n_tokens, const int n_expert_used_var, const int nchannels_y, const int si1, const int sis1, cudaStream_t stream) {
|
||||
const int n_experts, const int n_tokens, const int n_expert_used_var, const int nchannels_y, const int si1, const int sis1, const bool write_inverse, cudaStream_t stream) {
|
||||
GGML_ASSERT(n_tokens < (1 << 22) && "too few bits in mm_ids_helper_store");
|
||||
GGML_ASSERT(n_expert_used_var < (1 << 10) && "too few bits in mm_ids_helper_store");
|
||||
|
||||
@@ -132,33 +137,33 @@ static void launch_mm_ids_helper(
|
||||
const size_t nbytes_shared = n_tokens*sizeof(mm_ids_helper_store);
|
||||
GGML_ASSERT(nbytes_shared <= smpbo);
|
||||
mm_ids_helper<n_expert_used_template><<<num_blocks, block_size, nbytes_shared, stream>>>
|
||||
(ids, ids_src1, ids_dst, expert_bounds, n_tokens, n_expert_used_var, nchannels_y, si1, sis1);
|
||||
(ids, ids_src1, ids_dst, expert_bounds, n_tokens, n_expert_used_var, nchannels_y, si1, sis1, write_inverse);
|
||||
}
|
||||
|
||||
void ggml_cuda_launch_mm_ids_helper(
|
||||
const int32_t * __restrict__ ids, int32_t * __restrict__ ids_src1, int32_t * __restrict__ ids_dst, int32_t * __restrict__ expert_bounds,
|
||||
const int n_experts, const int n_tokens, const int n_expert_used, const int nchannels_y, const int si1, const int sis1, cudaStream_t stream) {
|
||||
const int n_experts, const int n_tokens, const int n_expert_used, const int nchannels_y, const int si1, const int sis1, const bool write_inverse, cudaStream_t stream) {
|
||||
switch (n_expert_used) {
|
||||
case 2:
|
||||
launch_mm_ids_helper< 2>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
launch_mm_ids_helper< 2>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, write_inverse, stream);
|
||||
break;
|
||||
case 4:
|
||||
launch_mm_ids_helper< 4>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
launch_mm_ids_helper< 4>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, write_inverse, stream);
|
||||
break;
|
||||
case 6:
|
||||
launch_mm_ids_helper< 6>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
launch_mm_ids_helper< 6>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, write_inverse, stream);
|
||||
break;
|
||||
case 8:
|
||||
launch_mm_ids_helper< 8>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
launch_mm_ids_helper< 8>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, write_inverse, stream);
|
||||
break;
|
||||
case 16:
|
||||
launch_mm_ids_helper<16>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
launch_mm_ids_helper<16>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, write_inverse, stream);
|
||||
break;
|
||||
case 32:
|
||||
launch_mm_ids_helper<32>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
launch_mm_ids_helper<32>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, write_inverse, stream);
|
||||
break;
|
||||
default:
|
||||
launch_mm_ids_helper< 0>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
launch_mm_ids_helper< 0>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, write_inverse, stream);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
void ggml_cuda_launch_mm_ids_helper(
|
||||
const int32_t * ids, int32_t * ids_src1, int32_t * ids_dst, int32_t * expert_bounds,
|
||||
int n_experts, int n_tokens, int n_expert_used, int nchannels_y, int si1, int sis1, cudaStream_t stream);
|
||||
int n_experts, int n_tokens, int n_expert_used, int nchannels_y, int si1, int sis1, bool write_inverse, cudaStream_t stream);
|
||||
|
||||
@@ -39,29 +39,37 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
|
||||
const block_q1_0 * bxi = (const block_q1_0 *) x + kbx0 + i*stride + kbx;
|
||||
const int qs_offset = 4*kqsx;
|
||||
const int qs0 = bxi->qs[qs_offset + 0] | (bxi->qs[qs_offset + 1] << 8) |
|
||||
(bxi->qs[qs_offset + 2] << 16) | (bxi->qs[qs_offset + 3] << 24);
|
||||
|
||||
int unpacked_bytes[8];
|
||||
#pragma unroll
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
const int shift = j * 4;
|
||||
const int bits4 = (qs0 >> shift) & 0x0F;
|
||||
const int b0 = (bits4 & 0x01) ? 1 : -1;
|
||||
const int b1 = (bits4 & 0x02) ? 1 : -1;
|
||||
const int b2 = (bits4 & 0x04) ? 1 : -1;
|
||||
const int b3 = (bits4 & 0x08) ? 1 : -1;
|
||||
unpacked_bytes[j] = (b0 & 0xFF) | ((b1 & 0xFF) << 8) | ((b2 & 0xFF) << 16) | ((b3 & 0xFF) << 24);
|
||||
}
|
||||
const int16_t * qxi = (const int16_t *) bxi->qs + kqsx * 2;
|
||||
|
||||
const int dst_offset = kbx*(scale_entries_per_block*QI8_0) + kqsx*QI8_0;
|
||||
#pragma unroll
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
const int q = qxi[j];
|
||||
|
||||
// unpack crumbs into nibble indices
|
||||
const int n0 = __byte_perm(0x11100100, 0x11100100, q >> 0); // [0, 1, 4, 5] [ 8, 9, 12, 13]
|
||||
const int n1 = __byte_perm(0x11100100, 0x11100100, q >> 2); // [2, 3, 6, 7] [10, 11, 14, 15]
|
||||
// unpack nibbles into byte values
|
||||
const int s0 = __byte_perm(0x01FF, 0x01FF, n0 >> 0);
|
||||
const int s1 = __byte_perm(0x01FF, 0x01FF, n1 >> 0);
|
||||
const int s2 = __byte_perm(0x01FF, 0x01FF, n0 >> 16);
|
||||
const int s3 = __byte_perm(0x01FF, 0x01FF, n1 >> 16);
|
||||
// unshuffle values
|
||||
const int v0 = __byte_perm(s0, s1, 0x5410);
|
||||
const int v1 = __byte_perm(s0, s1, 0x7632);
|
||||
const int v2 = __byte_perm(s2, s3, 0x5410);
|
||||
const int v3 = __byte_perm(s2, s3, 0x7632);
|
||||
|
||||
#if defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
x_qs[i*sram_stride + dst_offset + j] = unpacked_bytes[j];
|
||||
x_qs[i*sram_stride + dst_offset + j*4+0] = v0;
|
||||
x_qs[i*sram_stride + dst_offset + j*4+1] = v1;
|
||||
x_qs[i*sram_stride + dst_offset + j*4+2] = v2;
|
||||
x_qs[i*sram_stride + dst_offset + j*4+3] = v3;
|
||||
#else
|
||||
x_qs[i*(2*MMQ_TILE_NE_K + 1) + dst_offset + j] = unpacked_bytes[j];
|
||||
x_qs[i*(2*MMQ_TILE_NE_K + 1) + dst_offset + j*4+0] = v0;
|
||||
x_qs[i*(2*MMQ_TILE_NE_K + 1) + dst_offset + j*4+1] = v1;
|
||||
x_qs[i*(2*MMQ_TILE_NE_K + 1) + dst_offset + j*4+2] = v2;
|
||||
x_qs[i*(2*MMQ_TILE_NE_K + 1) + dst_offset + j*4+3] = v3;
|
||||
#endif // defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,11 +122,12 @@ void ggml_cuda_mul_mat_q(
|
||||
|
||||
const bool fallback = ne01 % 128 != 0;
|
||||
|
||||
// TODO: tighter pool buffer size vs q8 path
|
||||
const bool use_native_fp4 = blackwell_mma_available(cc) && (src0->type == GGML_TYPE_MXFP4 || src0->type == GGML_TYPE_NVFP4);
|
||||
const size_t y_block_size = use_native_fp4 ? sizeof(block_fp4_mmq) : sizeof(block_q8_1_mmq);
|
||||
const size_t y_values_per_block = use_native_fp4 ? QK_FP4_MMQ : QK8_1_MMQ;
|
||||
|
||||
if (!ids) {
|
||||
const size_t nbytes_src1_q8_1 = ne13*ne12 * ne11*ne10_padded * sizeof(block_q8_1)/QK8_1 +
|
||||
const size_t nbytes_src1_q8_1 = ne13*ne12 * ne11*ne10_padded * y_block_size/y_values_per_block +
|
||||
ggml_cuda_mmq_get_J_max(src0->type, fallback, cc, ne11) * sizeof(block_q8_1_mmq);
|
||||
ggml_cuda_pool_alloc<char> src1_q8_1(ctx.pool(), nbytes_src1_q8_1);
|
||||
|
||||
@@ -148,7 +149,7 @@ void ggml_cuda_mul_mat_q(
|
||||
|
||||
// Stride depends on quantization format
|
||||
const int64_t s12 = use_native_fp4 ?
|
||||
ne11 * ne10_padded * sizeof(block_fp4_mmq) / (QK_K * sizeof(int)) : // block_fp4_mmq holds 256 values
|
||||
ne11 * ne10_padded * sizeof(block_fp4_mmq) / (QK_FP4_MMQ * sizeof(int)) :
|
||||
ne11 * ne10_padded * sizeof(block_q8_1) / (QK8_1 * sizeof(int));
|
||||
const int64_t s13 = ne12*s12;
|
||||
|
||||
@@ -174,17 +175,21 @@ void ggml_cuda_mul_mat_q(
|
||||
ggml_cuda_pool_alloc<int32_t> ids_dst(ctx.pool(), ne_get_rows);
|
||||
ggml_cuda_pool_alloc<int32_t> expert_bounds(ctx.pool(), ne02 + 1);
|
||||
|
||||
// gate/up activations are broadcast across experts (ne11 == 1): quantize each token once and
|
||||
// scatter to its slots. ids_src1 then holds the inverse map (token slot -> compact row).
|
||||
const bool dedup_bcast = ne11 == 1 && n_expert_used > 1;
|
||||
|
||||
{
|
||||
GGML_ASSERT(ids->nb[0] == ggml_element_size(ids));
|
||||
const int si1 = ids->nb[1] / ggml_element_size(ids);
|
||||
const int sis1 = nb12 / nb11;
|
||||
|
||||
ggml_cuda_launch_mm_ids_helper((const int32_t *) ids->data, ids_src1.get(), ids_dst.get(), expert_bounds.get(),
|
||||
ne02, ne12, n_expert_used, ne11, si1, sis1, stream);
|
||||
ne02, ne12, n_expert_used, ne11, si1, sis1, /*write_inverse =*/ dedup_bcast, stream);
|
||||
CUDA_CHECK(cudaGetLastError());
|
||||
}
|
||||
|
||||
const size_t nbytes_src1_q8_1 = ne12*n_expert_used*ne10_padded * sizeof(block_q8_1)/QK8_1 +
|
||||
const size_t nbytes_src1_q8_1 = ne12*n_expert_used*ne10_padded * y_block_size/y_values_per_block +
|
||||
ggml_cuda_mmq_get_J_max(src0->type, fallback, cc, ne11) * sizeof(block_q8_1_mmq);
|
||||
ggml_cuda_pool_alloc<char> src1_q8_1(ctx.pool(), nbytes_src1_q8_1);
|
||||
|
||||
@@ -197,7 +202,16 @@ void ggml_cuda_mul_mat_q(
|
||||
const int64_t s12 = src1->nb[2] / ts_src1;
|
||||
const int64_t s13 = src1->nb[3] / ts_src1;
|
||||
|
||||
if (use_native_fp4) {
|
||||
if (dedup_bcast) {
|
||||
// quantize each token once, scatter its block to all n_expert_used slots
|
||||
if (use_native_fp4) {
|
||||
quantize_scatter_mmq_fp4_cuda(src1_d, ids_src1.get(), src1_q8_1.get(), src0->type, ne10,
|
||||
/*stride_token=*/s12, ne10_padded, ne12, ne11_flat, n_expert_used, stream);
|
||||
} else {
|
||||
quantize_scatter_mmq_q8_1_cuda(src1_d, ids_src1.get(), src1_q8_1.get(), src0->type, ne10,
|
||||
/*stride_token=*/s12, ne10_padded, ne12, ne11_flat, n_expert_used, stream);
|
||||
}
|
||||
} else if (use_native_fp4) {
|
||||
quantize_mmq_fp4_cuda(src1_d, ids_src1.get(), src1_q8_1.get(), src0->type, ne10, s11, s12, s13,
|
||||
ne10_padded, ne11_flat, ne12_flat, ne13_flat, stream);
|
||||
} else {
|
||||
@@ -207,8 +221,8 @@ void ggml_cuda_mul_mat_q(
|
||||
CUDA_CHECK(cudaGetLastError());
|
||||
}
|
||||
|
||||
static_assert(QK_K == 8 * QK_MXFP4, "QK_K needs to be 8 * QK_MXFP4");
|
||||
const int64_t s12 = use_native_fp4 ? ne11 * ne10_padded * sizeof(block_fp4_mmq) / (QK_K * sizeof(int)) :
|
||||
static_assert(QK_FP4_MMQ == 8 * QK_MXFP4, "QK_FP4_MMQ needs to be 8 * QK_MXFP4");
|
||||
const int64_t s12 = use_native_fp4 ? ne11 * ne10_padded * sizeof(block_fp4_mmq) / (QK_FP4_MMQ * sizeof(int)) :
|
||||
ne11 * ne10_padded * sizeof(block_q8_1) / (QK8_1 * sizeof(int));
|
||||
const int64_t s13 = ne12*s12;
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ enum mmq_q8_1_ds_layout {
|
||||
MMQ_Q8_1_DS_LAYOUT_D2S6,
|
||||
};
|
||||
|
||||
static constexpr int QK8_1_MMQ = 4*QK8_1;
|
||||
static constexpr int QK_FP4_MMQ = 2*QK8_1_MMQ;
|
||||
|
||||
struct block_q8_1_mmq {
|
||||
// The y float data is converted to a data layout that can simply be copied to shared memory as a contiguous block.
|
||||
// The y float data is first grouped as blocks of 128 values.
|
||||
@@ -39,7 +42,7 @@ struct block_q8_1_mmq {
|
||||
half d2s6[8]; // 1 16 bit scale per 64 values + 1 16 bit partial sum per 16 values for the first 96 values,
|
||||
// stored as d0,d1,s1,s2,s3,s4,s5
|
||||
};
|
||||
int8_t qs[4*QK8_1]; // 128 values quantized to 8 bit each
|
||||
int8_t qs[QK8_1_MMQ];
|
||||
};
|
||||
|
||||
// this struct is used for fp4 data types (currently only used for Blackwell)
|
||||
@@ -47,10 +50,10 @@ struct block_q8_1_mmq {
|
||||
// nvfp4 has block size 16, each int32 of d4 contains 4 ue4m3 scales
|
||||
struct block_fp4_mmq {
|
||||
uint32_t d4[4];
|
||||
int8_t qs[4 * 32]; // 256 FP4 values packed as 4-bit pairs (2 per byte)
|
||||
int8_t qs[QK_FP4_MMQ / 2];
|
||||
};
|
||||
|
||||
static_assert(sizeof(block_q8_1_mmq) == 4*QK8_1 + 4*sizeof(half2), "Unexpected block_q8_1_mmq size");
|
||||
static_assert(sizeof(block_q8_1_mmq) == QK8_1_MMQ + 4*sizeof(half2), "Unexpected block_q8_1_mmq size");
|
||||
static_assert(sizeof(block_q8_1_mmq) == 4*sizeof(block_q8_1), "Unexpected block_q8_1_mmq size");
|
||||
static_assert(sizeof(block_fp4_mmq) == sizeof(block_q8_1_mmq), "Unexpected block_fp4_mmq size");
|
||||
|
||||
@@ -833,9 +836,9 @@ static __device__ __forceinline__ void mul_mat_q_process_tile(
|
||||
|
||||
#if defined(BLACKWELL_MMA_AVAILABLE)
|
||||
// FP4 tile stores 8 blocks
|
||||
constexpr int ne_block = (type == GGML_TYPE_MXFP4 || type == GGML_TYPE_NVFP4) ? QK_K : 4 * QK8_1;
|
||||
constexpr int ne_block = (type == GGML_TYPE_MXFP4 || type == GGML_TYPE_NVFP4) ? QK_FP4_MMQ : QK8_1_MMQ;
|
||||
#else
|
||||
constexpr int ne_block = 4 * QK8_1;
|
||||
constexpr int ne_block = QK8_1_MMQ;
|
||||
#endif // defined(BLACKWELL_MMA_AVAILABLE)
|
||||
|
||||
constexpr int ITER_K = ggml_cuda_mmq_get_K_vram(type, J, fallback);
|
||||
|
||||
+199
-103
@@ -75,10 +75,12 @@ __device__ __forceinline__ uint8_t compute_e8m0_scale(float amax) {
|
||||
}
|
||||
|
||||
|
||||
// scatter: grid over tokens, quantize once, write to all the token's compact rows
|
||||
template <bool scatter>
|
||||
static __global__ void quantize_mmq_nvfp4(
|
||||
const float * __restrict__ x, const int32_t * __restrict__ ids, void * __restrict__ vy,
|
||||
const int64_t ne00, const int64_t s01, const int64_t s02, const int64_t s03,
|
||||
const int64_t ne0, const int64_t ne1, const int64_t ne2) {
|
||||
const int64_t ne0, const int64_t ne1, const int64_t ne2, const int n_expert_used) {
|
||||
#if defined(BLACKWELL_MMA_AVAILABLE)
|
||||
|
||||
const int64_t i0_base = ((int64_t) blockDim.x * blockIdx.y + threadIdx.x) * QK_NVFP4_SUB;
|
||||
@@ -86,25 +88,25 @@ static __global__ void quantize_mmq_nvfp4(
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t i1 = blockIdx.x;
|
||||
const int64_t i2 = blockIdx.z % ne2;
|
||||
const int64_t i3 = blockIdx.z / ne2;
|
||||
const int64_t i01 = ids ? ids[i1] : i1;
|
||||
const int64_t k_block = i0_base / QK_K;
|
||||
const int64_t blocks_per_col = (ne0 + QK_K - 1) / QK_K;
|
||||
const int64_t k_block = i0_base / QK_FP4_MMQ;
|
||||
const int64_t blocks_per_col = (ne0 + QK_FP4_MMQ - 1) / QK_FP4_MMQ;
|
||||
if (k_block >= blocks_per_col) {
|
||||
return;
|
||||
}
|
||||
const int sub = (i0_base % QK_FP4_MMQ) / QK_NVFP4_SUB;
|
||||
|
||||
const int64_t ib = blockIdx.z * ((int64_t) blocks_per_col * ne1) + k_block * ne1 + blockIdx.x;
|
||||
block_fp4_mmq * y = (block_fp4_mmq *) vy;
|
||||
block_fp4_mmq * yb = y + ib;
|
||||
|
||||
const int sub = (i0_base % QK_K) / QK_NVFP4_SUB;
|
||||
int64_t base_idx;
|
||||
if constexpr (scatter) {
|
||||
base_idx = (int64_t) blockIdx.x * s02; // one physical row per token
|
||||
} else {
|
||||
const int64_t i2 = blockIdx.z % ne2;
|
||||
const int64_t i3 = blockIdx.z / ne2;
|
||||
const int64_t i01 = ids ? ids[blockIdx.x] : blockIdx.x;
|
||||
base_idx = i3 * s03 + i2 * s02 + i01 * s01;
|
||||
}
|
||||
|
||||
float vals_raw[QK_NVFP4_SUB];
|
||||
float amax_raw = 0.0f;
|
||||
const int64_t base_idx = i3 * s03 + i2 * s02 + i01 * s01;
|
||||
#pragma unroll
|
||||
for (int k = 0; k < QK_NVFP4_SUB; k++) {
|
||||
const int64_t i00 = i0_base + k;
|
||||
@@ -160,11 +162,27 @@ static __global__ void quantize_mmq_nvfp4(
|
||||
q1 |= (uint32_t) ggml_cuda_float_to_fp4_e2m1(vals_raw[k + 12], inv_scale) << (8 * k + 4);
|
||||
}
|
||||
|
||||
uint32_t * yqs = reinterpret_cast<uint32_t *>(yb->qs);
|
||||
yqs[2 * sub + 0] = q0;
|
||||
yqs[2 * sub + 1] = q1;
|
||||
reinterpret_cast<uint8_t *>(yb->d4)[sub] = fp8_code;
|
||||
block_fp4_mmq * y = (block_fp4_mmq *) vy;
|
||||
if constexpr (scatter) {
|
||||
#pragma unroll
|
||||
for (int slot = 0; slot < n_expert_used; ++slot) {
|
||||
const int64_t i = ids[(int64_t) blockIdx.x * n_expert_used + slot];
|
||||
block_fp4_mmq * yb = y + (k_block * ne1 + i);
|
||||
uint32_t * yqs = reinterpret_cast<uint32_t *>(yb->qs);
|
||||
yqs[2 * sub + 0] = q0;
|
||||
yqs[2 * sub + 1] = q1;
|
||||
reinterpret_cast<uint8_t *>(yb->d4)[sub] = fp8_code;
|
||||
}
|
||||
} else {
|
||||
block_fp4_mmq * yb = y + (blockIdx.z * ((int64_t) blocks_per_col * ne1) + k_block * ne1 + blockIdx.x);
|
||||
uint32_t * yqs = reinterpret_cast<uint32_t *>(yb->qs);
|
||||
yqs[2 * sub + 0] = q0;
|
||||
yqs[2 * sub + 1] = q1;
|
||||
reinterpret_cast<uint8_t *>(yb->d4)[sub] = fp8_code;
|
||||
}
|
||||
GGML_UNUSED(n_expert_used);
|
||||
#else
|
||||
GGML_UNUSED(n_expert_used);
|
||||
NO_DEVICE_CODE; // This is for Blackwell NVFP4 activations only.
|
||||
#endif // defined(BLACKWELL_MMA_AVAILABLE)
|
||||
|
||||
@@ -172,6 +190,8 @@ static __global__ void quantize_mmq_nvfp4(
|
||||
|
||||
// quantize values in the format mxfp4 is stored which is interleaved nibbles
|
||||
// i.e. a block a0-a31 is represented as a0a16,a1a17 ...a15a31
|
||||
// scatter: grid over tokens, quantize once, write to all the token's compact rows
|
||||
template <bool scatter>
|
||||
static __global__ void quantize_mmq_mxfp4(const float * __restrict__ x,
|
||||
const int32_t * __restrict__ ids,
|
||||
void * __restrict__ vy,
|
||||
@@ -181,7 +201,8 @@ static __global__ void quantize_mmq_mxfp4(const float * __restrict__ x,
|
||||
const int64_t s03,
|
||||
const int64_t ne0,
|
||||
const int ne1,
|
||||
const int ne2) {
|
||||
const int ne2,
|
||||
const int n_expert_used) {
|
||||
constexpr int vals_per_scale = 32;
|
||||
constexpr int vals_per_warp = 2 * vals_per_scale; // Each warp processes 2 blocks of 32 = 64 values
|
||||
|
||||
@@ -196,30 +217,27 @@ static __global__ void quantize_mmq_mxfp4(const float * __restrict__ x,
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t i1 = blockIdx.x;
|
||||
const int64_t i2 = blockIdx.z % ne2;
|
||||
const int64_t i3 = blockIdx.z / ne2;
|
||||
|
||||
ggml_cuda_pdl_sync();
|
||||
const int64_t i01 = ids ? ids[i1] : i1;
|
||||
const int64_t i02 = i2;
|
||||
const int64_t i03 = i3;
|
||||
|
||||
block_fp4_mmq * y = (block_fp4_mmq *) vy;
|
||||
|
||||
const int64_t block_fp4_mmq_size = 8 * QK_MXFP4; // 256 values
|
||||
const int64_t ib0 = blockIdx.z * ((int64_t) ne1 * (ne0 / block_fp4_mmq_size));
|
||||
const int64_t ib = ib0 + (warp_start_offset / block_fp4_mmq_size) * ne1 + blockIdx.x;
|
||||
const int64_t block_fp4_mmq_size = QK_FP4_MMQ;
|
||||
const int64_t k_block = warp_start_offset / block_fp4_mmq_size;
|
||||
const int64_t quad_idx_in_block = (warp_start_offset % block_fp4_mmq_size) / vals_per_warp;
|
||||
|
||||
const int group_id = lane_id_32 / 4;
|
||||
const int lane_in_group = lane_id_32 % 4;
|
||||
const int base = group_id * 2;
|
||||
char2 * yqs2 = (char2 *) y[ib].qs;
|
||||
|
||||
const int64_t base_pos = i03 * s03 + i02 * s02 + i01 * s01;
|
||||
ggml_cuda_pdl_sync();
|
||||
int64_t base_pos;
|
||||
if constexpr (scatter) {
|
||||
base_pos = (int64_t) blockIdx.x * s02; // one physical row per token
|
||||
} else {
|
||||
const int64_t i2 = blockIdx.z % ne2;
|
||||
const int64_t i3 = blockIdx.z / ne2;
|
||||
const int64_t i01 = ids ? ids[blockIdx.x] : blockIdx.x;
|
||||
base_pos = i3 * s03 + i2 * s02 + i01 * s01;
|
||||
}
|
||||
|
||||
uint8_t scales[2];
|
||||
char2 packed[2];
|
||||
|
||||
#pragma unroll
|
||||
for (int b = 0; b < 2; ++b) {
|
||||
@@ -244,11 +262,8 @@ static __global__ void quantize_mmq_mxfp4(const float * __restrict__ x,
|
||||
const float val2 = __shfl_sync(0xFFFFFFFF, scaled_val, base + 1, WARP_SIZE);
|
||||
const float val3 = __shfl_sync(0xFFFFFFFF, scaled_val, base + 17, WARP_SIZE);
|
||||
|
||||
if (lane_in_group == 0) {
|
||||
__nv_fp4x4_e2m1 fp4_packed(make_float4(val0, val1, val2, val3));
|
||||
|
||||
yqs2[quad_idx_in_block * 16 + b * 8 + group_id] = *(char2 *) &fp4_packed;
|
||||
}
|
||||
__nv_fp4x4_e2m1 fp4_packed(make_float4(val0, val1, val2, val3));
|
||||
packed[b] = *(char2 *) &fp4_packed;
|
||||
#else
|
||||
// Fallback: manual FP4 conversion using LUT
|
||||
const uint8_t q_val = ggml_cuda_float_to_fp4_e2m1(xi, inv_s);
|
||||
@@ -258,26 +273,49 @@ static __global__ void quantize_mmq_mxfp4(const float * __restrict__ x,
|
||||
const uint8_t q_hi_0 = __shfl_sync(0xFFFFFFFF, q_val, base + 16, WARP_SIZE);
|
||||
const uint8_t q_hi_1 = __shfl_sync(0xFFFFFFFF, q_val, base + 17, WARP_SIZE);
|
||||
|
||||
if (lane_in_group == 0) {
|
||||
char2 q;
|
||||
q.x = (q_hi_0 << 4) | q_lo_0;
|
||||
q.y = (q_hi_1 << 4) | q_lo_1;
|
||||
yqs2[quad_idx_in_block * 16 + b * 8 + group_id] = q;
|
||||
}
|
||||
char2 q;
|
||||
q.x = (q_hi_0 << 4) | q_lo_0;
|
||||
q.y = (q_hi_1 << 4) | q_lo_1;
|
||||
packed[b] = q;
|
||||
#endif // CUDART_VERSION >= 12080
|
||||
}
|
||||
|
||||
if (lane_id_32 == 0) {
|
||||
// Store 2 scales packed into 1 uint32
|
||||
y[ib].d4[quad_idx_in_block] = (scales[1] << 8) | scales[0];
|
||||
block_fp4_mmq * y = (block_fp4_mmq *) vy;
|
||||
if constexpr (scatter) {
|
||||
#pragma unroll
|
||||
for (int slot = 0; slot < n_expert_used; ++slot) {
|
||||
const int64_t i = ids[(int64_t) blockIdx.x * n_expert_used + slot];
|
||||
block_fp4_mmq * yb = y + (k_block * ne1 + i);
|
||||
char2 * yqs2 = (char2 *) yb->qs;
|
||||
if (lane_in_group == 0) {
|
||||
yqs2[quad_idx_in_block * 16 + 0 * 8 + group_id] = packed[0];
|
||||
yqs2[quad_idx_in_block * 16 + 1 * 8 + group_id] = packed[1];
|
||||
}
|
||||
if (lane_id_32 == 0) {
|
||||
yb->d4[quad_idx_in_block] = (scales[1] << 8) | scales[0];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const int64_t ib0 = blockIdx.z * ((int64_t) ne1 * (ne0 / block_fp4_mmq_size));
|
||||
block_fp4_mmq * yb = y + (ib0 + k_block * ne1 + blockIdx.x);
|
||||
char2 * yqs2 = (char2 *) yb->qs;
|
||||
if (lane_in_group == 0) {
|
||||
yqs2[quad_idx_in_block * 16 + 0 * 8 + group_id] = packed[0];
|
||||
yqs2[quad_idx_in_block * 16 + 1 * 8 + group_id] = packed[1];
|
||||
}
|
||||
if (lane_id_32 == 0) {
|
||||
yb->d4[quad_idx_in_block] = (scales[1] << 8) | scales[0];
|
||||
}
|
||||
}
|
||||
GGML_UNUSED(n_expert_used);
|
||||
}
|
||||
|
||||
template <mmq_q8_1_ds_layout ds_layout>
|
||||
// scatter: grid over tokens, quantize once, write to all the token's compact rows
|
||||
template <mmq_q8_1_ds_layout ds_layout, bool scatter>
|
||||
static __global__ void quantize_mmq_q8_1(
|
||||
const float * __restrict__ x, const int32_t * __restrict__ ids, void * __restrict__ vy,
|
||||
const int64_t ne00, const int64_t s01, const int64_t s02, const int64_t s03,
|
||||
const int64_t ne0, const int ne1, const int ne2) {
|
||||
const int64_t ne0, const int ne1, const int ne2, const int n_expert_used) {
|
||||
|
||||
constexpr int vals_per_scale = ds_layout == MMQ_Q8_1_DS_LAYOUT_D2S6 ? 64 : 32;
|
||||
constexpr int vals_per_sum = ds_layout == MMQ_Q8_1_DS_LAYOUT_D2S6 ? 16 : 32;
|
||||
@@ -288,26 +326,27 @@ static __global__ void quantize_mmq_q8_1(
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t i1 = blockIdx.x;
|
||||
const int64_t i2 = blockIdx.z % ne2;
|
||||
const int64_t i3 = blockIdx.z / ne2;
|
||||
|
||||
const int64_t i00 = i0;
|
||||
ggml_cuda_pdl_sync();
|
||||
const int64_t i01 = ids ? ids[i1] : i1;
|
||||
const int64_t i02 = i2;
|
||||
const int64_t i03 = i3;
|
||||
|
||||
int64_t base_idx;
|
||||
if constexpr (scatter) {
|
||||
base_idx = (int64_t) blockIdx.x * s02; // one physical row per token
|
||||
} else {
|
||||
const int64_t i2 = blockIdx.z % ne2;
|
||||
const int64_t i3 = blockIdx.z / ne2;
|
||||
const int64_t i01 = ids ? ids[blockIdx.x] : blockIdx.x;
|
||||
base_idx = i3*s03 + i2*s02 + i01*s01;
|
||||
}
|
||||
|
||||
const float4 * x4 = (const float4 *) x;
|
||||
|
||||
block_q8_1_mmq * y = (block_q8_1_mmq *) vy;
|
||||
|
||||
const int64_t ib0 = blockIdx.z*((int64_t)gridDim.x*gridDim.y*blockDim.x/QK8_1); // first block of channel
|
||||
const int64_t ib = ib0 + (i0 / (4*QK8_1))*ne1 + blockIdx.x; // block index in channel
|
||||
const int64_t iqs = i0 % (4*QK8_1); // quant index in block
|
||||
const int64_t k_block = i0 / QK8_1_MMQ; // column block in the channel
|
||||
const int64_t iqs = i0 % QK8_1_MMQ; // quant index in block
|
||||
|
||||
// Load 4 floats per thread and calculate max. abs. value between them:
|
||||
const float4 xi = i0 < ne00 ? x4[(i03*s03 + i02*s02 + i01*s01 + i00)/4] : make_float4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
const float4 xi = i0 < ne00 ? x4[(base_idx + i00)/4] : make_float4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
float amax = fabsf(xi.x);
|
||||
amax = fmaxf(amax, fabsf(xi.y));
|
||||
amax = fmaxf(amax, fabsf(xi.z));
|
||||
@@ -336,40 +375,41 @@ static __global__ void quantize_mmq_q8_1(
|
||||
q.y = roundf(xi.y*d_inv);
|
||||
q.z = roundf(xi.z*d_inv);
|
||||
q.w = roundf(xi.w*d_inv);
|
||||
|
||||
// Write back 4 int8 values as a single 32 bit value for better memory bandwidth:
|
||||
char4 * yqs4 = (char4 *) y[ib].qs;
|
||||
yqs4[iqs/4] = q;
|
||||
|
||||
if (ds_layout == MMQ_Q8_1_DS_LAYOUT_D2S6) {
|
||||
if (iqs % 16 != 0 || iqs >= 96) {
|
||||
return;
|
||||
}
|
||||
|
||||
y[ib].d2s6[2 + iqs/16] = sum;
|
||||
|
||||
if (iqs % 64 != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float d = 1.0f / d_inv;
|
||||
|
||||
y[ib].d2s6[iqs/64] = d;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (iqs % 32 != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float d = 1.0f / d_inv;
|
||||
|
||||
if (ds_layout == MMQ_Q8_1_DS_LAYOUT_DS4) {
|
||||
y[ib].ds4[iqs/32] = make_half2(d, sum);
|
||||
} else {
|
||||
y[ib].d4[iqs/32] = d;
|
||||
// write the block once (normal) or to each of the token's compact rows (scatter)
|
||||
const int nwrite = scatter ? n_expert_used : 1;
|
||||
#pragma unroll
|
||||
for (int slot = 0; slot < nwrite; ++slot) {
|
||||
int64_t ib;
|
||||
if constexpr (scatter) {
|
||||
const int64_t i = ids[(int64_t) blockIdx.x * n_expert_used + slot];
|
||||
ib = k_block*ne1 + i;
|
||||
} else {
|
||||
const int64_t ib0 = blockIdx.z*((int64_t)gridDim.x*gridDim.y*blockDim.x/QK8_1); // first block of channel
|
||||
ib = ib0 + k_block*ne1 + blockIdx.x;
|
||||
}
|
||||
|
||||
// Write back 4 int8 values as a single 32 bit value for better memory bandwidth:
|
||||
char4 * yqs4 = (char4 *) y[ib].qs;
|
||||
yqs4[iqs/4] = q;
|
||||
|
||||
if (ds_layout == MMQ_Q8_1_DS_LAYOUT_D2S6) {
|
||||
if (iqs % 16 == 0 && iqs < 96) {
|
||||
y[ib].d2s6[2 + iqs/16] = sum;
|
||||
if (iqs % 64 == 0) {
|
||||
y[ib].d2s6[iqs/64] = d;
|
||||
}
|
||||
}
|
||||
} else if (iqs % 32 == 0) {
|
||||
if (ds_layout == MMQ_Q8_1_DS_LAYOUT_DS4) {
|
||||
y[ib].ds4[iqs/32] = make_half2(d, sum);
|
||||
} else {
|
||||
y[ib].d4[iqs/32] = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
GGML_UNUSED(n_expert_used);
|
||||
}
|
||||
|
||||
void quantize_row_q8_1_cuda(
|
||||
@@ -394,7 +434,7 @@ void quantize_mmq_q8_1_cuda(
|
||||
const int64_t ne00, const int64_t s01, const int64_t s02, const int64_t s03,
|
||||
const int64_t ne0, const int64_t ne1, const int64_t ne2, const int64_t ne3, cudaStream_t stream) {
|
||||
GGML_ASSERT(ne00 % 4 == 0);
|
||||
GGML_ASSERT(ne0 % (4*QK8_1) == 0);
|
||||
GGML_ASSERT(ne0 % QK8_1_MMQ == 0);
|
||||
|
||||
// ne1 tends to assume the highest values, therefore use it as the "x" dimension of the CUDA grid:
|
||||
const int64_t block_num_y = (ne0 + 4*CUDA_QUANTIZE_BLOCK_SIZE_MMQ - 1) / (4*CUDA_QUANTIZE_BLOCK_SIZE_MMQ);
|
||||
@@ -402,16 +442,16 @@ void quantize_mmq_q8_1_cuda(
|
||||
const dim3 block_size(CUDA_QUANTIZE_BLOCK_SIZE_MMQ, 1, 1);
|
||||
switch (mmq_get_q8_1_ds_layout(type_src0)) {
|
||||
case MMQ_Q8_1_DS_LAYOUT_D4:
|
||||
quantize_mmq_q8_1<MMQ_Q8_1_DS_LAYOUT_D4>
|
||||
<<<num_blocks, block_size, 0, stream>>>(x, ids, vy, ne00, s01, s02, s03, ne0, ne1, ne2);
|
||||
quantize_mmq_q8_1<MMQ_Q8_1_DS_LAYOUT_D4, false>
|
||||
<<<num_blocks, block_size, 0, stream>>>(x, ids, vy, ne00, s01, s02, s03, ne0, ne1, ne2, /*n_expert_used=*/0);
|
||||
break;
|
||||
case MMQ_Q8_1_DS_LAYOUT_DS4:
|
||||
quantize_mmq_q8_1<MMQ_Q8_1_DS_LAYOUT_DS4>
|
||||
<<<num_blocks, block_size, 0, stream>>>(x, ids, vy, ne00, s01, s02, s03, ne0, ne1, ne2);
|
||||
quantize_mmq_q8_1<MMQ_Q8_1_DS_LAYOUT_DS4, false>
|
||||
<<<num_blocks, block_size, 0, stream>>>(x, ids, vy, ne00, s01, s02, s03, ne0, ne1, ne2, /*n_expert_used=*/0);
|
||||
break;
|
||||
case MMQ_Q8_1_DS_LAYOUT_D2S6:
|
||||
quantize_mmq_q8_1<MMQ_Q8_1_DS_LAYOUT_D2S6>
|
||||
<<<num_blocks, block_size, 0, stream>>>(x, ids, vy, ne00, s01, s02, s03, ne0, ne1, ne2);
|
||||
quantize_mmq_q8_1<MMQ_Q8_1_DS_LAYOUT_D2S6, false>
|
||||
<<<num_blocks, block_size, 0, stream>>>(x, ids, vy, ne00, s01, s02, s03, ne0, ne1, ne2, /*n_expert_used=*/0);
|
||||
break;
|
||||
default:
|
||||
GGML_ABORT("fatal error");
|
||||
@@ -419,6 +459,62 @@ void quantize_mmq_q8_1_cuda(
|
||||
}
|
||||
}
|
||||
|
||||
// scatter=true reuses the quant kernel: grid over tokens, ids = inverse map (token slot -> compact row)
|
||||
void quantize_scatter_mmq_q8_1_cuda(
|
||||
const float * x, const int32_t * ids_src1_inv, void * vy, const ggml_type type_src0,
|
||||
const int64_t ne00, const int64_t stride_token, const int64_t ne0,
|
||||
const int64_t n_tokens, const int64_t nrows_dst, const int n_expert_used, cudaStream_t stream) {
|
||||
GGML_ASSERT(ne00 % 4 == 0);
|
||||
GGML_ASSERT(ne0 % QK8_1_MMQ == 0);
|
||||
|
||||
const int64_t block_num_y = (ne0 + 4*CUDA_QUANTIZE_BLOCK_SIZE_MMQ - 1) / (4*CUDA_QUANTIZE_BLOCK_SIZE_MMQ);
|
||||
const dim3 num_blocks(n_tokens, block_num_y, 1);
|
||||
const dim3 block_size(CUDA_QUANTIZE_BLOCK_SIZE_MMQ, 1, 1);
|
||||
switch (mmq_get_q8_1_ds_layout(type_src0)) {
|
||||
case MMQ_Q8_1_DS_LAYOUT_D4:
|
||||
quantize_mmq_q8_1<MMQ_Q8_1_DS_LAYOUT_D4, true><<<num_blocks, block_size, 0, stream>>>(
|
||||
x, ids_src1_inv, vy, ne00, /*s01=*/0, /*s02=*/stride_token, /*s03=*/0, ne0, /*ne1=*/(int) nrows_dst, /*ne2=*/1, n_expert_used);
|
||||
break;
|
||||
case MMQ_Q8_1_DS_LAYOUT_DS4:
|
||||
quantize_mmq_q8_1<MMQ_Q8_1_DS_LAYOUT_DS4, true><<<num_blocks, block_size, 0, stream>>>(
|
||||
x, ids_src1_inv, vy, ne00, /*s01=*/0, /*s02=*/stride_token, /*s03=*/0, ne0, /*ne1=*/(int) nrows_dst, /*ne2=*/1, n_expert_used);
|
||||
break;
|
||||
case MMQ_Q8_1_DS_LAYOUT_D2S6:
|
||||
quantize_mmq_q8_1<MMQ_Q8_1_DS_LAYOUT_D2S6, true><<<num_blocks, block_size, 0, stream>>>(
|
||||
x, ids_src1_inv, vy, ne00, /*s01=*/0, /*s02=*/stride_token, /*s03=*/0, ne0, /*ne1=*/(int) nrows_dst, /*ne2=*/1, n_expert_used);
|
||||
break;
|
||||
default:
|
||||
GGML_ABORT("fatal error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// scatter=true reuses the quant kernels: grid over tokens, ids = inverse map (token slot -> compact row)
|
||||
void quantize_scatter_mmq_fp4_cuda(
|
||||
const float * x, const int32_t * ids_src1_inv, void * vy, const ggml_type type_src0,
|
||||
const int64_t ne00, const int64_t stride_token, const int64_t ne0,
|
||||
const int64_t n_tokens, const int64_t nrows_dst, const int n_expert_used, cudaStream_t stream) {
|
||||
GGML_ASSERT(ne0 > 0);
|
||||
if (type_src0 == GGML_TYPE_NVFP4) {
|
||||
GGML_ASSERT(ne00 % QK_NVFP4 == 0);
|
||||
constexpr int nvfp4_block_size = 128;
|
||||
const int64_t block_num_y = (ne0 + QK_NVFP4_SUB * nvfp4_block_size - 1) / (QK_NVFP4_SUB * nvfp4_block_size);
|
||||
const dim3 block_size(nvfp4_block_size, 1, 1);
|
||||
const dim3 num_blocks(n_tokens, block_num_y, 1);
|
||||
quantize_mmq_nvfp4<true><<<num_blocks, block_size, 0, stream>>>(
|
||||
x, ids_src1_inv, vy, ne00, /*s01=*/0, /*s02=*/stride_token, /*s03=*/0, ne0, /*ne1=*/nrows_dst, /*ne2=*/1, n_expert_used);
|
||||
} else {
|
||||
GGML_ASSERT(type_src0 == GGML_TYPE_MXFP4);
|
||||
constexpr int nwarps = 8;
|
||||
constexpr int vals_per_block = nwarps * 2 * QK_MXFP4;
|
||||
const int64_t block_num_y = (ne0 + vals_per_block - 1) / vals_per_block;
|
||||
const dim3 block_size(WARP_SIZE, nwarps, 1);
|
||||
const dim3 num_blocks(n_tokens, block_num_y, 1);
|
||||
quantize_mmq_mxfp4<true><<<num_blocks, block_size, 0, stream>>>(
|
||||
x, ids_src1_inv, vy, ne00, /*s01=*/0, /*s02=*/stride_token, /*s03=*/0, ne0, /*ne1=*/(int) nrows_dst, /*ne2=*/1, n_expert_used);
|
||||
}
|
||||
}
|
||||
|
||||
void quantize_mmq_fp4_cuda(
|
||||
const float * x, const int32_t * ids, void * vy, const ggml_type type_src0,
|
||||
const int64_t ne00, const int64_t s01, const int64_t s02, const int64_t s03,
|
||||
@@ -432,8 +528,8 @@ void quantize_mmq_fp4_cuda(
|
||||
const int64_t block_num_y = (ne0 + QK_NVFP4_SUB * nvfp4_block_size - 1) / (QK_NVFP4_SUB * nvfp4_block_size);
|
||||
const dim3 block_size(nvfp4_block_size, 1, 1);
|
||||
const dim3 num_blocks(ne1, block_num_y, ne2 * ne3);
|
||||
quantize_mmq_nvfp4<<<num_blocks, block_size, 0, stream>>>(
|
||||
x, ids, vy, ne00, s01, s02, s03, ne0, ne1, ne2);
|
||||
quantize_mmq_nvfp4<false><<<num_blocks, block_size, 0, stream>>>(
|
||||
x, ids, vy, ne00, s01, s02, s03, ne0, ne1, ne2, /*n_expert_used=*/0);
|
||||
} else {
|
||||
GGML_ASSERT(ne0 % (2 * QK_MXFP4) == 0);
|
||||
|
||||
@@ -445,6 +541,6 @@ void quantize_mmq_fp4_cuda(
|
||||
const dim3 num_blocks(ne1, block_num_y, ne2 * ne3);
|
||||
const dim3 block_size(WARP_SIZE, nwarps, 1);
|
||||
|
||||
quantize_mmq_mxfp4<<<num_blocks, block_size, 0, stream>>>(x, ids, vy, ne00, s01, s02, s03, ne0, ne1, ne2);
|
||||
quantize_mmq_mxfp4<false><<<num_blocks, block_size, 0, stream>>>(x, ids, vy, ne00, s01, s02, s03, ne0, ne1, ne2, /*n_expert_used=*/0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,3 +39,28 @@ void quantize_mmq_fp4_cuda(const float * x,
|
||||
int64_t ne2,
|
||||
int64_t ne3,
|
||||
cudaStream_t stream);
|
||||
|
||||
// quantize each token once and scatter the block to its compact rows (via the inverse map)
|
||||
void quantize_scatter_mmq_fp4_cuda(const float * x,
|
||||
const int32_t * ids_src1_inv,
|
||||
void * vy,
|
||||
ggml_type type_src0,
|
||||
int64_t ne00,
|
||||
int64_t stride_token,
|
||||
int64_t ne0,
|
||||
int64_t n_tokens,
|
||||
int64_t nrows_dst,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
void quantize_scatter_mmq_q8_1_cuda(const float * x,
|
||||
const int32_t * ids_src1_inv,
|
||||
void * vy,
|
||||
ggml_type type_src0,
|
||||
int64_t ne00,
|
||||
int64_t stride_token,
|
||||
int64_t ne0,
|
||||
int64_t n_tokens,
|
||||
int64_t nrows_dst,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
@@ -681,35 +681,40 @@ static __device__ __forceinline__ float vec_dot_q1_0_q8_1(
|
||||
// Q8_1: 32 elements per block with individual scales
|
||||
// iqs selects which of the 4 chunks of 32 elements to process (0-3)
|
||||
|
||||
const float d1 = bq1_0->d;
|
||||
const float d1 = bq1_0->d;
|
||||
const int16_t * qs = (const int16_t *) bq1_0->qs + iqs * 2;
|
||||
|
||||
// Process only the chunk specified by iqs
|
||||
const block_q8_1 * bq8_1_chunk = bq8_1 + iqs;
|
||||
|
||||
// Load 32 bits (4 bytes) for this chunk from Q1_0
|
||||
const int offset = iqs * 4;
|
||||
const int v = bq1_0->qs[offset + 0] | (bq1_0->qs[offset + 1] << 8) |
|
||||
(bq1_0->qs[offset + 2] << 16) | (bq1_0->qs[offset + 3] << 24);
|
||||
|
||||
// Unpack 32 bits into 32 signed values (-1 or +1)
|
||||
int vi_bytes[8];
|
||||
#pragma unroll
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
const int shift = j * 4;
|
||||
const int bits4 = (v >> shift) & 0x0F;
|
||||
const int b0 = (bits4 & 0x01) ? 1 : -1;
|
||||
const int b1 = (bits4 & 0x02) ? 1 : -1;
|
||||
const int b2 = (bits4 & 0x04) ? 1 : -1;
|
||||
const int b3 = (bits4 & 0x08) ? 1 : -1;
|
||||
vi_bytes[j] = (b0 & 0xFF) | ((b1 & 0xFF) << 8) | ((b2 & 0xFF) << 16) | ((b3 & 0xFF) << 24);
|
||||
}
|
||||
|
||||
// Compute dot product for this 32-element chunk
|
||||
int sumi = 0;
|
||||
#pragma unroll
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
const int u = get_int_b4(bq8_1_chunk->qs, j);
|
||||
sumi = ggml_cuda_dp4a(vi_bytes[j], u, sumi);
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
const int q = qs[j];
|
||||
|
||||
const int u0 = get_int_b4(bq8_1_chunk->qs, j*4+0);
|
||||
const int u1 = get_int_b4(bq8_1_chunk->qs, j*4+1);
|
||||
const int u2 = get_int_b4(bq8_1_chunk->qs, j*4+2);
|
||||
const int u3 = get_int_b4(bq8_1_chunk->qs, j*4+3);
|
||||
|
||||
// unpack crumbs into nibble indices
|
||||
const int n0 = __byte_perm(0x11100100, 0x11100100, q >> 0); // [0, 1, 4, 5] [ 8, 9, 12, 13]
|
||||
const int n1 = __byte_perm(0x11100100, 0x11100100, q >> 2); // [2, 3, 6, 7] [10, 11, 14, 15]
|
||||
// unpack nibbles into byte values
|
||||
const int s0 = __byte_perm(0x01FF, 0x01FF, n0 >> 0);
|
||||
const int s1 = __byte_perm(0x01FF, 0x01FF, n1 >> 0);
|
||||
const int s2 = __byte_perm(0x01FF, 0x01FF, n0 >> 16);
|
||||
const int s3 = __byte_perm(0x01FF, 0x01FF, n1 >> 16);
|
||||
// unshuffle values
|
||||
const int v0 = __byte_perm(s0, s1, 0x5410);
|
||||
const int v1 = __byte_perm(s0, s1, 0x7632);
|
||||
const int v2 = __byte_perm(s2, s3, 0x5410);
|
||||
const int v3 = __byte_perm(s2, s3, 0x7632);
|
||||
|
||||
sumi = ggml_cuda_dp4a(v0, u0, sumi);
|
||||
sumi = ggml_cuda_dp4a(v1, u1, sumi);
|
||||
sumi = ggml_cuda_dp4a(v2, u2, sumi);
|
||||
sumi = ggml_cuda_dp4a(v3, u3, sumi);
|
||||
}
|
||||
|
||||
// Apply Q1_0's single scale and this chunk's Q8_1 scale
|
||||
|
||||
@@ -38,7 +38,7 @@ static inline void hmx_queue_process(struct hmx_queue *q, bool* killed) {
|
||||
if (!d->done) {
|
||||
FARF(HIGH, "hmx-queue-process: ir %u func %p data %p", ir, d->func, d->data);
|
||||
|
||||
enum hmx_queue_signal sig = (enum hmx_queue_signal) (unsigned int) d->func;
|
||||
uintptr_t sig = (uintptr_t) d->func;
|
||||
switch (sig) {
|
||||
case HMX_QUEUE_NOOP: /* noop */; break;
|
||||
case HMX_QUEUE_KILL: *killed = true; break;
|
||||
|
||||
@@ -1834,6 +1834,23 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_col2im_1d(ggml_m
|
||||
return res;
|
||||
}
|
||||
|
||||
ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_snake(ggml_metal_library_t lib, enum ggml_type type) {
|
||||
GGML_ASSERT(type == GGML_TYPE_F32 || type == GGML_TYPE_F16 || type == GGML_TYPE_BF16);
|
||||
|
||||
char base[256];
|
||||
char name[256];
|
||||
|
||||
snprintf(base, 256, "kernel_snake_%s", ggml_type_name(type));
|
||||
snprintf(name, 256, "%s", base);
|
||||
|
||||
ggml_metal_pipeline_with_params res = ggml_metal_library_get_pipeline(lib, name);
|
||||
if (!res.pipeline) {
|
||||
res = ggml_metal_library_compile_pipeline(lib, base, name, nullptr);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_conv_transpose_2d(ggml_metal_library_t lib, const ggml_tensor * op) {
|
||||
assert(op->op == GGML_OP_CONV_TRANSPOSE_2D);
|
||||
|
||||
|
||||
@@ -151,6 +151,7 @@ struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_im2col
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_conv_transpose_1d (ggml_metal_library_t lib, const struct ggml_tensor * op);
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_conv_transpose_2d (ggml_metal_library_t lib, const struct ggml_tensor * op);
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_col2im_1d (ggml_metal_library_t lib, const struct ggml_tensor * op);
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_snake (ggml_metal_library_t lib, enum ggml_type type);
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_conv_2d (ggml_metal_library_t lib, const struct ggml_tensor * op);
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_conv_2d_dw (ggml_metal_library_t lib, const struct ggml_tensor * op, bool tiled);
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_conv_3d (ggml_metal_library_t lib, const struct ggml_tensor * op);
|
||||
|
||||
@@ -1340,7 +1340,11 @@ bool ggml_metal_device_supports_op(ggml_metal_device_t dev, const struct ggml_te
|
||||
return op->src[0]->type != GGML_TYPE_NVFP4;
|
||||
case GGML_OP_SET_ROWS:
|
||||
{
|
||||
if (op->src[0]->type != GGML_TYPE_F32 && op->src[0]->type != GGML_TYPE_F16) {
|
||||
if (op->src[0]->type == GGML_TYPE_F16) {
|
||||
return op->type == GGML_TYPE_F16;
|
||||
}
|
||||
|
||||
if (op->src[0]->type != GGML_TYPE_F32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -616,6 +616,11 @@ typedef struct {
|
||||
int32_t p0;
|
||||
} ggml_metal_kargs_col2im_1d;
|
||||
|
||||
typedef struct {
|
||||
int32_t T;
|
||||
int32_t C;
|
||||
} ggml_metal_kargs_snake;
|
||||
|
||||
typedef struct {
|
||||
int32_t IC;
|
||||
int32_t IH;
|
||||
|
||||
@@ -3077,7 +3077,58 @@ int ggml_metal_op_flash_attn_ext(ggml_metal_op_t ctx, int idx) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Snake activation autofuse: mul -> sin -> sqr -> mul -> add
|
||||
static bool ggml_metal_op_can_fuse_snake(ggml_metal_op_t ctx, int idx) {
|
||||
static constexpr ggml_op snake_ops[5] = { GGML_OP_MUL, GGML_OP_SIN, GGML_OP_SQR, GGML_OP_MUL, GGML_OP_ADD };
|
||||
|
||||
if (ctx->node(idx)->op != GGML_OP_MUL || !ctx->can_fuse(idx, snake_ops, 5)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ggml_tensor * mul0 = ctx->node(idx + 0);
|
||||
const ggml_tensor * sin_node = ctx->node(idx + 1);
|
||||
const ggml_tensor * sqr = ctx->node(idx + 2);
|
||||
const ggml_tensor * mul1 = ctx->node(idx + 3);
|
||||
const ggml_tensor * add = ctx->node(idx + 4);
|
||||
|
||||
// x carries the full activation shape, a is the broadcast operand
|
||||
const ggml_tensor * x = ggml_are_same_shape(mul0, mul0->src[0]) ? mul0->src[0] : mul0->src[1];
|
||||
const ggml_tensor * a = (x == mul0->src[0]) ? mul0->src[1] : mul0->src[0];
|
||||
|
||||
// mul1 reads sqr and inv_b in either operand order
|
||||
const ggml_tensor * inv_b = (mul1->src[0] == sqr) ? mul1->src[1] : mul1->src[0];
|
||||
|
||||
// closure check: the trailing add reads the same x as the leading mul
|
||||
const ggml_tensor * x_in_add = (add->src[0] == mul1) ? add->src[1] : add->src[0];
|
||||
|
||||
// x is in the supported whitelist and every chain intermediate shares x's type.
|
||||
// a and inv_b bind as device const float * in the kernel, so they stay F32.
|
||||
const bool types_ok =
|
||||
(x->type == GGML_TYPE_F32 || x->type == GGML_TYPE_F16 || x->type == GGML_TYPE_BF16) &&
|
||||
(a->type == GGML_TYPE_F32) && (inv_b->type == GGML_TYPE_F32) &&
|
||||
(mul0->type == x->type) && (sin_node->type == x->type) &&
|
||||
(sqr->type == x->type) && (mul1->type == x->type) &&
|
||||
(add->type == x->type);
|
||||
// a / inv_b collapse to [1, C, 1, 1], x and add stay 2D
|
||||
const bool shape_ok = ggml_are_same_shape(a, inv_b) && a->ne[0] == 1 && a->ne[1] == x->ne[1];
|
||||
const bool dim_ok =
|
||||
(x->ne[2] == 1) && (x->ne[3] == 1) &&
|
||||
(add->ne[2] == 1) && (add->ne[3] == 1) &&
|
||||
(a->ne[2] == 1) && (a->ne[3] == 1) &&
|
||||
(inv_b->ne[2] == 1) && (inv_b->ne[3] == 1);
|
||||
// kernel reads x[idx] and a[c] / inv_b[c] linearly, so every operand is contiguous
|
||||
const bool contig_ok =
|
||||
ggml_is_contiguous(x) && ggml_is_contiguous(add) &&
|
||||
ggml_is_contiguous(a) && ggml_is_contiguous(inv_b);
|
||||
|
||||
return types_ok && shape_ok && dim_ok && contig_ok && x_in_add == x;
|
||||
}
|
||||
|
||||
int ggml_metal_op_bin(ggml_metal_op_t ctx, int idx) {
|
||||
if (ctx->use_fusion && ggml_metal_op_can_fuse_snake(ctx, idx)) {
|
||||
return ggml_metal_op_snake_fused(ctx, idx);
|
||||
}
|
||||
|
||||
ggml_tensor * op = ctx->node(idx);
|
||||
|
||||
ggml_metal_library_t lib = ctx->lib;
|
||||
@@ -3984,6 +4035,55 @@ int ggml_metal_op_col2im_1d(ggml_metal_op_t ctx, int idx) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Dispatch the fused snake kernel from the matched mul -> sin -> sqr -> mul -> add chain.
|
||||
// idx points at the leading mul. The caller has validated the chain.
|
||||
int ggml_metal_op_snake_fused(ggml_metal_op_t ctx, int idx) {
|
||||
ggml_metal_library_t lib = ctx->lib;
|
||||
ggml_metal_encoder_t enc = ctx->enc;
|
||||
|
||||
const ggml_tensor * mul0 = ctx->node(idx + 0);
|
||||
const ggml_tensor * sqr = ctx->node(idx + 2);
|
||||
const ggml_tensor * mul1 = ctx->node(idx + 3);
|
||||
ggml_tensor * add = ctx->node(idx + 4);
|
||||
|
||||
const ggml_tensor * x = ggml_are_same_shape(mul0, mul0->src[0]) ? mul0->src[0] : mul0->src[1];
|
||||
const ggml_tensor * a = (x == mul0->src[0]) ? mul0->src[1] : mul0->src[0];
|
||||
const ggml_tensor * inv_b = (mul1->src[0] == sqr) ? mul1->src[1] : mul1->src[0];
|
||||
|
||||
const int T = (int) x->ne[0];
|
||||
const int C = (int) x->ne[1];
|
||||
const int total = T * C;
|
||||
|
||||
// the encode loop pre-checked the leading mul only, check the rest of the chain
|
||||
for (int i = 1; i < 5; ++i) {
|
||||
if (!ggml_metal_op_concurrency_check(ctx, ctx->node(idx + i))) {
|
||||
ggml_metal_op_concurrency_reset(ctx);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto pipeline = ggml_metal_library_get_pipeline_snake(lib, x->type);
|
||||
|
||||
ggml_metal_kargs_snake args = {
|
||||
/*.T =*/ T,
|
||||
/*.C =*/ C,
|
||||
};
|
||||
|
||||
ggml_metal_encoder_set_pipeline(enc, pipeline);
|
||||
ggml_metal_encoder_set_bytes (enc, &args, sizeof(args), 0);
|
||||
ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(x), 1);
|
||||
ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(a), 2);
|
||||
ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(inv_b), 3);
|
||||
ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(add), 4);
|
||||
|
||||
const int nth = 256;
|
||||
const int ntg = (total + nth - 1) / nth;
|
||||
ggml_metal_encoder_dispatch_threadgroups(enc, ntg, 1, 1, nth, 1, 1);
|
||||
|
||||
return 5;
|
||||
}
|
||||
|
||||
int ggml_metal_op_conv_transpose_2d(ggml_metal_op_t ctx, int idx) {
|
||||
ggml_tensor * op = ctx->node(idx);
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ int ggml_metal_op_conv_3d (ggml_metal_op_t ctx, int idx);
|
||||
int ggml_metal_op_conv_transpose_1d (ggml_metal_op_t ctx, int idx);
|
||||
int ggml_metal_op_conv_transpose_2d (ggml_metal_op_t ctx, int idx);
|
||||
int ggml_metal_op_col2im_1d (ggml_metal_op_t ctx, int idx);
|
||||
int ggml_metal_op_snake_fused (ggml_metal_op_t ctx, int idx);
|
||||
int ggml_metal_op_upscale (ggml_metal_op_t ctx, int idx);
|
||||
int ggml_metal_op_pad (ggml_metal_op_t ctx, int idx);
|
||||
int ggml_metal_op_pad_reflect_1d (ggml_metal_op_t ctx, int idx);
|
||||
|
||||
@@ -5406,6 +5406,35 @@ template [[host_name("kernel_col2im_1d_bf16")]] kernel void kernel_col2im_1d<bfl
|
||||
#endif
|
||||
|
||||
|
||||
template <typename T>
|
||||
kernel void kernel_snake(
|
||||
constant ggml_metal_kargs_snake & args,
|
||||
device const T * x,
|
||||
device const float * a,
|
||||
device const float * inv_b,
|
||||
device T * dst,
|
||||
uint tgpig [[threadgroup_position_in_grid]],
|
||||
uint tpitg [[thread_position_in_threadgroup]],
|
||||
uint ntg [[threads_per_threadgroup]]) {
|
||||
|
||||
const int idx = tgpig * ntg + tpitg;
|
||||
if (idx >= args.T * args.C) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int c = idx / args.T; // x is [T, C], a / inv_b collapse to [1, C]
|
||||
const float xi = float(x[idx]);
|
||||
const float si = sin(a[c] * xi);
|
||||
dst[idx] = T(xi + si * si * inv_b[c]);
|
||||
}
|
||||
|
||||
template [[host_name("kernel_snake_f32")]] kernel void kernel_snake<float>(constant ggml_metal_kargs_snake &, device const float *, device const float *, device const float *, device float *, uint, uint, uint);
|
||||
template [[host_name("kernel_snake_f16")]] kernel void kernel_snake<half>(constant ggml_metal_kargs_snake &, device const half *, device const float *, device const float *, device half *, uint, uint, uint);
|
||||
#if defined(GGML_METAL_HAS_BF16)
|
||||
template [[host_name("kernel_snake_bf16")]] kernel void kernel_snake<bfloat>(constant ggml_metal_kargs_snake &, device const bfloat *, device const float *, device const float *, device bfloat *, uint, uint, uint);
|
||||
#endif
|
||||
|
||||
|
||||
typedef void (conv_transpose_2d_t)(
|
||||
constant ggml_metal_kargs_conv_transpose_2d & args,
|
||||
device const float * src0,
|
||||
|
||||
@@ -114,6 +114,7 @@ enum GPU_FAMILY {
|
||||
|
||||
enum ADRENO_GPU_GEN {
|
||||
ADRENO_UNKNOWN,
|
||||
A6X,
|
||||
A7X,
|
||||
A8X,
|
||||
X1E,
|
||||
@@ -122,6 +123,7 @@ enum ADRENO_GPU_GEN {
|
||||
|
||||
enum ADRENO_CL_COMPILER_TYPE {
|
||||
E031,
|
||||
E17,
|
||||
DX,
|
||||
};
|
||||
|
||||
@@ -243,6 +245,19 @@ static ggml_cl_version get_opencl_c_version(ggml_cl_version platform_version, cl
|
||||
}
|
||||
|
||||
static ADRENO_GPU_GEN get_adreno_gpu_gen(const char *device_name) {
|
||||
if (strstr(device_name, "610") || strstr(device_name, "612") ||
|
||||
strstr(device_name, "613") || strstr(device_name, "615") ||
|
||||
strstr(device_name, "616") || strstr(device_name, "618") ||
|
||||
strstr(device_name, "619") || strstr(device_name, "620") ||
|
||||
strstr(device_name, "630") || strstr(device_name, "640") ||
|
||||
strstr(device_name, "642") || strstr(device_name, "643") ||
|
||||
strstr(device_name, "644") || strstr(device_name, "650") ||
|
||||
strstr(device_name, "660") || strstr(device_name, "663") ||
|
||||
strstr(device_name, "680") || strstr(device_name, "685") ||
|
||||
strstr(device_name, "690")) {
|
||||
return ADRENO_GPU_GEN::A6X;
|
||||
}
|
||||
|
||||
if (strstr(device_name, "730") ||
|
||||
strstr(device_name, "740") ||
|
||||
strstr(device_name, "750")) {
|
||||
@@ -250,7 +265,8 @@ static ADRENO_GPU_GEN get_adreno_gpu_gen(const char *device_name) {
|
||||
}
|
||||
|
||||
if (strstr(device_name, "830") ||
|
||||
strstr(device_name, "840")) {
|
||||
strstr(device_name, "840") ||
|
||||
strstr(device_name, "850")) {
|
||||
return ADRENO_GPU_GEN::A8X;
|
||||
}
|
||||
|
||||
@@ -274,6 +290,17 @@ static ggml_cl_compiler_version get_adreno_cl_compiler_version(const char *drive
|
||||
size_t compiler_minor_offset = 8;
|
||||
size_t compiler_patch_offset = 11;
|
||||
|
||||
if (compiler_ver_pos == std::string::npos) {
|
||||
compiler_ver_pos = driver_ver_str.find("E17");
|
||||
if (compiler_ver_pos != std::string::npos) {
|
||||
type = ADRENO_CL_COMPILER_TYPE::E17;
|
||||
compiler_ver_len = 12;
|
||||
compiler_major_offset = 4;
|
||||
compiler_minor_offset = 7;
|
||||
compiler_patch_offset = 10;
|
||||
}
|
||||
}
|
||||
|
||||
if (compiler_ver_pos == std::string::npos) {
|
||||
compiler_ver_pos = driver_ver_str.find("DX");
|
||||
if (compiler_ver_pos == std::string::npos) {
|
||||
@@ -282,6 +309,8 @@ static ggml_cl_compiler_version get_adreno_cl_compiler_version(const char *drive
|
||||
type = ADRENO_CL_COMPILER_TYPE::DX;
|
||||
compiler_ver_len = 11;
|
||||
compiler_major_offset = 3;
|
||||
compiler_minor_offset = 6;
|
||||
compiler_patch_offset = 9;
|
||||
}
|
||||
|
||||
std::string compiler_ver_str = driver_ver_str.substr(compiler_ver_pos, compiler_ver_len);
|
||||
@@ -532,6 +561,7 @@ struct ggml_backend_opencl_context {
|
||||
bool fp16_support;
|
||||
bool has_vector_subgroup_broadcast;
|
||||
bool has_subgroup_shuffle = false; // cl_khr_subgroup_shuffle or cl_qcom_subgroup_shuffle
|
||||
bool has_integer_dot = false; // cl_khr_integer_dot_product or cl_qcom_dot_product8
|
||||
bool has_qcom_subgroup_shuffle = false; // specifically cl_qcom_subgroup_shuffle
|
||||
bool disable_fusion;
|
||||
|
||||
@@ -834,7 +864,7 @@ struct ggml_backend_opencl_context {
|
||||
cl_kernel kernel_gemv_moe_q5_1_f32_ns, kernel_gemm_moe_q5_1_f32_ns;
|
||||
cl_kernel kernel_gemv_moe_q4_k_f32_ns, kernel_gemm_moe_q4_k_f32_ns, kernel_gemm_moe_q4_k_f32_ns_bin;
|
||||
cl_kernel kernel_gemv_moe_q4_k_f32_ns_wimg = nullptr; // weight-as-texture MoE decode GEMV (opt-in)
|
||||
cl_kernel kernel_gemm_moe_q4_k_q8_1_dp4a; // dp4a (int8) prefill GEMM variant
|
||||
cl_kernel kernel_gemm_moe_q4_k_q8_1_dp4a = nullptr; // dp4a (int8) prefill GEMM variant
|
||||
cl_kernel kernel_moe_reorder_quant_a_q8_1; // fused reorder + q8_1 quant for the dp4a GEMM
|
||||
cl_kernel kernel_gemm_moe_q8_1_dp4a_q80 = nullptr; // generic dp4a MoE GEMM (MOE_QT=80), opt-in
|
||||
cl_kernel kernel_moe_expand_scale_q8_0 = nullptr; // q8_0 per-block d -> uniform scale[16]
|
||||
@@ -844,12 +874,12 @@ struct ggml_backend_opencl_context {
|
||||
cl_kernel kernel_moe_expand_scale_q5_K = nullptr; // q5_K 6-bit s[] -> uniform scale[16]/min[8]
|
||||
cl_kernel kernel_gemv_moe_q5_k_f32_ns, kernel_gemm_moe_q5_k_f32_ns;
|
||||
cl_kernel kernel_gemv_moe_q6_k_f32_ns, kernel_gemm_moe_q6_k_f32_ns;
|
||||
cl_kernel kernel_gemm_moe_q6_k_q8_1_dp4a; // dp4a (int8) q6_K MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_q6_k_q8_1_dp4a = nullptr; // dp4a (int8) q6_K MoE prefill GEMM
|
||||
cl_kernel kernel_gemv_moe_mxfp4_f32, kernel_gemm_moe_mxfp4_f32;
|
||||
cl_kernel kernel_gemv_moe_mxfp4_f32_ns, kernel_gemm_moe_mxfp4_f32_ns, kernel_gemm_moe_mxfp4_f32_ns_bin;
|
||||
cl_kernel kernel_gemv_moe_mxfp4_f32_ns_wimg = nullptr; // weight-as-texture MoE decode GEMV
|
||||
cl_kernel kernel_gemm_moe_mxfp4_q8_1_dp4a; // dp4a (int8) mxfp4 MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_q4_0_q8_1_dp4a; // dp4a (int8) q4_0 MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_mxfp4_q8_1_dp4a = nullptr; // dp4a (int8) mxfp4 MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_q4_0_q8_1_dp4a = nullptr; // dp4a (int8) q4_0 MoE prefill GEMM
|
||||
cl_kernel kernel_moe_reorder_b;
|
||||
cl_kernel kernel_moe_histogram, kernel_moe_scan, kernel_moe_fill, kernel_moe_scatter;
|
||||
cl_kernel kernel_moe_combine_f32 = nullptr; // fused router-weight mul + cross-expert sum
|
||||
@@ -1037,10 +1067,10 @@ struct ggml_backend_opencl_context {
|
||||
cl_kernel kernel_gemv_noshuffle_q1_0_f32;
|
||||
cl_kernel kernel_gemv_noshuffle_q4_k_f32;
|
||||
cl_kernel kernel_gemm_noshuffle_q4_k_f32;
|
||||
cl_kernel kernel_gemm_noshuffle_q4_k_q8_1_dp4a; // dp4a (int8) dense prefill GEMM
|
||||
cl_kernel kernel_gemm_noshuffle_q4_k_q8_1_dp4a_wimg; // dp4a dense prefill GEMM, weights via texture (X1 opt-in)
|
||||
cl_kernel kernel_gemm_noshuffle_q5_k_q8_1_dp4a; // dp4a (int8) dense q5_K prefill GEMM
|
||||
cl_kernel kernel_gemm_noshuffle_q6_k_q8_1_dp4a; // dp4a (int8) dense q6_K prefill GEMM
|
||||
cl_kernel kernel_gemm_noshuffle_q4_k_q8_1_dp4a = nullptr; // dp4a (int8) dense prefill GEMM
|
||||
cl_kernel kernel_gemm_noshuffle_q4_k_q8_1_dp4a_wimg = nullptr; // dp4a dense prefill GEMM, weights via texture (X1 opt-in)
|
||||
cl_kernel kernel_gemm_noshuffle_q5_k_q8_1_dp4a = nullptr; // dp4a (int8) dense q5_K prefill GEMM
|
||||
cl_kernel kernel_gemm_noshuffle_q6_k_q8_1_dp4a = nullptr; // dp4a (int8) dense q6_K prefill GEMM
|
||||
cl_kernel kernel_quant_a_q8_1; // plain activation q8_1 pre-pass
|
||||
cl_kernel kernel_gemv_noshuffle_q6_K_f32;
|
||||
cl_kernel kernel_gemm_noshuffle_q6_K_f32;
|
||||
@@ -1640,6 +1670,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
// those compiler versions since it is anyway not used for Adreno.
|
||||
if (backend_ctx->gpu_family != ADRENO ||
|
||||
backend_ctx->adreno_cl_compiler_version.newer_than_or_same(E031, 38, 11, 0) ||
|
||||
backend_ctx->adreno_cl_compiler_version.type == E17 ||
|
||||
backend_ctx->adreno_cl_compiler_version.type == DX) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
@@ -3490,7 +3521,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
}
|
||||
|
||||
// gemm_noshuffle_q5_0_q8_1_dp4a (dp4a dense q5_0 prefill GEMM)
|
||||
{
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "gemm_noshuffle_q5_0_q8_1_dp4a.cl.h"
|
||||
@@ -3580,7 +3611,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
}
|
||||
|
||||
// gemm_noshuffle_iq4_nl_q8_1_dp4a (dp4a dense IQ4_NL prefill GEMM)
|
||||
{
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "gemm_noshuffle_iq4_nl_q8_1_dp4a.cl.h"
|
||||
@@ -3595,7 +3626,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
}
|
||||
|
||||
// gemm_noshuffle_q4_0_q8_1_dp4a (dp4a dense q4_0 prefill GEMM)
|
||||
{
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "gemm_noshuffle_q4_0_q8_1_dp4a.cl.h"
|
||||
@@ -3708,7 +3739,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
}
|
||||
|
||||
// gemm_noshuffle_q4_k_q8_1_dp4a (dp4a dense prefill GEMM)
|
||||
{
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "gemm_noshuffle_q4_k_q8_1_dp4a.cl.h"
|
||||
@@ -3730,7 +3761,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
}
|
||||
|
||||
// gemm_noshuffle_q8_0_q8_1_dp4a (dp4a dense q8_0 prefill GEMM)
|
||||
{
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "gemm_noshuffle_q8_0_q8_1_dp4a.cl.h"
|
||||
@@ -3746,7 +3777,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
}
|
||||
|
||||
// gemm_noshuffle_q5_k_q8_1_dp4a (dp4a dense prefill GEMM for q5_K)
|
||||
{
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "gemm_noshuffle_q5_k_q8_1_dp4a.cl.h"
|
||||
@@ -3761,7 +3792,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
}
|
||||
|
||||
// gemm_noshuffle_q6_k_q8_1_dp4a (dp4a dense prefill GEMM for q6_K ffn_down/output)
|
||||
{
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "gemm_noshuffle_q6_k_q8_1_dp4a.cl.h"
|
||||
@@ -4091,7 +4122,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
}
|
||||
|
||||
// gemm_moe_q4_k_q8_1_dp4a (dp4a prefill GEMM)
|
||||
{
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "gemm_moe_q4_k_q8_1_dp4a.cl.h"
|
||||
@@ -4108,7 +4139,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
}
|
||||
|
||||
// gemm_moe_mxfp4_q8_1_dp4a (dp4a prefill GEMM)
|
||||
{
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "gemm_moe_mxfp4_q8_1_dp4a.cl.h"
|
||||
@@ -4125,7 +4156,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
}
|
||||
|
||||
// gemm_moe_q4_0_q8_1_dp4a (dp4a prefill GEMM)
|
||||
{
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "gemm_moe_q4_0_q8_1_dp4a.cl.h"
|
||||
@@ -4142,7 +4173,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
}
|
||||
|
||||
// gemm_moe_q8_1_dp4a (generic dp4a MoE GEMM; MOE_QT=80 -> q8_0 expert variant)
|
||||
{
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "gemm_moe_q8_1_dp4a.cl.h"
|
||||
@@ -4256,7 +4287,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
}
|
||||
|
||||
// gemm_moe_q6_k_q8_1_dp4a (dp4a q6_K MoE prefill GEMM)
|
||||
{
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "gemm_moe_q6_k_q8_1_dp4a.cl.h"
|
||||
@@ -5602,6 +5633,8 @@ static void ggml_opencl_print_backend_info(ggml_backend_opencl_device_context *
|
||||
backend_ctx->has_subgroup_shuffle ? "true" : "false");
|
||||
GGML_LOG_INFO("ggml_opencl: device FP16 support: %s\n",
|
||||
backend_ctx->fp16_support ? "true" : "false");
|
||||
GGML_LOG_INFO("ggml_opencl: khr dot product support: %s\n",
|
||||
backend_ctx->has_integer_dot ? "true" : "false");
|
||||
GGML_LOG_INFO("ggml_opencl: mem base addr align: %u\n",
|
||||
backend_ctx->alignment);
|
||||
GGML_LOG_INFO("ggml_opencl: global mem size: %zu MB\n",
|
||||
@@ -5810,6 +5843,12 @@ static ggml_backend_opencl_context * ggml_cl_init(ggml_backend_dev_t dev) {
|
||||
strstr(ext_buffer, "cl_khr_subgroup_shuffle") != NULL ||
|
||||
backend_ctx->has_qcom_subgroup_shuffle;
|
||||
|
||||
// check for cl_khr_integer_dot_product
|
||||
// cl_qcom_dot_product8 uses signed * unsigned
|
||||
// while cl_khr_integer_dot_product uses signed * signed -- we stick with khr for now
|
||||
backend_ctx->has_integer_dot =
|
||||
strstr(ext_buffer, "cl_khr_integer_dot_product") != NULL;
|
||||
|
||||
cl_uint base_align_in_bits;
|
||||
CL_CHECK(clGetDeviceInfo(device, CL_DEVICE_MEM_BASE_ADDR_ALIGN, sizeof(cl_uint), &base_align_in_bits, NULL));
|
||||
GGML_ASSERT(base_align_in_bits % 8u == 0);
|
||||
@@ -6923,8 +6962,31 @@ inline bool use_adreno_kernels(const ggml_backend_opencl_context *backend_ctx, c
|
||||
return threashold_ok;
|
||||
}
|
||||
|
||||
static bool adreno_e17_compiler_quirks(const ggml_backend_opencl_context *backend_ctx) {
|
||||
if (!backend_ctx || backend_ctx->gpu_family != GPU_FAMILY::ADRENO ||
|
||||
backend_ctx->adreno_cl_compiler_version.type != ADRENO_CL_COMPILER_TYPE::E17) {
|
||||
return false;
|
||||
}
|
||||
const char * env = getenv("GGML_OPENCL_ADRENO_E17_QUIRKS");
|
||||
return !(env && env[0] == '0');
|
||||
}
|
||||
|
||||
inline bool use_adreno_moe_kernels(const ggml_backend_opencl_context *backend_ctx, const ggml_tensor *tensor) {
|
||||
GGML_UNUSED(backend_ctx);
|
||||
// The moe weight repack kernels *_trans4_ns alias a private ushort8 through a uchar*.
|
||||
// Certain compilers (found with some A7x and A6x) miscompiles this, corrupting the weights.
|
||||
// So, exclude A6x and A7x from using Adreno MoE kernels for now.
|
||||
// The quants that have a general mul_mat_id kernel fallback to the general version; the
|
||||
// rest fallback to CPU.
|
||||
if (backend_ctx && (backend_ctx->adreno_gen == ADRENO_GPU_GEN::A6X ||
|
||||
backend_ctx->adreno_gen == ADRENO_GPU_GEN::A7X ||
|
||||
backend_ctx->adreno_gen == ADRENO_GPU_GEN::ADRENO_UNKNOWN)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (adreno_e17_compiler_quirks(backend_ctx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int ne01 = tensor->ne[1];
|
||||
return (((strstr(tensor->name, "ffn") != NULL) && (strstr(tensor->name, "exps") != NULL)) || (strstr(tensor->name, "as") != NULL)) && (ne01 % 32 == 0);
|
||||
}
|
||||
@@ -7257,6 +7319,10 @@ static bool ggml_opencl_supports_op(ggml_backend_dev_t dev, const struct ggml_te
|
||||
case GGML_OP_MEAN:
|
||||
return op->src[0]->type == GGML_TYPE_F32;
|
||||
case GGML_OP_FLASH_ATTN_EXT: {
|
||||
// The E17 compilers segfault while building FA kernels, skip E17 for now
|
||||
if (adreno_e17_compiler_quirks(backend_ctx)) {
|
||||
return false;
|
||||
}
|
||||
const ggml_tensor * q = op->src[0];
|
||||
const ggml_tensor * k = op->src[1];
|
||||
const ggml_tensor * v = op->src[2];
|
||||
@@ -7310,6 +7376,14 @@ static bool ggml_opencl_supports_op(ggml_backend_dev_t dev, const struct ggml_te
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some compilers for A7x (Adreno 740, compiler E031.41) crashes when
|
||||
// building FA kernels with mixed or quant types (f32_f16, f32_q8_0, f32_q4_0)
|
||||
// Here we skip all A7x for these kernels to avoid crash
|
||||
if (backend_ctx->adreno_gen == ADRENO_GPU_GEN::A7X &&
|
||||
(is_f32_f16 || is_f32_q8_0 || is_f32_q4_0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dk == 512) {
|
||||
if (backend_ctx->gpu_family == INTEL) {
|
||||
return false;
|
||||
@@ -10516,10 +10590,16 @@ static ggml_backend_buffer_t ggml_backend_opencl_buffer_type_alloc_buffer(ggml_b
|
||||
|
||||
cl_int err;
|
||||
cl_mem mem = clCreateBuffer(backend_ctx->context, CL_MEM_READ_WRITE, size, NULL, &err);
|
||||
#if GGML_OPENCL_TARGET_VERSION >= 300
|
||||
// clCreateBufferWithProperties and cl_mem_properties are OpenCL 3.0. Drivers older than
|
||||
// that do not export the symbol, so a build targeting them fails to link. The large
|
||||
// buffer extension is only ever enabled on drivers that are well past 3.0, so this path
|
||||
// is dead there anyway.
|
||||
if (err != CL_SUCCESS && backend_ctx->adreno_use_large_buffer) {
|
||||
cl_mem_properties props[] = { 0x41A6 /* CL_LARGE_BUFFER_QCOM */, 1, 0 };
|
||||
mem = clCreateBufferWithProperties(backend_ctx->context, props, CL_MEM_READ_WRITE, size, NULL, &err);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (err != CL_SUCCESS) {
|
||||
GGML_LOG_INFO("%s: failed to allocate %.2f MiB\n", __func__, size / 1024.0 / 1024.0);
|
||||
@@ -15819,18 +15899,14 @@ static void ggml_cl_mul_mat_q4_0_f32_adreno(ggml_backend_t backend, const ggml_t
|
||||
CL_CHECK(clReleaseMemObject(b_sub_buf));
|
||||
CL_CHECK(clReleaseMemObject(b_img));
|
||||
} else {
|
||||
// dp4a (int8) dense prefill GEMM: quant activations to q8_1, then the int8
|
||||
// dp4a inner-loop GEMM, in place of the transpose + f16 half-dot kernel.
|
||||
// q4_0 = d*(q-8); mirrors the IQ4_NL/q8_0 dense dp4a paths (+ the sum term).
|
||||
// OPT-IN / DEFAULT OFF: correct, but neutral on X2E. q4_0's dequant
|
||||
// ((q-8)*scale) is already trivial so the f16 GEMM is weight-BW-bound and the
|
||||
// int8 ALU win has nothing to beat -- same as q5_0 dense (unlike IQ4_NL, whose
|
||||
// codebook dequant is expensive enough for dp4a to help). Kept for A/B; force
|
||||
// on with GGML_OPENCL_Q4_0_DENSE_DP4A=1. Needs N>8, K%32==0, M%64==0.
|
||||
// dp4a (int8) dense prefill GEMM, default off
|
||||
static const char * q4_0_dense_dp4a_env = getenv("GGML_OPENCL_Q4_0_DENSE_DP4A");
|
||||
const bool q4_0_dense_dp4a_on = q4_0_dense_dp4a_env
|
||||
bool q4_0_dense_dp4a_on = q4_0_dense_dp4a_env
|
||||
? (atoi(q4_0_dense_dp4a_env) != 0)
|
||||
: false;
|
||||
// dot prod has to be available
|
||||
q4_0_dense_dp4a_on = backend_ctx->has_integer_dot && q4_0_dense_dp4a_on;
|
||||
|
||||
if (q4_0_dense_dp4a_on && backend_ctx->kernel_gemm_noshuffle_q4_0_q8_1_dp4a
|
||||
&& N > 8 && (K % 32 == 0) && (M % 64 == 0)) {
|
||||
cl_mem a_sub = nullptr;
|
||||
@@ -16253,27 +16329,16 @@ static void ggml_cl_mul_mat_q5_0_f32_adreno(ggml_backend_t backend, const ggml_t
|
||||
CL_CHECK(clReleaseMemObject(b_sub_buf));
|
||||
CL_CHECK(clReleaseMemObject(b_img));
|
||||
} else {
|
||||
// dp4a (int8) dense q5_0 prefill GEMM. Quantizes the [N,K] activations to
|
||||
// q8_1 and runs the int8 dot instead of the f16 half-dot. Large-batch
|
||||
// (ne1>8) only. q5_0 weight = (x-16)*d (x = nibble | hi<<4); x packed as a
|
||||
// 0..31 byte (dp4a), the -16 centering folded into a single min term
|
||||
// (d*16) via the q8_1 block sum. Reads the qs/qh/d buffers byte-identically
|
||||
// to the f16 kernel (greedy byte-identical, MUL_MAT NMSE-OK).
|
||||
//
|
||||
// OPT-IN / DEFAULT OFF. Unlike q8_0/q4_K dense, dp4a is not a win for q5_0 on
|
||||
// X2E: the q5_0 model is bottlenecked elsewhere, so the dense-GEMM int8 win
|
||||
// has nothing to surface and the q8_1 prepass slightly hurts. Kept correct +
|
||||
// opt-in for the X1 A/B (different texture-cache dynamic) and the
|
||||
// weight-texture variant. Env: GGML_OPENCL_Q5_DENSE_DP4A=1.
|
||||
// Weight-as-texture variant (X1 lever): routes the dominant qs nibble plane
|
||||
// through an image1d_buffer (qh stays a buffer). Opt-in
|
||||
// GGML_OPENCL_Q5_DENSE_DP4A_WIMG; when set it also forces the dp4a path on.
|
||||
// dp4a (int8) dense q5_0 prefill GEMM, default off
|
||||
static const char * q5_dense_dp4a_env = getenv("GGML_OPENCL_Q5_DENSE_DP4A");
|
||||
static const char * q5_dense_wimg_env = getenv("GGML_OPENCL_Q5_DENSE_DP4A_WIMG");
|
||||
const bool q5_dense_wimg_on = q5_dense_wimg_env && (atoi(q5_dense_wimg_env) != 0);
|
||||
const bool q5_dense_dp4a_on = q5_dense_wimg_on
|
||||
bool q5_dense_dp4a_on = q5_dense_wimg_on
|
||||
? true
|
||||
: (q5_dense_dp4a_env && (atoi(q5_dense_dp4a_env) != 0));
|
||||
// dot prod has to be available
|
||||
q5_dense_dp4a_on = backend_ctx->has_integer_dot && q5_dense_dp4a_on;
|
||||
|
||||
if (q5_dense_dp4a_on && backend_ctx->kernel_gemm_noshuffle_q5_0_q8_1_dp4a
|
||||
&& N > 8 && (K % 32 == 0) && (M % 64 == 0)) {
|
||||
cl_mem a_sub = nullptr;
|
||||
@@ -16708,15 +16773,14 @@ static void ggml_cl_mul_mat_iq4_nl_f32_adreno(ggml_backend_t backend, const ggml
|
||||
} else {
|
||||
// dp4a (int8) dense IQ4_NL prefill GEMM. Quantizes the [N,K] activations to
|
||||
// q8_1 and runs the int8 dot instead of the f16 half-dot. Large-batch
|
||||
// (ne1>8) only. IQ4_NL weight = kvalues[nibble]*d; the codebook value IS the
|
||||
// int8 (no min term), so this is the q8_0 dense case plus a nibble->int8 LUT
|
||||
// unpack. Reads the q/d buffers byte-identically to the f16 kernel. No bin
|
||||
// kernel for IQ4_NL -> baseline is f16, default ON for X2E (like q4_K/q6_K
|
||||
// dense dp4a). X1 stays on f16. Env: GGML_OPENCL_IQ4NL_DENSE_DP4A.
|
||||
// (ne1>8) only
|
||||
static const char * iq4nl_dense_dp4a_env = getenv("GGML_OPENCL_IQ4NL_DENSE_DP4A");
|
||||
const bool iq4nl_dense_dp4a_on = iq4nl_dense_dp4a_env
|
||||
bool iq4nl_dense_dp4a_on = iq4nl_dense_dp4a_env
|
||||
? (atoi(iq4nl_dense_dp4a_env) != 0)
|
||||
: (backend_ctx->adreno_gen == ADRENO_GPU_GEN::X2E);
|
||||
// dot prod has to be available
|
||||
iq4nl_dense_dp4a_on = backend_ctx->has_integer_dot && iq4nl_dense_dp4a_on;
|
||||
|
||||
if (iq4nl_dense_dp4a_on && backend_ctx->kernel_gemm_noshuffle_iq4_nl_q8_1_dp4a
|
||||
&& N > 8 && (K % 32 == 0) && (M % 64 == 0)) {
|
||||
cl_mem a_sub = nullptr;
|
||||
@@ -16966,13 +17030,15 @@ static void ggml_cl_mul_mat_q8_0_f32_adreno(ggml_backend_t backend, const ggml_t
|
||||
static const char * q8_dense_wimg_env = getenv("GGML_OPENCL_Q8_DENSE_DP4A_WIMG");
|
||||
const bool q8_dense_wimg_on = q8_dense_wimg_env && (atoi(q8_dense_wimg_env) != 0);
|
||||
|
||||
const bool q8_bin_loaded = (backend_ctx->kernel_gemm_noshuffle_q8_0_f32_bin != nullptr);
|
||||
const bool q8_bin_loaded = (backend_ctx->kernel_gemm_noshuffle_q8_0_f32_bin != nullptr);
|
||||
// bin kernel takes precedence
|
||||
const bool q8_dense_dp4a_on = q8_dense_wimg_on
|
||||
bool q8_dense_dp4a_on = q8_dense_wimg_on
|
||||
? true
|
||||
: q8_dense_dp4a_env
|
||||
? (atoi(q8_dense_dp4a_env) != 0)
|
||||
: (backend_ctx->adreno_gen == ADRENO_GPU_GEN::X2E && !q8_bin_loaded);
|
||||
// dot prod has to be available
|
||||
q8_dense_dp4a_on = backend_ctx->has_integer_dot && q8_dense_dp4a_on;
|
||||
|
||||
if (q8_dense_dp4a_on && backend_ctx->kernel_gemm_noshuffle_q8_0_q8_1_dp4a
|
||||
&& N > 8 && (K % 32 == 0) && (M % 64 == 0)) {
|
||||
@@ -17379,13 +17445,16 @@ static void ggml_cl_mul_mat_q4_k_f32_adreno(ggml_backend_t backend, const ggml_t
|
||||
static const char * q4k_dense_dp4a_env = getenv("GGML_OPENCL_Q4K_DENSE_DP4A");
|
||||
static const char * q4k_dense_wimg_env = getenv("GGML_OPENCL_Q4K_DENSE_DP4A_WIMG");
|
||||
|
||||
const bool q4k_dense_wimg_on = q4k_dense_wimg_env && (atoi(q4k_dense_wimg_env) != 0);
|
||||
const bool q4k_dense_dp4a_on = q4k_dense_wimg_on
|
||||
const bool q4k_dense_wimg_on = q4k_dense_wimg_env && (atoi(q4k_dense_wimg_env) != 0);
|
||||
bool q4k_dense_dp4a_on = q4k_dense_wimg_on
|
||||
? true
|
||||
: q4k_dense_dp4a_env
|
||||
? (atoi(q4k_dense_dp4a_env) != 0)
|
||||
: (backend_ctx->adreno_gen == ADRENO_GPU_GEN::X2E);
|
||||
|
||||
// dp4 has to be available
|
||||
q4k_dense_dp4a_on = backend_ctx->has_integer_dot && q4k_dense_dp4a_on;
|
||||
|
||||
// Min N for the dp4a prefill GEMM, default 9, i.e., ne1 > 8
|
||||
static const char * q4k_dp4a_minn_env = getenv("GGML_OPENCL_Q4K_DP4A_MINN");
|
||||
const int q4k_dp4a_minn = q4k_dp4a_minn_env ? atoi(q4k_dp4a_minn_env) : 9;
|
||||
@@ -17608,9 +17677,11 @@ static void ggml_cl_mul_mat_q6_K_f32_adreno(ggml_backend_t backend, const ggml_t
|
||||
|
||||
// dp4a (int8) dense q6_K prefill GEMM
|
||||
static const char * q6k_dense_dp4a_env = getenv("GGML_OPENCL_Q6K_DENSE_DP4A");
|
||||
static const bool q6k_dense_dp4a_on = (q6k_dense_dp4a_env != nullptr)
|
||||
bool q6k_dense_dp4a_on = (q6k_dense_dp4a_env != nullptr)
|
||||
? (atoi(q6k_dense_dp4a_env) != 0)
|
||||
: (backend_ctx->adreno_gen != ADRENO_GPU_GEN::X1E);
|
||||
// dot prod has to be available
|
||||
q6k_dense_dp4a_on = backend_ctx->has_integer_dot && q6k_dense_dp4a_on;
|
||||
|
||||
const bool is_output_w_dp4a = strncmp(src0->name, "output", 6) == 0 ||
|
||||
strncmp(src0->name, "token_embd", 10) == 0;
|
||||
@@ -17901,9 +17972,11 @@ static void ggml_cl_mul_mat_q5_K_f32_adreno(ggml_backend_t backend, const ggml_t
|
||||
|
||||
// dp4a (int8) dense q5_K prefill GEMM
|
||||
static const char * q5k_dense_dp4a_env = getenv("GGML_OPENCL_Q5K_DENSE_DP4A");
|
||||
const bool q5k_dense_dp4a_on = q5k_dense_dp4a_env
|
||||
bool q5k_dense_dp4a_on = q5k_dense_dp4a_env
|
||||
? (atoi(q5k_dense_dp4a_env) != 0)
|
||||
: (backend_ctx->adreno_gen == ADRENO_GPU_GEN::X2E);
|
||||
// dot prod has to be available
|
||||
q5k_dense_dp4a_on = backend_ctx->has_integer_dot && q5k_dense_dp4a_on;
|
||||
|
||||
if (q5k_dense_dp4a_on && ne1 > 8 && (ne00 % 32 == 0) && (ne01 % 64 == 0)) {
|
||||
const int Mm = ne01, Nn = ne1, Kk = ne00;
|
||||
@@ -20640,6 +20713,8 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
bool use_moe_dp4a = q4_0_moe_dp4a_env
|
||||
? (atoi(q4_0_moe_dp4a_env) != 0)
|
||||
: (backend_ctx->adreno_gen == ADRENO_GPU_GEN::X2E);
|
||||
// dot prod has to be available
|
||||
use_moe_dp4a = backend_ctx->has_integer_dot && use_moe_dp4a;
|
||||
// bin kernel takes precedence
|
||||
use_moe_dp4a = use_moe_dp4a && backend_ctx->kernel_gemm_moe_q4_0_f32_ns_bin == nullptr;
|
||||
|
||||
@@ -21815,6 +21890,8 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
bool use_moe_dp4a = (q4k_moe_dp4a_env != nullptr)
|
||||
? (atoi(q4k_moe_dp4a_env) != 0)
|
||||
: (backend_ctx->adreno_gen == ADRENO_GPU_GEN::X2E || backend_ctx->adreno_gen == ADRENO_GPU_GEN::X1E);
|
||||
// dot prod has to be available
|
||||
use_moe_dp4a = backend_ctx->has_integer_dot && use_moe_dp4a;
|
||||
// bin kernel takes precedence
|
||||
use_moe_dp4a = use_moe_dp4a && backend_ctx->kernel_gemm_moe_q4_k_f32_ns_bin == nullptr;
|
||||
|
||||
@@ -22316,10 +22393,12 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
|
||||
// dp4a (int8) q6_K MoE prefill GEMM
|
||||
static const char * q6k_moe_dp4a_env = getenv("GGML_OPENCL_Q6K_MOE_DP4A");
|
||||
static const bool use_moe_dp4a = (q6k_moe_dp4a_env != nullptr)
|
||||
bool use_moe_dp4a = (q6k_moe_dp4a_env != nullptr)
|
||||
? (atoi(q6k_moe_dp4a_env) != 0)
|
||||
: (backend_ctx->adreno_gen == ADRENO_GPU_GEN::X2E
|
||||
|| backend_ctx->adreno_gen == ADRENO_GPU_GEN::X1E);
|
||||
// dot prod has to be available
|
||||
use_moe_dp4a = backend_ctx->has_integer_dot && use_moe_dp4a;
|
||||
|
||||
cl_buffer_region region;
|
||||
region.origin = 0;
|
||||
@@ -22569,6 +22648,8 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
bool use_moe_dp4a = mxfp4_moe_dp4a_env
|
||||
? (atoi(mxfp4_moe_dp4a_env) != 0)
|
||||
: (backend_ctx->adreno_gen == ADRENO_GPU_GEN::X2E);
|
||||
// dot prod has to be available
|
||||
use_moe_dp4a = backend_ctx->has_integer_dot && use_moe_dp4a;
|
||||
// bin kernel takes precedence
|
||||
use_moe_dp4a = use_moe_dp4a && backend_ctx->kernel_gemm_moe_mxfp4_f32_ns_bin == nullptr;
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
#elif defined(cl_qcom_subgroup_shuffle)
|
||||
#pragma OPENCL EXTENSION cl_qcom_subgroup_shuffle : enable
|
||||
#define HAS_SUBGROUP_SHUFFLE 1
|
||||
// Adreno compilers that expose only cl_qcom_subgroup_shuffle do not declare the KHR
|
||||
// name, so calling it is an implicit declaration and the program fails to build.
|
||||
// Route it to the qcom builtin.
|
||||
#define sub_group_shuffle_xor(val, mask) qcom_sub_group_shuffle_xor((val), (mask), CLK_SUB_GROUP_SHUFFLE_WIDTH_WAVE_SIZE_QCOM, 0.0f)
|
||||
#endif
|
||||
|
||||
#define ACC_TYPE float
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#elif defined(cl_qcom_subgroup_shuffle)
|
||||
#pragma OPENCL EXTENSION cl_qcom_subgroup_shuffle : enable
|
||||
#define HAS_SUBGROUP_SHUFFLE 1
|
||||
// Adreno compilers that expose only cl_qcom_subgroup_shuffle do not declare the KHR
|
||||
// name, so calling it is an implicit declaration and the program fails to build.
|
||||
// Route it to the qcom builtin.
|
||||
#define sub_group_shuffle_xor(val, mask) qcom_sub_group_shuffle_xor((val), (mask), CLK_SUB_GROUP_SHUFFLE_WIDTH_WAVE_SIZE_QCOM, 0.0f)
|
||||
#endif
|
||||
|
||||
// Flash attention: Q=f32, K=q4_0, V=q4_0.
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#elif defined(cl_qcom_subgroup_shuffle)
|
||||
#pragma OPENCL EXTENSION cl_qcom_subgroup_shuffle : enable
|
||||
#define HAS_SUBGROUP_SHUFFLE 1
|
||||
// Adreno compilers that expose only cl_qcom_subgroup_shuffle do not declare the KHR
|
||||
// name, so calling it is an implicit declaration and the program fails to build.
|
||||
// Route it to the qcom builtin.
|
||||
#define sub_group_shuffle_xor(val, mask) qcom_sub_group_shuffle_xor((val), (mask), CLK_SUB_GROUP_SHUFFLE_WIDTH_WAVE_SIZE_QCOM, 0.0f)
|
||||
#endif
|
||||
|
||||
// Flash attention: Q=f32, K=q8_0, V=q8_0.
|
||||
|
||||
@@ -274,8 +274,9 @@ kernel void kernel_gemm_moe_mxfp4_f32_ns(
|
||||
shared_b[b_local_offset.y] = bx8_f16.hi;
|
||||
|
||||
// Dequantization
|
||||
reg_a.lo = mxfp4_to_fp16_packed8(as_ushort2(mxfp4x16.lo)) * s;
|
||||
reg_a.hi = mxfp4_to_fp16_packed8(as_ushort2(mxfp4x16.hi)) * s;
|
||||
// Cast the e8m0 scale to half to satisfy E17 compilers
|
||||
reg_a.lo = mxfp4_to_fp16_packed8(as_ushort2(mxfp4x16.lo)) * (half)s;
|
||||
reg_a.hi = mxfp4_to_fp16_packed8(as_ushort2(mxfp4x16.hi)) * (half)s;
|
||||
|
||||
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
@@ -304,8 +305,9 @@ kernel void kernel_gemm_moe_mxfp4_f32_ns(
|
||||
shared_b[b_local_offset.y] = bx8_f16.hi;
|
||||
|
||||
// Dequantization
|
||||
reg_a.lo = mxfp4_to_fp16_packed8(as_ushort2(mxfp4x16.lo)) * s;
|
||||
reg_a.hi = mxfp4_to_fp16_packed8(as_ushort2(mxfp4x16.hi)) * s;
|
||||
// Cast the e8m0 scale to half to satisfy E17 compilers
|
||||
reg_a.lo = mxfp4_to_fp16_packed8(as_ushort2(mxfp4x16.lo)) * (half)s;
|
||||
reg_a.hi = mxfp4_to_fp16_packed8(as_ushort2(mxfp4x16.hi)) * (half)s;
|
||||
|
||||
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
|
||||
@@ -296,7 +296,12 @@ kernel void kernel_gemv_noshuffle_iq4_nl_f32(
|
||||
// 2 outputs per fiber in wave 0
|
||||
if (groupId == 0) {
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
vstore2(totalSum, 0, &(dst[gid * 2]));
|
||||
// Guard the two output rows. The x-grid is padded to CEIL_DIV(ne01/2,64)*64,
|
||||
// so when ne01 is not a multiple of 128 the tail row-pairs run past row ne01
|
||||
// and would overrun dst into the adjacent tensor. No-op / byte-identical when
|
||||
// ne01 % 128 == 0 (M/2 already a multiple of 64 -> no padding).
|
||||
if (gid * 2 + 0 < M) dst[gid * 2 + 0] = totalSum.s0;
|
||||
if (gid * 2 + 1 < M) dst[gid * 2 + 1] = totalSum.s1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -116,6 +116,10 @@ __kernel void kernel_gemv_noshuffle_q1_0_f32(
|
||||
|
||||
if (groupId == 0) {
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
dst[gid] = totalSum;
|
||||
// Guard the output row. The x-grid is padded to CEIL_DIV(M,wavesize)*wavesize,
|
||||
// so when ne01 is not a multiple of the wave size the tail work-items run past
|
||||
// row ne01 and would overrun dst into the adjacent tensor. No-op / byte-identical
|
||||
// when ne01 is wave-aligned (no padding).
|
||||
if (gid < M) dst[gid] = totalSum;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +268,12 @@ __kernel void kernel_gemv_noshuffle_q4_0_f32(
|
||||
// 2 outputs per fiber in wave 0
|
||||
if (groupId == 0) {
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
vstore2(totalSum, 0, &(dst[gid * 2]));
|
||||
// Guard the two output rows. The x-grid is padded to CEIL_DIV(ne01/2,64)*64,
|
||||
// so when ne01 is not a multiple of 128 the tail row-pairs run past row ne01
|
||||
// and would overrun dst into the adjacent tensor. No-op / byte-identical when
|
||||
// ne01 % 128 == 0 (M/2 already a multiple of 64 -> no padding).
|
||||
if (gid * 2 + 0 < M) dst[gid * 2 + 0] = totalSum.s0;
|
||||
if (gid * 2 + 1 < M) dst[gid * 2 + 1] = totalSum.s1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -262,7 +262,11 @@ __kernel void kernel_gemv_noshuffle_q4_0_f32(
|
||||
// 2 outputs per fiber in wave 0
|
||||
if (groupId == 0) {
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
vstore2(totalSum, 0, &(dst[gid * 2]));
|
||||
// Guard the two output rows against the padded x-grid tail overrunning dst.
|
||||
// The current shape specializations are all ne01 % 128 == 0 (no padding), so
|
||||
// this is a no-op / byte-identical today; keep it in lockstep with the base kernel.
|
||||
if (gid * 2 + 0 < ne01) dst[gid * 2 + 0] = totalSum.s0;
|
||||
if (gid * 2 + 1 < ne01) dst[gid * 2 + 1] = totalSum.s1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -277,7 +277,12 @@ kernel void kernel_gemv_noshuffle_q4_1_f32(
|
||||
// 2 outputs per fiber in wave 0
|
||||
if (groupId == 0) {
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
vstore2(totalSum, 0, &(dst[gid * 2]));
|
||||
// Guard the two output rows. The x-grid is padded to CEIL_DIV(ne01/2,64)*64,
|
||||
// so when ne01 is not a multiple of 128 the tail row-pairs run past row ne01
|
||||
// and would overrun dst into the adjacent tensor. No-op / byte-identical when
|
||||
// ne01 % 128 == 0 (M/2 already a multiple of 64 -> no padding).
|
||||
if (gid * 2 + 0 < M) dst[gid * 2 + 0] = totalSum.s0;
|
||||
if (gid * 2 + 1 < M) dst[gid * 2 + 1] = totalSum.s1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -312,7 +312,12 @@ kernel void kernel_gemv_noshuffle_q4_k_f32(
|
||||
// 2 outputs per fiber in wave 0
|
||||
if (groupId == 0) {
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
vstore2(totalSum, 0, &(dst[gid * 2]));
|
||||
// Guard the two output rows. The x-grid is padded to CEIL_DIV(ne01/2,64)*64,
|
||||
// so when ne01 is not a multiple of 128 the tail row-pairs run past row ne01
|
||||
// and would overrun dst into the adjacent tensor. No-op / byte-identical when
|
||||
// ne01 % 128 == 0 (M/2 already a multiple of 64 -> no padding).
|
||||
if (gid * 2 + 0 < M) dst[gid * 2 + 0] = totalSum.s0;
|
||||
if (gid * 2 + 1 < M) dst[gid * 2 + 1] = totalSum.s1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -285,7 +285,12 @@ __kernel void kernel_gemv_noshuffle_q5_0_f32(
|
||||
// 2 outputs per fiber in wave 0
|
||||
if (groupId == 0) {
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
vstore2(totalSum, 0, &(dst[gid * 2]));
|
||||
// Guard the two output rows. The x-grid is padded to CEIL_DIV(ne01/2,64)*64,
|
||||
// so when ne01 is not a multiple of 128 the tail row-pairs run past row ne01
|
||||
// and would overrun dst into the adjacent tensor. No-op / byte-identical when
|
||||
// ne01 % 128 == 0 (M/2 already a multiple of 64 -> no padding).
|
||||
if (gid * 2 + 0 < M) dst[gid * 2 + 0] = totalSum.s0;
|
||||
if (gid * 2 + 1 < M) dst[gid * 2 + 1] = totalSum.s1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -288,7 +288,12 @@ __kernel void kernel_gemv_noshuffle_q5_1_f32(
|
||||
// 2 outputs per fiber in wave 0
|
||||
if (groupId == 0) {
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
vstore2(totalSum, 0, &(dst[gid * 2]));
|
||||
// Guard the two output rows. The x-grid is padded to CEIL_DIV(ne01/2,64)*64,
|
||||
// so when ne01 is not a multiple of 128 the tail row-pairs run past row ne01
|
||||
// and would overrun dst into the adjacent tensor. No-op / byte-identical when
|
||||
// ne01 % 128 == 0 (M/2 already a multiple of 64 -> no padding).
|
||||
if (gid * 2 + 0 < M) dst[gid * 2 + 0] = totalSum.s0;
|
||||
if (gid * 2 + 1 < M) dst[gid * 2 + 1] = totalSum.s1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -321,6 +321,11 @@ kernel void kernel_gemv_noshuffle_q5_k_f32(
|
||||
// 2 outputs per fiber in wave 0
|
||||
if (groupId == 0) {
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
vstore2(totalSum, 0, &(dst[gid * 2]));
|
||||
// Guard the two output rows. The x-grid is padded to CEIL_DIV(ne01/2,64)*64,
|
||||
// so when ne01 is not a multiple of 128 the tail row-pairs run past row ne01
|
||||
// and would overrun dst into the adjacent tensor. No-op / byte-identical when
|
||||
// ne01 % 128 == 0 (M/2 already a multiple of 64 -> no padding).
|
||||
if (gid * 2 + 0 < M) dst[gid * 2 + 0] = totalSum.s0;
|
||||
if (gid * 2 + 1 < M) dst[gid * 2 + 1] = totalSum.s1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,6 +288,11 @@ kernel void kernel_gemv_noshuffle_q6_K_f32(
|
||||
|
||||
if (grp == 0) {
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
vstore2(total_sum, 0, &(dst[gid * 2]));
|
||||
// Guard the two output rows. The x-grid is padded to CEIL_DIV(ne01/2,64)*64,
|
||||
// so when ne01 is not a multiple of 128 the tail row-pairs run past row ne01
|
||||
// and would overrun dst into the adjacent tensor (garbage downstream).
|
||||
// No-op / byte-identical when ne01 % 128 == 0 (no padding).
|
||||
if (gid * 2 + 0 < ne01) dst[gid * 2 + 0] = total_sum.s0;
|
||||
if (gid * 2 + 1 < ne01) dst[gid * 2 + 1] = total_sum.s1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +190,10 @@ __kernel void kernel_gemv_noshuffle_q8_0_f32(
|
||||
// 1 outputs per fiber in wave 0
|
||||
if (groupId == 0) {
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
dst[gid] = totalSum;
|
||||
// Guard the output row. The x-grid is padded to CEIL_DIV(M,wavesize)*wavesize,
|
||||
// so when ne01 is not a multiple of the wave size the tail work-items run past
|
||||
// row ne01 and would overrun dst into the adjacent tensor. No-op / byte-identical
|
||||
// when ne01 is wave-aligned (no padding).
|
||||
if (gid < M) dst[gid] = totalSum;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,14 @@ kernel void kernel_mul_mat_f16_f16(
|
||||
|
||||
global half * x = (global half *) (src0 + offset_src0);
|
||||
|
||||
if (ne00 < 128) {
|
||||
// The vector path below casts the row pointers to half4, which must be 8-byte aligned.
|
||||
// A row address is r0*nb01 + ..., and a permuted or strided src leaves nb01/nb11
|
||||
// unconstrained -- an odd ne00, say, gives a row that is only 2-byte aligned. Every
|
||||
// src1 row this work-item walks is src1_base + r1*nb11, so require both.
|
||||
const ulong src1_base = (ulong) (src1 + (i12)*nb12 + (i13)*nb13);
|
||||
const bool row_aligned = (((ulong) x) & 7) == 0 && (src1_base & 7) == 0 && (nb11 & 7) == 0;
|
||||
|
||||
if (ne00 < 128 || !row_aligned) {
|
||||
for (int row = 0; row < N_F16_F16; ++row) {
|
||||
int r1 = rb + row;
|
||||
if (r1 >= ne11) {
|
||||
|
||||
@@ -64,7 +64,14 @@ kernel void kernel_mul_mat_f16_f32(
|
||||
|
||||
global half * x = (global half *) (src0 + offset_src0);
|
||||
|
||||
if (ne00 < 128) {
|
||||
// The vector path below casts the row pointers to half4/float4, which must be 8- and
|
||||
// 16-byte aligned. A row address is r0*nb01 + ..., and a permuted or strided src leaves
|
||||
// nb01/nb11 unconstrained -- an odd ne00, say, gives a row that is only 2-byte aligned.
|
||||
// Every src1 row this work-item walks is src1_base + r1*nb11, so require both.
|
||||
const ulong src1_base = (ulong) (src1 + (i12)*nb12 + (i13)*nb13);
|
||||
const bool row_aligned = (((ulong) x) & 7) == 0 && (src1_base & 15) == 0 && (nb11 & 15) == 0;
|
||||
|
||||
if (ne00 < 128 || !row_aligned) {
|
||||
for (int row = 0; row < N_F16_F32; ++row) {
|
||||
int r1 = rb + row;
|
||||
if (r1 >= ne11) {
|
||||
|
||||
@@ -64,8 +64,15 @@ kernel void kernel_mul_mat_f16_f32_1row(
|
||||
global half * x = (global half *) (src0 + offset_src0);
|
||||
global float * y = (global float *) (src1 + offset_src1);
|
||||
|
||||
// The vector path below casts the row pointers to half4/float4, which must be 8- and
|
||||
// 16-byte aligned. A row address is r0*nb01 + ..., and a permuted or strided src leaves
|
||||
// nb01/nb11 unconstrained -- an odd ne00, say, gives a row that is only 2-byte aligned.
|
||||
// Take the vector path only when the rows this work-item touches are actually aligned;
|
||||
// the scalar loop has no such requirement.
|
||||
const bool row_aligned = (((ulong) x) & 7) == 0 && (((ulong) y) & 15) == 0;
|
||||
|
||||
float sumf = 0;
|
||||
if (ne00 < 128) {
|
||||
if (ne00 < 128 || !row_aligned) {
|
||||
for (int i = get_sub_group_local_id(); i < ne00; i += get_max_sub_group_size()) {
|
||||
sumf += (float) x[i] * (float) y[i];
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
#elif defined(cl_qcom_subgroup_shuffle)
|
||||
#pragma OPENCL EXTENSION cl_qcom_subgroup_shuffle : enable
|
||||
#define HAS_SUBGROUP_SHUFFLE 1
|
||||
// Adreno compilers that expose only cl_qcom_subgroup_shuffle do not declare the KHR
|
||||
// name, so calling it is an implicit declaration and the program fails to build.
|
||||
// Route it to the qcom builtin.
|
||||
#define sub_group_shuffle_xor(val, mask) qcom_sub_group_shuffle_xor((val), (mask), CLK_SUB_GROUP_SHUFFLE_WIDTH_WAVE_SIZE_QCOM, 0.0f)
|
||||
#endif
|
||||
|
||||
// Assumes row size (ne00) is a multiple of 4
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma OPENCL EXTENSION cl_khr_fp16 : enable
|
||||
|
||||
#ifdef cl_intel_required_subgroup_size
|
||||
#pragma OPENCL EXTENSION cl_intel_required_subgroup_size : enable
|
||||
#define INTEL_GPU 1
|
||||
|
||||
@@ -64,6 +64,7 @@ extern int g_ggml_sycl_enable_fusion;
|
||||
extern int g_ggml_sycl_prioritize_dmmv;
|
||||
extern int g_ggml_sycl_enable_flash_attention;
|
||||
extern int g_ggml_sycl_dev2dev_memcpy;
|
||||
extern int g_ggml_sycl_fa_onednn;
|
||||
|
||||
|
||||
#if defined(__clang__) && __has_builtin(__builtin_expect)
|
||||
|
||||
@@ -71,8 +71,8 @@ struct dw_cwhn_layout {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Layout>
|
||||
static void conv2d_dw_kernel(const float * input, const float * kernel, float * output,
|
||||
template <typename KernelT, typename Layout>
|
||||
static void conv2d_dw_kernel(const float * input, const KernelT * kernel, float * output,
|
||||
const conv2d_dw_params p, const sycl::nd_item<3> & item_ct1) {
|
||||
const int global_idx = item_ct1.get_local_id(2) +
|
||||
item_ct1.get_group(2) * item_ct1.get_local_range(2);
|
||||
@@ -93,15 +93,15 @@ static void conv2d_dw_kernel(const float * input, const float * kernel, float *
|
||||
for (int kx = bounds.x_min; kx < bounds.x_max; ++kx) {
|
||||
const int in_x = dw_calculate_input_coord(out_x, kx, p.stride_x, p.dilation_x, p.padding_x);
|
||||
acc += input[Layout::input_index(n, c, in_y, in_x, p)] *
|
||||
kernel[Layout::kernel_index(c, ky, kx, p)];
|
||||
static_cast<float>(kernel[Layout::kernel_index(c, ky, kx, p)]);
|
||||
}
|
||||
}
|
||||
|
||||
output[Layout::output_index(n, c, out_y, out_x, p)] = acc;
|
||||
}
|
||||
|
||||
template <typename Layout>
|
||||
static void conv2d_dw_sycl(const float * x_d, const float * w_d, float * y_d,
|
||||
template <typename KernelT, typename Layout>
|
||||
static void conv2d_dw_sycl(const float * x_d, const KernelT * w_d, float * y_d,
|
||||
const conv2d_dw_params p, const queue_ptr & stream) {
|
||||
const int total = p.batches * p.channels * p.out_h * p.out_w;
|
||||
const int num_blocks = (total + SYCL_CONV2D_DW_BLOCK_SIZE - 1) / SYCL_CONV2D_DW_BLOCK_SIZE;
|
||||
@@ -109,7 +109,7 @@ static void conv2d_dw_sycl(const float * x_d, const float * w_d, float * y_d,
|
||||
const sycl::range<3> block_nums(1, 1, num_blocks);
|
||||
stream->parallel_for(sycl::nd_range<3>(block_nums * block_dims, block_dims),
|
||||
[=](sycl::nd_item<3> item_ct1) {
|
||||
conv2d_dw_kernel<Layout>(x_d, w_d, y_d, p, item_ct1);
|
||||
conv2d_dw_kernel<KernelT, Layout>(x_d, w_d, y_d, p, item_ct1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -119,9 +119,9 @@ void ggml_sycl_op_conv2d_dw(ggml_backend_sycl_context & ctx, ggml_tensor * dst)
|
||||
const ggml_tensor * kernel = dst->src[0];
|
||||
const ggml_tensor * input = dst->src[1];
|
||||
|
||||
GGML_ASSERT(kernel->type == GGML_TYPE_F32 && input->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT((kernel->type == GGML_TYPE_F32 || kernel->type == GGML_TYPE_F16) &&
|
||||
input->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32);
|
||||
|
||||
const float * w_d = (const float *) kernel->data;
|
||||
const float * x_d = (const float *) input->data;
|
||||
float * y_d = (float *) dst->data;
|
||||
|
||||
@@ -148,11 +148,23 @@ void ggml_sycl_op_conv2d_dw(ggml_backend_sycl_context & ctx, ggml_tensor * dst)
|
||||
|
||||
const queue_ptr stream = ctx.stream();
|
||||
|
||||
if (ggml_is_contiguous(input)) {
|
||||
conv2d_dw_sycl<dw_whcn_layout>(x_d, w_d, y_d, params, stream);
|
||||
} else if (ggml_is_contiguous_channels(input)) {
|
||||
conv2d_dw_sycl<dw_cwhn_layout>(x_d, w_d, y_d, params, stream);
|
||||
if (kernel->type == GGML_TYPE_F16) {
|
||||
const sycl::half * w_d = (const sycl::half *) kernel->data;
|
||||
if (ggml_is_contiguous(input)) {
|
||||
conv2d_dw_sycl<sycl::half, dw_whcn_layout>(x_d, w_d, y_d, params, stream);
|
||||
} else if (ggml_is_contiguous_channels(input)) {
|
||||
conv2d_dw_sycl<sycl::half, dw_cwhn_layout>(x_d, w_d, y_d, params, stream);
|
||||
} else {
|
||||
GGML_ABORT("Unsupported memory layout for conv2d_dw");
|
||||
}
|
||||
} else {
|
||||
GGML_ABORT("Unsupported memory layout for conv2d_dw");
|
||||
const float * w_d = (const float *) kernel->data;
|
||||
if (ggml_is_contiguous(input)) {
|
||||
conv2d_dw_sycl<float, dw_whcn_layout>(x_d, w_d, y_d, params, stream);
|
||||
} else if (ggml_is_contiguous_channels(input)) {
|
||||
conv2d_dw_sycl<float, dw_cwhn_layout>(x_d, w_d, y_d, params, stream);
|
||||
} else {
|
||||
GGML_ABORT("Unsupported memory layout for conv2d_dw");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
typedef void (*dequantize_kernel_t)(const void * vx, const int64_t ib, const int iqs, dfloat2 & v);
|
||||
typedef void (*dequantize_kernel_t_reorder)(const void *d, const int64_t ib, const void *qs,
|
||||
const int iqs, dfloat2 &v);
|
||||
typedef void (*dequantize_kernel_f32_t)(const void * vx, const int64_t ib, const int iqs, float & v0, float & v1);
|
||||
|
||||
#if QK_K == 256
|
||||
static inline void get_scale_min_k4(int j, const uint8_t * q, uint8_t & d, uint8_t & m);
|
||||
@@ -85,6 +86,21 @@ static __dpct_inline__ void dequantize_q1_0_reorder(const void *d_ptr, const int
|
||||
v.y() = (2 * bit_1 - 1) * d;
|
||||
}
|
||||
|
||||
static __dpct_inline__ void dequantize_q1_0(const void *vx, const int64_t ib,
|
||||
const int iqs, dfloat2 &v) {
|
||||
const block_q1_0 * x = (const block_q1_0 *) vx;
|
||||
const dfloat d = x[ib].d;
|
||||
|
||||
const int bit_index_0 = iqs + 0;
|
||||
const int bit_index_1 = iqs + 1;
|
||||
|
||||
const int bit_0 = (x[ib].qs[bit_index_0 / 8] >> (bit_index_0 % 8)) & 1;
|
||||
const int bit_1 = (x[ib].qs[bit_index_1 / 8] >> (bit_index_1 % 8)) & 1;
|
||||
|
||||
v.x() = (2 * bit_0 - 1) * d;
|
||||
v.y() = (2 * bit_1 - 1) * d;
|
||||
}
|
||||
|
||||
static __dpct_inline__ void dequantize_q4_1(const void *vx, const int64_t ib,
|
||||
const int iqs, dfloat2 &v) {
|
||||
const block_q4_1 * x = (const block_q4_1 *) vx;
|
||||
@@ -140,6 +156,39 @@ static __dpct_inline__ void dequantize_q4_K(const void *vx, const int64_t ib,
|
||||
#endif
|
||||
}
|
||||
|
||||
static __dpct_inline__ void dequantize_q4_K_f32(const void *vx, const int64_t ib,
|
||||
const int iqs, float &v0, float &v1) {
|
||||
#if QK_K == 256
|
||||
const block_q4_K * x = (const block_q4_K *) vx;
|
||||
const sycl::half2 dm = x[ib].dm;
|
||||
const float dall = dm[0];
|
||||
const float dmin = dm[1];
|
||||
|
||||
auto dequantize_one = [&](const int idx) -> float {
|
||||
const int il = idx / 64;
|
||||
const int in = idx % 64;
|
||||
const int is = 2 * il + (in >= 32 ? 1 : 0);
|
||||
const int qsi = 32 * il + (in & 31);
|
||||
|
||||
uint8_t sc;
|
||||
uint8_t m;
|
||||
get_scale_min_k4(is, x[ib].scales, sc, m);
|
||||
|
||||
const float d = dall * sc;
|
||||
const float mn = dmin * m;
|
||||
const uint8_t q = x[ib].qs[qsi];
|
||||
const uint8_t qv = (in >= 32) ? (q >> 4) : (q & 0xF);
|
||||
|
||||
return d * qv - mn;
|
||||
};
|
||||
|
||||
v0 = dequantize_one(iqs + 0);
|
||||
v1 = dequantize_one(iqs + 1);
|
||||
#else
|
||||
GGML_ABORT("Q4_K dequantize not supported for QK_K != 256");
|
||||
#endif
|
||||
}
|
||||
|
||||
static __dpct_inline__ void dequantize_q2_K(const void *vx, const int64_t ib,
|
||||
const int iqs, dfloat2 &v) {
|
||||
#if QK_K == 256
|
||||
@@ -159,7 +208,7 @@ static __dpct_inline__ void dequantize_q2_K(const void *vx, const int64_t ib,
|
||||
const float d = dall * (sc & 0xF);
|
||||
const float m = dmin * (sc >> 4);
|
||||
|
||||
return sycl::fma((dfloat) ((q >> (2 * g)) & 3), (dfloat) d, (dfloat) (-m));
|
||||
return (dfloat) d * (dfloat) ((q >> (2 * g)) & 3) - (dfloat) m;
|
||||
};
|
||||
|
||||
v.x() = dequantize_one(iqs + 0);
|
||||
@@ -169,6 +218,35 @@ static __dpct_inline__ void dequantize_q2_K(const void *vx, const int64_t ib,
|
||||
#endif
|
||||
}
|
||||
|
||||
static __dpct_inline__ void dequantize_q2_K_f32(const void *vx, const int64_t ib,
|
||||
const int iqs, float &v0, float &v1) {
|
||||
#if QK_K == 256
|
||||
const block_q2_K * x = (const block_q2_K *) vx;
|
||||
const float dall = x[ib].dm[0];
|
||||
const float dmin = x[ib].dm[1];
|
||||
|
||||
auto dequantize_one = [&](const int idx) -> float {
|
||||
const int n = idx / 128;
|
||||
const int r = idx % 128;
|
||||
const int g = r / 32;
|
||||
const int l = r % 32;
|
||||
const int is = 8 * n + l / 16;
|
||||
|
||||
const uint8_t q = x[ib].qs[32 * n + l];
|
||||
const uint8_t sc = x[ib].scales[is + 2 * g];
|
||||
const float d = dall * (sc & 0xF);
|
||||
const float m = dmin * (sc >> 4);
|
||||
|
||||
return d * ((q >> (2 * g)) & 3) - m;
|
||||
};
|
||||
|
||||
v0 = dequantize_one(iqs + 0);
|
||||
v1 = dequantize_one(iqs + 1);
|
||||
#else
|
||||
GGML_ABORT("Q2_K dequantize not supported for QK_K != 256");
|
||||
#endif
|
||||
}
|
||||
|
||||
static __dpct_inline__ void dequantize_q3_K(const void *vx, const int64_t ib,
|
||||
const int iqs, dfloat2 &v) {
|
||||
#if QK_K == 256
|
||||
@@ -242,6 +320,42 @@ static __dpct_inline__ void dequantize_q5_K(const void *vx, const int64_t ib,
|
||||
#endif
|
||||
}
|
||||
|
||||
static __dpct_inline__ void dequantize_q5_K_f32(const void *vx, const int64_t ib,
|
||||
const int iqs, float &v0, float &v1) {
|
||||
#if QK_K == 256
|
||||
const block_q5_K * x = (const block_q5_K *) vx;
|
||||
const float dall = x[ib].dm[0];
|
||||
const float dmin = x[ib].dm[1];
|
||||
|
||||
auto dequantize_one = [&](const int idx) -> float {
|
||||
const int il = idx / 64;
|
||||
const int in = idx % 64;
|
||||
const int is = 2 * il + (in >= 32 ? 1 : 0);
|
||||
const int ir = (in & 31) / 2;
|
||||
const int iq = in & 1;
|
||||
|
||||
const uint8_t q = x[ib].qs[32 * il + 2 * ir + iq];
|
||||
const uint8_t h = x[ib].qh[2 * ir + iq];
|
||||
const uint8_t qv = (in >= 32) ? (q >> 4) : (q & 0xF);
|
||||
|
||||
uint8_t sc;
|
||||
uint8_t m;
|
||||
get_scale_min_k4(is, x[ib].scales, sc, m);
|
||||
|
||||
const float d = dall * sc;
|
||||
const float mn = dmin * m;
|
||||
const uint8_t hm = 1 << (2 * il + (in >= 32 ? 1 : 0));
|
||||
|
||||
return (qv + ((h & hm) ? 16 : 0)) * d - mn;
|
||||
};
|
||||
|
||||
v0 = dequantize_one(iqs + 0);
|
||||
v1 = dequantize_one(iqs + 1);
|
||||
#else
|
||||
GGML_ABORT("Q5_K dequantize not supported for QK_K != 256");
|
||||
#endif
|
||||
}
|
||||
|
||||
static __dpct_inline__ void dequantize_q6_K(const void *vx, const int64_t ib,
|
||||
const int iqs, dfloat2 &v) {
|
||||
#if QK_K == 256
|
||||
@@ -296,21 +410,6 @@ static __dpct_inline__ void dequantize_mxfp4(const void *vx, const int64_t ib,
|
||||
v.y() = d * kvalues_mxfp4[q >> 4] * 0.5f;
|
||||
}
|
||||
|
||||
static __dpct_inline__ void dequantize_q1_0(const void *vx, const int64_t ib,
|
||||
const int iqs, dfloat2 &v) {
|
||||
const block_q1_0 * x = (const block_q1_0 *) vx;
|
||||
const dfloat d = x[ib].d;
|
||||
|
||||
const int bit_index_0 = iqs + 0;
|
||||
const int bit_index_1 = iqs + 1;
|
||||
|
||||
const int bit_0 = (x[ib].qs[bit_index_0 / 8] >> (bit_index_0 % 8)) & 1;
|
||||
const int bit_1 = (x[ib].qs[bit_index_1 / 8] >> (bit_index_1 % 8)) & 1;
|
||||
|
||||
v.x() = (2 * bit_0 - 1) * d;
|
||||
v.y() = (2 * bit_1 - 1) * d;
|
||||
}
|
||||
|
||||
static __dpct_inline__ void dequantize_nvfp4(const void *vx, const int64_t ib,
|
||||
const int iqs, dfloat2 &v) {
|
||||
const block_nvfp4 & xb = ((const block_nvfp4 *) vx)[ib];
|
||||
|
||||
@@ -247,6 +247,17 @@ static __dpct_inline__ T op_leaky_relu(T x, float negative_slope) {
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static __dpct_inline__ T op_xielu(T x, float alpha_n, float alpha_p, float beta, float eps) {
|
||||
const float xi = static_cast<float>(x);
|
||||
const float gate_pos = (xi > 0.0f);
|
||||
const float y_pos = alpha_p * xi * xi + beta * xi;
|
||||
const float min_v_eps = sycl::fmin(xi, eps);
|
||||
const float y_neg = (sycl::expm1(min_v_eps) - xi) * alpha_n + beta * xi;
|
||||
const float out = gate_pos * y_pos + (1.0f - gate_pos) * y_neg;
|
||||
return static_cast<T>(out);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static __dpct_inline__ T op_sqr(T x) {
|
||||
return x * x;
|
||||
@@ -359,6 +370,13 @@ static void unary_op_leaky_relu_kernel(const T * x, T * dst, const int k, float
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void unary_op_xielu_kernel(const T * x, T * dst, const int k, float alpha_n, float alpha_p, float beta, float eps, const sycl::nd_item<1> &item_ct1) {
|
||||
SYCL_GLOBAL_ID_LOOP(k, item_ct1) {
|
||||
dst[i] = op_xielu(x[i], alpha_n, alpha_p, beta, eps);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void unary_op_sqr_kernel(const T * x, T * dst, const int k, const sycl::nd_item<1> &item_ct1) {
|
||||
SYCL_GLOBAL_ID_LOOP(k, item_ct1) {
|
||||
@@ -836,6 +854,23 @@ static inline void ggml_sycl_op_clamp(ggml_backend_sycl_context & ctx, ggml_tens
|
||||
}, min_val, max_val);
|
||||
}
|
||||
|
||||
static inline void ggml_sycl_op_xielu(ggml_backend_sycl_context & ctx, ggml_tensor * dst) {
|
||||
const float alpha_n = ggml_get_op_params_f32(dst, 1);
|
||||
const float alpha_p = ggml_get_op_params_f32(dst, 2);
|
||||
const float beta = ggml_get_op_params_f32(dst, 3);
|
||||
const float eps = ggml_get_op_params_f32(dst, 4);
|
||||
ggml_sycl_detail::dispatch_ggml_sycl_op_unary(ctx, dst,
|
||||
[](const auto* src, auto* dst_ptr, int k_elements, queue_ptr stream, float alpha_n_arg, float alpha_p_arg, float beta_arg, float eps_arg) {
|
||||
const int num_blocks = ceil_div(k_elements, SYCL_RELU_BLOCK_SIZE);
|
||||
stream->parallel_for(
|
||||
sycl::nd_range<1>(sycl::range<1>(num_blocks) * sycl::range<1>(SYCL_RELU_BLOCK_SIZE),
|
||||
sycl::range<1>(SYCL_RELU_BLOCK_SIZE)),
|
||||
[=](sycl::nd_item<1> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]] {
|
||||
unary_op_xielu_kernel(src, dst_ptr, k_elements, alpha_n_arg, alpha_p_arg, beta_arg, eps_arg, item_ct1);
|
||||
});
|
||||
}, alpha_n, alpha_p, beta, eps);
|
||||
}
|
||||
|
||||
static inline void ggml_sycl_op_floor(ggml_backend_sycl_context & ctx, ggml_tensor * dst) {
|
||||
ggml_sycl_detail::ggml_sycl_op_unary(ctx, dst, [](auto x) {
|
||||
return op_floor(x);
|
||||
@@ -1153,6 +1188,11 @@ void ggml_sycl_clamp(ggml_backend_sycl_context & ctx, ggml_tensor * dst) {
|
||||
ggml_sycl_op_clamp(ctx, dst);
|
||||
}
|
||||
|
||||
void ggml_sycl_xielu(ggml_backend_sycl_context & ctx, ggml_tensor * dst) {
|
||||
scope_op_debug_print scope_dbg_print(__func__, dst, /*num_src=*/1);
|
||||
ggml_sycl_op_xielu(ctx, dst);
|
||||
}
|
||||
|
||||
void ggml_sycl_sgn(ggml_backend_sycl_context & ctx, ggml_tensor * dst) {
|
||||
scope_op_debug_print scope_dbg_print(__func__, dst, /*num_src=*/1);
|
||||
ggml_sycl_op_sgn(ctx, dst);
|
||||
|
||||
@@ -75,6 +75,8 @@ void ggml_sycl_sqr(ggml_backend_sycl_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_sycl_clamp(ggml_backend_sycl_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_sycl_xielu(ggml_backend_sycl_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_sycl_sgn(ggml_backend_sycl_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_sycl_abs(ggml_backend_sycl_context & ctx, ggml_tensor * dst);
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "fattn-onednn.hpp"
|
||||
#include "fattn-tile.hpp"
|
||||
|
||||
// set minimum query length to treat as prefill (32)
|
||||
#define GGML_SYCL_FA_ONEDNN_MIN_Q 32
|
||||
|
||||
bool ggml_sycl_flash_attn_ext_onednn_supported(const ggml_tensor * dst) {
|
||||
#if !GGML_SYCL_DNNL
|
||||
GGML_UNUSED(dst);
|
||||
return false;
|
||||
#else
|
||||
if (!g_ggml_sycl_fa_onednn) {
|
||||
return false;
|
||||
}
|
||||
// Battlemage (Xe2) only, for now. On other Intel archs oneDNN's fused SDPA returns wrong results
|
||||
// for some shapes (e.g. head_dim=64 on Arc / xe_hpg) -- an oneDNN bug tracked upstream at
|
||||
// https://github.com/uxlfoundation/oneDNN/issues/5510. Remove this hardware limitation once that
|
||||
// is fixed; until then non-BMG archs fall back to the existing FA kernel.
|
||||
const gpu_arch arch = ggml_sycl_info().devices[ggml_sycl_get_device()].hw_info.arch;
|
||||
if (arch != gpu_arch::intel_gpu_bmg_g21 && arch != gpu_arch::intel_gpu_bmg_g31) {
|
||||
return false;
|
||||
}
|
||||
const ggml_tensor * Q = dst->src[0];
|
||||
const ggml_tensor * K = dst->src[1];
|
||||
const ggml_tensor * V = dst->src[2];
|
||||
const ggml_tensor * mask = dst->src[3];
|
||||
const ggml_tensor * sinks = dst->src[4];
|
||||
|
||||
// gate for f16 KV only for now
|
||||
// need to implement quantized KV
|
||||
if (K->type != GGML_TYPE_F16 || V->type != GGML_TYPE_F16) {
|
||||
return false;
|
||||
}
|
||||
// gate for the following cases
|
||||
// 1. if the oneDNN graph Add node has no input --> skip
|
||||
// 2. types other than f16 need different logical_tensor declaration
|
||||
// 3. the mask must be shape [1, 1, q, seq]
|
||||
// 4. sinks: excludes attention sink (Xiao et al., 2024) that can't be modeled by oneDNN graph
|
||||
if (!mask || mask->type != GGML_TYPE_F16 || mask->ne[2] != 1 || mask->ne[3] != 1 || sinks) {
|
||||
return false;
|
||||
}
|
||||
float max_bias = 0.0f, logit_softcap = 0.0f;
|
||||
memcpy(&max_bias, (const float *) dst->op_params + 1, sizeof(float));
|
||||
memcpy(&logit_softcap, (const float *) dst->op_params + 2, sizeof(float));
|
||||
if (max_bias != 0.0f || logit_softcap != 0.0f) {
|
||||
return false;
|
||||
}
|
||||
// K and V must share head_dim: the SDPA graph uses a single `d` for both.
|
||||
const int64_t d = K->ne[0];
|
||||
if (V->ne[0] != d || Q->ne[3] != 1) {
|
||||
return false;
|
||||
}
|
||||
// GQA must divide evenly.
|
||||
if (K->ne[2] == 0 || Q->ne[2] % K->ne[2] != 0) {
|
||||
return false;
|
||||
}
|
||||
// Prefill only.
|
||||
if (Q->ne[1] < GGML_SYCL_FA_ONEDNN_MIN_Q) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if GGML_SYCL_DNNL
|
||||
|
||||
#include "dnnl.hpp"
|
||||
#include "dnnl_sycl.hpp"
|
||||
#include "oneapi/dnnl/dnnl_graph.hpp" // graph API lives only under oneapi/dnnl/, not at the include root
|
||||
|
||||
using namespace dnnl;
|
||||
using namespace dnnl::graph;
|
||||
|
||||
// strided src (f16 or f32) -> contiguous f16 [ne0,ne1,ne2,ne3] (ne0 innermost). nb* are BYTE strides.
|
||||
template <typename src_t>
|
||||
static void cont_to_f16_sycl(const char * src, sycl::half * dst,
|
||||
int64_t ne0, int64_t ne1, int64_t ne2, int64_t ne3,
|
||||
size_t nb1, size_t nb2, size_t nb3, dpct::queue_ptr stream) {
|
||||
const int64_t n = ne0 * ne1 * ne2 * ne3;
|
||||
stream->parallel_for(sycl::range<1>(n), [=](sycl::id<1> ix) {
|
||||
const int64_t gid = ix[0];
|
||||
int64_t i = gid;
|
||||
const int64_t i0 = i % ne0; i /= ne0;
|
||||
const int64_t i1 = i % ne1; i /= ne1;
|
||||
const int64_t i2 = i % ne2; const int64_t i3 = i / ne2;
|
||||
const src_t * p = (const src_t *) (src + i1 * nb1 + i2 * nb2 + i3 * nb3) + i0;
|
||||
dst[gid] = (sycl::half) (*p);
|
||||
});
|
||||
}
|
||||
|
||||
// oneDNN SDPA out (f16 contiguous [mb,H,q,d]) -> ggml dst (f32 [head_dim,H,n_tok,mb], contiguous).
|
||||
static void permute_sdpa_out_sycl(const sycl::half * out, float * dst,
|
||||
int64_t mb, int64_t H, int64_t q, int64_t d, dpct::queue_ptr stream) {
|
||||
const int64_t n = mb * H * q * d;
|
||||
stream->parallel_for(sycl::range<1>(n), [=](sycl::id<1> ix) {
|
||||
const int64_t gid = ix[0];
|
||||
int64_t i = gid;
|
||||
const int64_t e = i % d; i /= d;
|
||||
const int64_t t = i % q; i /= q;
|
||||
const int64_t h = i % H; const int64_t b = i / H;
|
||||
dst[e + h * d + t * d * H + b * d * H * q] = (float) out[gid];
|
||||
});
|
||||
}
|
||||
|
||||
struct sdpa_partition {
|
||||
compiled_partition cp;
|
||||
std::vector<logical_tensor> ins;
|
||||
logical_tensor out;
|
||||
size_t id_q = 0, id_k = 0, id_v = 0, id_scale = 0, id_mask = 0;
|
||||
bool ok = false;
|
||||
};
|
||||
|
||||
// Build + compile the contiguous-input GQA SDPA graph (MatMul->Divide->Add->SoftMax->MatMul), f32 out.
|
||||
// Mirrors the hardware-verified scratch/onednn_sdpa_probe.cpp build_gqa (partitions=1, sdp_primitive_kernel_t).
|
||||
static sdpa_partition build_sdpa(const engine & eng, int H, int Hkv, int q, int seq, int d) {
|
||||
using ltype = logical_tensor::layout_type;
|
||||
using dt = logical_tensor::data_type;
|
||||
using ldims = logical_tensor::dims;
|
||||
const dt fi = dt::f32, t = dt::f16;
|
||||
const int rep = H / Hkv;
|
||||
const ldims q_sz = {1, Hkv, rep, q, d}, kv_sz = {1, Hkv, 1, seq, d}, s_sz = {1, Hkv, rep, q, seq},
|
||||
sc = {1, 1, 1, 1, 1}, msk = {1, 1, 1, q, seq}, o_sz = {1, Hkv, rep, q, d};
|
||||
int64_t id = 0;
|
||||
sdpa_partition E;
|
||||
|
||||
auto query = logical_tensor(id++, t, q_sz, ltype::strided);
|
||||
auto key = logical_tensor(id++, t, kv_sz, ltype::strided);
|
||||
auto score = logical_tensor(id++, fi, s_sz, ltype::strided);
|
||||
auto bmm1 = op(id++, op::kind::MatMul, "bmm1");
|
||||
bmm1.set_attr<bool>(op::attr::transpose_b, true); // key is [.., seq, d]
|
||||
bmm1.add_inputs({query, key}); bmm1.add_outputs({score});
|
||||
|
||||
auto scale = logical_tensor(id++, t, sc, ltype::strided);
|
||||
auto scaled = logical_tensor(id++, fi, s_sz, ltype::strided);
|
||||
auto sdiv = op(id++, op::kind::Divide, "scale_div"); // score / (1/kq_scale) == score * kq_scale
|
||||
sdiv.add_inputs({score, scale}); sdiv.add_outputs({scaled});
|
||||
|
||||
auto mask = logical_tensor(id++, t, msk, ltype::strided);
|
||||
auto masked = logical_tensor(id++, fi, s_sz, ltype::strided);
|
||||
auto madd = op(id++, op::kind::Add, "mask_add");
|
||||
madd.add_inputs({scaled, mask}); madd.add_outputs({masked});
|
||||
|
||||
auto probs = logical_tensor(id++, t, s_sz, ltype::strided);
|
||||
auto smax = op(id++, op::kind::SoftMax, "softmax");
|
||||
smax.set_attr<int64_t>(op::attr::axis, -1);
|
||||
smax.set_attr<std::string>(op::attr::mode, "inf_as_zero");
|
||||
smax.add_inputs({masked}); smax.add_outputs({probs});
|
||||
|
||||
auto value = logical_tensor(id++, t, kv_sz, ltype::strided);
|
||||
// f16 output is REQUIRED to hit sdp_primitive_kernel_t (the systolic micro-kernel); an f32 output
|
||||
// falls to larger_partition_kernel_t which materializes N^2 (confirmed: scratch/onednn_sdpa_kernel_probe.cpp).
|
||||
// converted to the f32 ggml dst in the permute below.
|
||||
auto output = logical_tensor(id++, t, o_sz, ltype::strided); // f16 contiguous [mb,Hkv,rep,q,d]
|
||||
auto bmm2 = op(id++, op::kind::MatMul, "bmm2");
|
||||
bmm2.add_inputs({probs, value}); bmm2.add_outputs({output});
|
||||
|
||||
dnnl::graph::graph g(eng.get_kind());
|
||||
g.add_op(bmm1); g.add_op(sdiv); g.add_op(madd); g.add_op(smax); g.add_op(bmm2);
|
||||
g.finalize();
|
||||
|
||||
auto parts = g.get_partitions();
|
||||
if (parts.size() != 1 || !parts[0].is_supported()) {
|
||||
return E; // ok stays false -> caller falls back to TILE
|
||||
}
|
||||
E.ins = parts[0].get_input_ports();
|
||||
E.out = parts[0].get_output_ports()[0];
|
||||
E.cp = parts[0].compile(E.ins, {E.out}, eng);
|
||||
E.out = E.cp.query_logical_tensor(E.out.get_id());
|
||||
E.id_q = query.get_id(); E.id_k = key.get_id(); E.id_v = value.get_id();
|
||||
E.id_scale = scale.get_id(); E.id_mask = mask.get_id();
|
||||
E.ok = true;
|
||||
return E;
|
||||
}
|
||||
|
||||
void ggml_sycl_flash_attn_ext_onednn(ggml_backend_sycl_context & ctx, ggml_tensor * dst) try {
|
||||
const ggml_tensor * Q = dst->src[0];
|
||||
const ggml_tensor * K = dst->src[1];
|
||||
const ggml_tensor * V = dst->src[2];
|
||||
const ggml_tensor * mask = dst->src[3];
|
||||
|
||||
const int64_t d = K->ne[0]; // head_dim
|
||||
const int64_t seq = K->ne[1]; // n_kv
|
||||
const int64_t Hkv = K->ne[2]; // n_head_kv
|
||||
const int64_t H = Q->ne[2]; // n_head
|
||||
const int64_t q = Q->ne[1]; // n_tok
|
||||
const int64_t mb = Q->ne[3]; // batch (== 1, gated)
|
||||
|
||||
float kq_scale = 1.0f;
|
||||
memcpy(&kq_scale, (const float *) dst->op_params + 0, sizeof(float));
|
||||
|
||||
dpct::queue_ptr stream = ctx.stream();
|
||||
dnnl::engine eng = ctx.engine_dnnl(stream);
|
||||
dnnl::stream strm = ctx.stream_dnnl(stream);
|
||||
|
||||
// cont/cast inputs to contiguous f16 (head-major) -- the layout the fast systolic path wants.
|
||||
ggml_sycl_pool_alloc<sycl::half> Qf(ctx.pool(), (size_t) H * q * d);
|
||||
ggml_sycl_pool_alloc<sycl::half> Kf(ctx.pool(), (size_t) Hkv * seq * d);
|
||||
ggml_sycl_pool_alloc<sycl::half> Vf(ctx.pool(), (size_t) Hkv * seq * d);
|
||||
cont_to_f16_sycl<float> ((const char *) Q->data, Qf.get(), d, q, H, mb, Q->nb[1], Q->nb[2], Q->nb[3], stream);
|
||||
cont_to_f16_sycl<sycl::half>((const char *) K->data, Kf.get(), d, seq, Hkv, mb, K->nb[1], K->nb[2], K->nb[3], stream);
|
||||
cont_to_f16_sycl<sycl::half>((const char *) V->data, Vf.get(), d, seq, Hkv, mb, V->nb[1], V->nb[2], V->nb[3], stream);
|
||||
|
||||
// divide-by-(1/scale) reproduces ggml's score *= kq_scale on the proven probe graph.
|
||||
const sycl::half scale_h = (sycl::half) (1.0f / kq_scale);
|
||||
ggml_sycl_pool_alloc<sycl::half> scbuf(ctx.pool(), 1);
|
||||
stream->memcpy(scbuf.get(), &scale_h, sizeof(sycl::half));
|
||||
|
||||
ggml_sycl_pool_alloc<sycl::half> outf(ctx.pool(), (size_t) H * q * d); // f16 contiguous SDPA out [mb,H,q,d]
|
||||
|
||||
// compile once per (device, shape), reuse across layers/calls.
|
||||
static std::unordered_map<std::string, sdpa_partition> cache;
|
||||
char keyb[96];
|
||||
snprintf(keyb, sizeof(keyb), "%d:%lld:%lld:%lld:%lld:%lld", ggml_sycl_get_device(),
|
||||
(long long) H, (long long) Hkv, (long long) q, (long long) seq, (long long) d);
|
||||
auto it = cache.find(keyb);
|
||||
if (it == cache.end()) {
|
||||
it = cache.emplace(keyb, build_sdpa(eng, (int) H, (int) Hkv, (int) q, (int) seq, (int) d)).first;
|
||||
}
|
||||
sdpa_partition & E = it->second;
|
||||
// _supported() is authoritative: if it accepted this op the partition must build.
|
||||
// A failure here is a gap in _supported() -- surface it, don't mask it with a fallback.
|
||||
GGML_ASSERT(E.ok && "oneDNN SDPA partition failed to build for a _supported() shape");
|
||||
|
||||
auto id2ptr = [&](size_t r) -> void * {
|
||||
if (r == E.id_q) return Qf.get();
|
||||
if (r == E.id_k) return Kf.get();
|
||||
if (r == E.id_v) return Vf.get();
|
||||
if (r == E.id_scale) return scbuf.get();
|
||||
if (r == E.id_mask) return (void *) mask->data;
|
||||
return nullptr;
|
||||
};
|
||||
std::vector<tensor> ti;
|
||||
ti.reserve(E.ins.size());
|
||||
for (auto & lt : E.ins) {
|
||||
ti.emplace_back(lt, eng, id2ptr(lt.get_id()));
|
||||
}
|
||||
tensor to(E.out, eng, outf.get());
|
||||
E.cp.execute(strm, ti, {to});
|
||||
|
||||
permute_sdpa_out_sycl(outf.get(), (float *) dst->data, mb, H, q, d, stream);
|
||||
// Single device: no sync is required, and actually PP perf is ~6% > wait_and_throw() (tested on llama-3.1-8b & qwen3.6-27b, both Q8_0, with Arc B70).
|
||||
// Any future multi-GPU refactor MUST re-measure this single-device path and keep the best
|
||||
// single-device PP speed. Otherwise (multiple devices/streams can race the reuse):
|
||||
if (ggml_sycl_info().device_count > 1) {
|
||||
// cont_to_f16 -> oneDNN execute -> permute is async on this stream, but the
|
||||
// pool_alloc*s above free their device buffers at host return. Without this wait the next
|
||||
// scheduler op re-acquires those bytes while the GPU is still computing the SDPA, turning
|
||||
// it into garbage and collapsing multi-turn output to a single repeated token ("GGGGG...").
|
||||
stream->wait_and_throw();
|
||||
}
|
||||
}
|
||||
catch (const std::exception & e) {
|
||||
// any oneDNN/SYCL failure is non-fatal: fall back to the existing kernel (strictly additive).
|
||||
GGML_LOG_WARN("%s: oneDNN SDPA failed (%s); falling back to TILE kernel\n", __func__, e.what());
|
||||
ggml_sycl_flash_attn_ext_tile(ctx, dst);
|
||||
}
|
||||
|
||||
#endif // GGML_SYCL_DNNL
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef GGML_SYCL_FATTN_ONEDNN_HPP
|
||||
#define GGML_SYCL_FATTN_ONEDNN_HPP
|
||||
|
||||
#include "common.hpp"
|
||||
|
||||
// Static-only check: fused-XMX oneDNN Graph SDPA path==flash-attn op
|
||||
// (f16 KV, no softcap/ALiBi, single stream, tuned head_dim, prefill-sized q.)
|
||||
bool ggml_sycl_flash_attn_ext_onednn_supported(const ggml_tensor * dst);
|
||||
|
||||
// Run flash attention through oneDNN's fused xmx SDPA
|
||||
// execute the cached SDPA partition, write the f32 dst. Falls back to the TILE kernel on any failure.
|
||||
void ggml_sycl_flash_attn_ext_onednn(ggml_backend_sycl_context & ctx, ggml_tensor * dst);
|
||||
|
||||
#endif // GGML_SYCL_FATTN_ONEDNN_HPP
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "fattn-tile.hpp"
|
||||
#include "fattn-vec.hpp"
|
||||
#include "fattn.hpp"
|
||||
#include "fattn-onednn.hpp"
|
||||
|
||||
|
||||
#define FATTN_VEC_CASE(D, type_K, type_V) \
|
||||
@@ -96,6 +97,7 @@ static void ggml_sycl_flash_attn_ext_vec(ggml_backend_sycl_context & ctx, ggml_t
|
||||
enum best_fattn_kernel {
|
||||
BEST_FATTN_KERNEL_NONE = 0,
|
||||
BEST_FATTN_KERNEL_VEC = 100,
|
||||
BEST_FATTN_KERNEL_ONEDNN = 150, // added enum for onednn==150
|
||||
BEST_FATTN_KERNEL_TILE = 200,
|
||||
};
|
||||
|
||||
@@ -189,7 +191,11 @@ static best_fattn_kernel ggml_sycl_get_best_fattn_kernel(const int device, const
|
||||
// For small batch sizes the vector kernel may be preferable over the kernels optimized for large batch sizes:
|
||||
const bool can_use_vector_kernel = Q->ne[0] <= 512 && Q->ne[0] % 64 == 0 && K->ne[1] % FATTN_KQ_STRIDE == 0;
|
||||
|
||||
// Todo: Use the XMX kernel if possible:
|
||||
// Fused-XMX path: oneDNN Graph SDPA (flash attention). Strictly
|
||||
// additive -- taken only when statically supported, otherwise falls through to VEC/TILE below.
|
||||
if (ggml_sycl_flash_attn_ext_onednn_supported(dst)) {
|
||||
return BEST_FATTN_KERNEL_ONEDNN;
|
||||
}
|
||||
|
||||
// If there are no tensor cores available, use the generic tile kernel:
|
||||
if (can_use_vector_kernel) {
|
||||
@@ -213,6 +219,13 @@ void ggml_sycl_flash_attn_ext(ggml_backend_sycl_context & ctx, ggml_tensor * dst
|
||||
switch (ggml_sycl_get_best_fattn_kernel(ggml_sycl_get_device(), dst)) {
|
||||
case BEST_FATTN_KERNEL_NONE:
|
||||
GGML_ABORT("Not support Flash-Attention");
|
||||
case BEST_FATTN_KERNEL_ONEDNN:
|
||||
// guarded: ggml_sycl_flash_attn_ext_onednn() is only defined under GGML_SYCL_DNNL;
|
||||
// the reference must be compiled out here or the GGML_SYCL_DNNL=0 build fails to link.
|
||||
#if GGML_SYCL_DNNL
|
||||
ggml_sycl_flash_attn_ext_onednn(ctx, dst);
|
||||
#endif
|
||||
break;
|
||||
case BEST_FATTN_KERNEL_TILE:
|
||||
ggml_sycl_flash_attn_ext_tile(ctx, dst);
|
||||
break;
|
||||
|
||||
@@ -60,6 +60,50 @@ static void k_get_rows(
|
||||
dst_row[iybs + iqs + y_offset] = v.y();
|
||||
}
|
||||
|
||||
template<int qk, int qr, dequantize_kernel_f32_t dequantize_kernel, typename dst_t>
|
||||
static void k_get_rows_f32(
|
||||
const void * src0, const int32_t * src1, dst_t * dst,
|
||||
int64_t ne00,
|
||||
int64_t ne12,
|
||||
size_t s1, size_t s2, size_t s3,
|
||||
size_t nb01, size_t nb02, size_t nb03,
|
||||
size_t s10, size_t s11, size_t s12,
|
||||
const sycl::nd_item<3> &item_ct1) {
|
||||
|
||||
const int i00 = (item_ct1.get_group(2) * item_ct1.get_local_range(2) +
|
||||
item_ct1.get_local_id(2)) *
|
||||
2;
|
||||
const int i10 = item_ct1.get_local_range(1) * item_ct1.get_group(1) +
|
||||
item_ct1.get_local_id(1);
|
||||
const int i11 = (item_ct1.get_group(0) * item_ct1.get_local_range(0) +
|
||||
item_ct1.get_local_id(0)) /
|
||||
ne12;
|
||||
const int i12 = (item_ct1.get_group(0) * item_ct1.get_local_range(0) +
|
||||
item_ct1.get_local_id(0)) %
|
||||
ne12;
|
||||
|
||||
if (i00 >= ne00) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int i01 = src1[i10*s10 + i11*s11 + i12*s12];
|
||||
|
||||
dst_t * dst_row = dst + i10*s1 + i11*s2 + i12*s3;
|
||||
const void * src0_row = (const char *)src0 + i01*nb01 + i11*nb02 + i12*nb03;
|
||||
|
||||
const int ib = i00/qk;
|
||||
const int iqs = (i00%qk)/qr;
|
||||
const int iybs = i00 - i00%qk;
|
||||
const int y_offset = qr == 1 ? 1 : qk/2;
|
||||
|
||||
float v0;
|
||||
float v1;
|
||||
dequantize_kernel(src0_row, ib, iqs, v0, v1);
|
||||
|
||||
dst_row[iybs + iqs + 0] = (dst_t) v0;
|
||||
dst_row[iybs + iqs + y_offset] = (dst_t) v1;
|
||||
}
|
||||
|
||||
template<typename src0_t, typename dst_t>
|
||||
static void k_get_rows_float(
|
||||
const src0_t * src0, const int32_t * src1, dst_t * dst,
|
||||
@@ -129,6 +173,39 @@ static void get_rows_sycl(ggml_backend_sycl_context & ctx, const ggml_tensor *sr
|
||||
GGML_UNUSED(ctx);
|
||||
}
|
||||
|
||||
template <int qk, int qr, dequantize_kernel_f32_t dq>
|
||||
static void get_rows_sycl_f32(ggml_backend_sycl_context & ctx, const ggml_tensor *src0, const ggml_tensor *src1,
|
||||
ggml_tensor *dst, const void *src0_dd,
|
||||
const int32_t *src1_dd, float *dst_dd,
|
||||
queue_ptr stream) {
|
||||
|
||||
GGML_TENSOR_BINARY_OP_LOCALS
|
||||
|
||||
const sycl::range<3> block_dims(1, 1, SYCL_GET_ROWS_BLOCK_SIZE);
|
||||
const int block_num_x = (ne00 + 2*SYCL_GET_ROWS_BLOCK_SIZE - 1) / (2*SYCL_GET_ROWS_BLOCK_SIZE);
|
||||
const sycl::range<3> block_nums(ne11 * ne12, ne10, block_num_x);
|
||||
|
||||
const size_t s1 = nb1 / ggml_element_size(dst);
|
||||
const size_t s2 = nb2 / ggml_element_size(dst);
|
||||
const size_t s3 = nb3 / ggml_element_size(dst);
|
||||
|
||||
const size_t s10 = nb10 / ggml_element_size(src1);
|
||||
const size_t s11 = nb11 / ggml_element_size(src1);
|
||||
const size_t s12 = nb12 / ggml_element_size(src1);
|
||||
|
||||
GGML_ASSERT(ne00 % 2 == 0);
|
||||
|
||||
stream->parallel_for(sycl::nd_range<3>(block_nums * block_dims, block_dims),
|
||||
[=](sycl::nd_item<3> item_ct1) {
|
||||
k_get_rows_f32<qk, qr, dq>(
|
||||
src0_dd, src1_dd, dst_dd, ne00, ne12, s1, s2,
|
||||
s3, nb01, nb02, nb03, s10, s11, s12, item_ct1);
|
||||
});
|
||||
|
||||
GGML_UNUSED(dst);
|
||||
GGML_UNUSED(ctx);
|
||||
}
|
||||
|
||||
template <typename src0_t, typename dst_t>
|
||||
static void get_rows_sycl_float(ggml_backend_sycl_context & ctx, const ggml_tensor *src0,
|
||||
const ggml_tensor *src1, ggml_tensor *dst,
|
||||
@@ -244,7 +321,7 @@ void ggml_sycl_op_get_rows(ggml_backend_sycl_context & ctx, ggml_tensor * dst) {
|
||||
src1_i32, (float *)dst->data, ctx.stream());
|
||||
break;
|
||||
case GGML_TYPE_Q2_K:
|
||||
get_rows_sycl<QK_K, 1, dequantize_q2_K>(ctx, dst->src[0], dst->src[1], dst, (const float *)dst->src[0]->data,
|
||||
get_rows_sycl_f32<QK_K, 1, dequantize_q2_K_f32>(ctx, dst->src[0], dst->src[1], dst, (const float *)dst->src[0]->data,
|
||||
src1_i32, (float *)dst->data, ctx.stream());
|
||||
break;
|
||||
case GGML_TYPE_Q3_K:
|
||||
@@ -260,7 +337,7 @@ void ggml_sycl_op_get_rows(ggml_backend_sycl_context & ctx, ggml_tensor * dst) {
|
||||
src1_i32, (float *)dst->data, ctx.stream());
|
||||
break;
|
||||
case GGML_TYPE_Q4_K:
|
||||
get_rows_sycl<QK_K, 1, dequantize_q4_K>(ctx, dst->src[0], dst->src[1], dst, (const float *)dst->src[0]->data,
|
||||
get_rows_sycl_f32<QK_K, 1, dequantize_q4_K_f32>(ctx, dst->src[0], dst->src[1], dst, (const float *)dst->src[0]->data,
|
||||
src1_i32, (float *)dst->data, ctx.stream());
|
||||
break;
|
||||
case GGML_TYPE_Q5_0:
|
||||
@@ -272,7 +349,7 @@ void ggml_sycl_op_get_rows(ggml_backend_sycl_context & ctx, ggml_tensor * dst) {
|
||||
src1_i32, (float *)dst->data, ctx.stream());
|
||||
break;
|
||||
case GGML_TYPE_Q5_K:
|
||||
get_rows_sycl<QK_K, 1, dequantize_q5_K>(ctx, dst->src[0], dst->src[1], dst, (const float *)dst->src[0]->data,
|
||||
get_rows_sycl_f32<QK_K, 1, dequantize_q5_K_f32>(ctx, dst->src[0], dst->src[1], dst, (const float *)dst->src[0]->data,
|
||||
src1_i32, (float *)dst->data, ctx.stream());
|
||||
break;
|
||||
case GGML_TYPE_Q6_K:
|
||||
|
||||
@@ -84,6 +84,7 @@ int g_ggml_sycl_debug = 0;
|
||||
int g_ggml_sycl_enable_optimize = 1;
|
||||
int g_ggml_sycl_enable_graph = 0;
|
||||
int g_ggml_sycl_enable_dnn = 1;
|
||||
int g_ggml_sycl_fa_onednn = 1;
|
||||
int g_ggml_sycl_enable_vmm = 1;
|
||||
int g_ggml_sycl_enable_fusion = 1;
|
||||
int g_ggml_sycl_prioritize_dmmv = 0;
|
||||
@@ -285,6 +286,7 @@ static void ggml_check_sycl() try {
|
||||
g_ggml_sycl_enable_optimize = ggml_sycl_get_env("GGML_SYCL_ENABLE_OPT", 1);
|
||||
g_ggml_sycl_enable_graph = ggml_sycl_get_env("GGML_SYCL_ENABLE_GRAPH", 0);
|
||||
g_ggml_sycl_enable_dnn = ggml_sycl_get_env("GGML_SYCL_ENABLE_DNN", 1);
|
||||
g_ggml_sycl_fa_onednn = ggml_sycl_get_env("GGML_SYCL_FA_ONEDNN", 1);
|
||||
g_ggml_sycl_enable_vmm = ggml_sycl_get_env("GGML_SYCL_ENABLE_VMM", 1);
|
||||
g_ggml_sycl_enable_fusion = ggml_sycl_get_env("GGML_SYCL_ENABLE_FUSION", 1);
|
||||
g_ggml_sycl_prioritize_dmmv = ggml_sycl_get_env("GGML_SYCL_PRIORITIZE_DMMV", 0);
|
||||
@@ -352,8 +354,10 @@ static void ggml_check_sycl() try {
|
||||
|
||||
#if defined(GGML_SYCL_DNNL)
|
||||
GGML_LOG_INFO(" GGML_SYCL_ENABLE_DNN: %d\n", g_ggml_sycl_enable_dnn);
|
||||
GGML_LOG_INFO(" GGML_SYCL_FA_ONEDNN: %d\n", g_ggml_sycl_fa_onednn);
|
||||
#else
|
||||
GGML_LOG_INFO(" GGML_SYCL_ENABLE_DNN: DNN disabled by compile flag\n");
|
||||
GGML_LOG_INFO(" GGML_SYCL_FA_ONEDNN: %d\n", g_ggml_sycl_fa_onednn);
|
||||
#endif
|
||||
#ifdef SYCL_FLASH_ATTN
|
||||
GGML_LOG_INFO(" GGML_SYCL_ENABLE_FLASH_ATTN: %d\n", g_ggml_sycl_enable_flash_attention);
|
||||
@@ -839,7 +843,7 @@ static const char * ggml_backend_sycl_buffer_type_get_name(ggml_backend_buffer_t
|
||||
}
|
||||
|
||||
static bool check_usm_system(int device, size_t size) {
|
||||
bool use_usm_system = g_ggml_sycl_usm_system && size >= MEM_SIZE_1G;
|
||||
bool use_usm_system = g_ggml_sycl_usm_system && size >= ((size_t)4 * MEM_SIZE_1G);
|
||||
|
||||
if (use_usm_system && !ggml_sycl_info().devices[device].usm_system_support) {
|
||||
GGML_LOG_INFO("Device does not support USM system allocations\n");
|
||||
@@ -878,6 +882,7 @@ ggml_backend_sycl_buffer_type_alloc_buffer(ggml_backend_buffer_type_t buft,
|
||||
|
||||
void * dev_ptr;
|
||||
if (use_usm_system) {
|
||||
GGML_SYCL_DEBUG("[SYCL] allocating %lu Bytes with USM system\n", size);
|
||||
dev_ptr = (void *)aligned_malloc_host(alignment, aligned_size);
|
||||
if (!dev_ptr) {
|
||||
GGML_LOG_ERROR("%s: can't allocate %lu Bytes of memory on host\n", __func__, size);
|
||||
@@ -5006,6 +5011,9 @@ static bool ggml_sycl_compute_forward(ggml_backend_sycl_context & ctx, struct gg
|
||||
case GGML_UNARY_OP_ELU:
|
||||
ggml_sycl_elu(ctx, dst);
|
||||
break;
|
||||
case GGML_UNARY_OP_XIELU:
|
||||
ggml_sycl_xielu(ctx, dst);
|
||||
break;
|
||||
case GGML_UNARY_OP_FLOOR:
|
||||
ggml_sycl_floor(ctx, dst);
|
||||
break;
|
||||
@@ -5668,6 +5676,7 @@ static bool do_ggml_backend_sycl_device_supports_op(ggml_backend_dev_t dev, cons
|
||||
case GGML_UNARY_OP_EXPM1:
|
||||
case GGML_UNARY_OP_SOFTPLUS:
|
||||
case GGML_UNARY_OP_ELU:
|
||||
case GGML_UNARY_OP_XIELU:
|
||||
case GGML_UNARY_OP_CEIL:
|
||||
return true;
|
||||
case GGML_UNARY_OP_FLOOR:
|
||||
|
||||
@@ -723,6 +723,7 @@ struct vk_device_struct {
|
||||
bool uma;
|
||||
bool prefer_host_memory;
|
||||
bool float_controls_rte_fp16;
|
||||
bool float_controls_denorm_preserve_fp16;
|
||||
bool subgroup_basic;
|
||||
bool subgroup_arithmetic;
|
||||
bool subgroup_shuffle;
|
||||
@@ -868,8 +869,9 @@ struct vk_device_struct {
|
||||
vk_pipeline pipeline_cpy_f32_quant[GGML_TYPE_COUNT];
|
||||
vk_pipeline pipeline_cpy_quant_f32[GGML_TYPE_COUNT];
|
||||
vk_pipeline pipeline_cpy_transpose_16, pipeline_cpy_transpose_32;
|
||||
vk_pipeline pipeline_set_rows_i32[GGML_TYPE_COUNT];
|
||||
vk_pipeline pipeline_set_rows_i64[GGML_TYPE_COUNT];
|
||||
// [src0 0=fp32,1=fp16][dst]
|
||||
vk_pipeline pipeline_set_rows_i32[2][GGML_TYPE_COUNT];
|
||||
vk_pipeline pipeline_set_rows_i64[2][GGML_TYPE_COUNT];
|
||||
vk_pipeline pipeline_norm_f32;
|
||||
vk_pipeline pipeline_group_norm_f32;
|
||||
vk_pipeline pipeline_rms_norm_f32;
|
||||
@@ -959,6 +961,7 @@ struct vk_device_struct {
|
||||
vk_pipeline pipeline_col2im_1d_f32;
|
||||
vk_pipeline pipeline_col2im_1d_f16;
|
||||
vk_pipeline pipeline_col2im_1d_bf16;
|
||||
vk_pipeline pipeline_out_prod_f32;
|
||||
vk_pipeline pipeline_snake_f32;
|
||||
vk_pipeline pipeline_snake_f16;
|
||||
vk_pipeline pipeline_snake_bf16;
|
||||
@@ -2595,10 +2598,10 @@ static void ggml_vk_create_pipeline_func(vk_device& device, vk_pipeline& pipelin
|
||||
|
||||
vk::ShaderModuleCreateInfo shader_module_create_info({}, spv_size, reinterpret_cast<const uint32_t *>(spv_data));
|
||||
|
||||
// Patch SPIR-V to enable RTE rounding for FP16, avoiding the need for
|
||||
// separate shader variants compiled with -DRTE16.
|
||||
// Patch SPIR-V to enable supported FP16 float controls, avoiding the need
|
||||
// for separate shader variants.
|
||||
std::vector<uint32_t> spirv;
|
||||
if (device->float_controls_rte_fp16) {
|
||||
if (device->float_controls_rte_fp16 || device->float_controls_denorm_preserve_fp16) {
|
||||
const uint32_t* spv_words = reinterpret_cast<const uint32_t *>(spv_data);
|
||||
size_t word_count = spv_size / sizeof(uint32_t);
|
||||
spirv.assign(spv_words, spv_words + word_count);
|
||||
@@ -2635,9 +2638,17 @@ static void ggml_vk_create_pipeline_func(vk_device& device, vk_pipeline& pipelin
|
||||
|
||||
// Insert from latest position first so earlier indices stay valid.
|
||||
|
||||
// OpExecutionMode %entrypoint RoundingModeRTE 16
|
||||
uint32_t exec_mode[] = { (4u << spv::WordCountShift) | spv::OpExecutionMode, entry_point_id, spv::ExecutionModeRoundingModeRTE, 16 };
|
||||
spirv.insert(spirv.begin() + exec_insert_pos, std::begin(exec_mode), std::end(exec_mode));
|
||||
if (device->float_controls_rte_fp16) {
|
||||
// OpExecutionMode %entrypoint RoundingModeRTE 16
|
||||
uint32_t exec_mode[] = { (4u << spv::WordCountShift) | spv::OpExecutionMode, entry_point_id, spv::ExecutionModeRoundingModeRTE, 16 };
|
||||
spirv.insert(spirv.begin() + exec_insert_pos, std::begin(exec_mode), std::end(exec_mode));
|
||||
}
|
||||
|
||||
if (device->float_controls_denorm_preserve_fp16) {
|
||||
// OpExecutionMode %entrypoint DenormPreserve 16
|
||||
uint32_t exec_mode[] = { (4u << spv::WordCountShift) | spv::OpExecutionMode, entry_point_id, spv::ExecutionModeDenormPreserve, 16 };
|
||||
spirv.insert(spirv.begin() + exec_insert_pos, std::begin(exec_mode), std::end(exec_mode));
|
||||
}
|
||||
|
||||
// OpExtension "SPV_KHR_float_controls"
|
||||
const char ext_str[] = "SPV_KHR_float_controls";
|
||||
@@ -2647,9 +2658,17 @@ static void ggml_vk_create_pipeline_func(vk_device& device, vk_pipeline& pipelin
|
||||
memcpy(&extension[1], ext_str, sizeof(ext_str));
|
||||
spirv.insert(spirv.begin() + ext_insert_pos, extension.begin(), extension.end());
|
||||
|
||||
// OpCapability RoundingModeRTE
|
||||
uint32_t capability[] = { (2u << spv::WordCountShift) | spv::OpCapability, spv::CapabilityRoundingModeRTE };
|
||||
spirv.insert(spirv.begin() + cap_insert_pos, std::begin(capability), std::end(capability));
|
||||
if (device->float_controls_rte_fp16) {
|
||||
// OpCapability RoundingModeRTE
|
||||
uint32_t capability[] = { (2u << spv::WordCountShift) | spv::OpCapability, spv::CapabilityRoundingModeRTE };
|
||||
spirv.insert(spirv.begin() + cap_insert_pos, std::begin(capability), std::end(capability));
|
||||
}
|
||||
|
||||
if (device->float_controls_denorm_preserve_fp16) {
|
||||
// OpCapability DenormPreserve
|
||||
uint32_t capability[] = { (2u << spv::WordCountShift) | spv::OpCapability, spv::CapabilityDenormPreserve };
|
||||
spirv.insert(spirv.begin() + cap_insert_pos, std::begin(capability), std::end(capability));
|
||||
}
|
||||
|
||||
shader_module_create_info = vk::ShaderModuleCreateInfo({}, spirv.size() * sizeof(uint32_t), spirv.data());
|
||||
}
|
||||
@@ -5187,20 +5206,22 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
|
||||
ggml_vk_create_pipeline(device, device->pipeline_cpy_f32_quant[GGML_TYPE_Q8_0], "cpy_f32_q8_0", cpy_f32_q8_0_len, cpy_f32_q8_0_data, "main", 2, sizeof(vk_op_unary_push_constants), {32, 1, 1}, {}, 1);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_cpy_f32_quant[GGML_TYPE_IQ4_NL], "cpy_f32_iq4_nl", cpy_f32_iq4_nl_len, cpy_f32_iq4_nl_data, "main", 2, sizeof(vk_op_unary_push_constants), {32, 1, 1}, {}, 1);
|
||||
|
||||
#define SET_ROWS(itype) \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [GGML_TYPE_F32], "set_rows_f32" #itype, set_rows_f32 ## itype ## _len, set_rows_f32 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [GGML_TYPE_F16], "set_rows_f16" #itype, set_rows_f16 ## itype ## _len, set_rows_f16 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [GGML_TYPE_BF16], "set_rows_bf16" #itype, set_rows_bf16 ## itype ## _len, set_rows_bf16 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [GGML_TYPE_Q1_0], "set_rows_q1_0" #itype, set_rows_q1_0 ## itype ## _len, set_rows_q1_0 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [GGML_TYPE_Q4_0], "set_rows_q4_0" #itype, set_rows_q4_0 ## itype ## _len, set_rows_q4_0 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [GGML_TYPE_Q4_1], "set_rows_q4_1" #itype, set_rows_q4_1 ## itype ## _len, set_rows_q4_1 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [GGML_TYPE_Q5_0], "set_rows_q5_0" #itype, set_rows_q5_0 ## itype ## _len, set_rows_q5_0 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [GGML_TYPE_Q5_1], "set_rows_q5_1" #itype, set_rows_q5_1 ## itype ## _len, set_rows_q5_1 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [GGML_TYPE_Q8_0], "set_rows_q8_0" #itype, set_rows_q8_0 ## itype ## _len, set_rows_q8_0 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [GGML_TYPE_IQ4_NL], "set_rows_iq4_nl" #itype, set_rows_iq4_nl ## itype ## _len, set_rows_iq4_nl ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true);
|
||||
#define SET_ROWS(src_idx, src, itype) \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_F32], "set_rows_" #src "_f32" #itype, set_rows_ ## src ## _f32 ## itype ## _len, set_rows_ ## src ## _f32 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_F16], "set_rows_" #src "_f16" #itype, set_rows_ ## src ## _f16 ## itype ## _len, set_rows_ ## src ## _f16 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_BF16], "set_rows_" #src "_bf16" #itype, set_rows_ ## src ## _bf16 ## itype ## _len, set_rows_ ## src ## _bf16 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_Q1_0], "set_rows_" #src "_q1_0" #itype, set_rows_ ## src ## _q1_0 ## itype ## _len, set_rows_ ## src ## _q1_0 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_Q4_0], "set_rows_" #src "_q4_0" #itype, set_rows_ ## src ## _q4_0 ## itype ## _len, set_rows_ ## src ## _q4_0 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_Q4_1], "set_rows_" #src "_q4_1" #itype, set_rows_ ## src ## _q4_1 ## itype ## _len, set_rows_ ## src ## _q4_1 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_Q5_0], "set_rows_" #src "_q5_0" #itype, set_rows_ ## src ## _q5_0 ## itype ## _len, set_rows_ ## src ## _q5_0 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_Q5_1], "set_rows_" #src "_q5_1" #itype, set_rows_ ## src ## _q5_1 ## itype ## _len, set_rows_ ## src ## _q5_1 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_Q8_0], "set_rows_" #src "_q8_0" #itype, set_rows_ ## src ## _q8_0 ## itype ## _len, set_rows_ ## src ## _q8_0 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
|
||||
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_IQ4_NL], "set_rows_" #src "_iq4_nl" #itype, set_rows_ ## src ## _iq4_nl ## itype ## _len, set_rows_ ## src ## _iq4_nl ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true);
|
||||
|
||||
SET_ROWS(_i32)
|
||||
SET_ROWS(_i64)
|
||||
SET_ROWS(0, f32, _i32)
|
||||
SET_ROWS(0, f32, _i64)
|
||||
SET_ROWS(1, f16, _i32)
|
||||
SET_ROWS(1, f16, _i64)
|
||||
#undef SET_ROWS
|
||||
|
||||
|
||||
@@ -5459,6 +5480,8 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
|
||||
ggml_vk_create_pipeline(device, device->pipeline_col2im_1d_f16, "col2im_1d_f16", col2im_1d_f16_len, col2im_1d_f16_data, "main", 2, sizeof(vk_op_col2im_1d_push_constants), {256, 1, 1}, {}, 1, true);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_col2im_1d_bf16, "col2im_1d_bf16", col2im_1d_bf16_len, col2im_1d_bf16_data, "main", 2, sizeof(vk_op_col2im_1d_push_constants), {256, 1, 1}, {}, 1, true);
|
||||
|
||||
ggml_vk_create_pipeline(device, device->pipeline_out_prod_f32, "out_prod_f32", out_prod_f32_len, out_prod_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), {256, 1, 1}, {}, 1);
|
||||
|
||||
ggml_vk_create_pipeline(device, device->pipeline_snake_f32, "snake_f32", snake_f32_len, snake_f32_data, "main", 4, sizeof(vk_op_snake_push_constants), {256, 1, 1}, {}, 1);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_snake_f16, "snake_f16", snake_f16_len, snake_f16_data, "main", 4, sizeof(vk_op_snake_push_constants), {256, 1, 1}, {}, 1);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_snake_bf16, "snake_bf16", snake_bf16_len, snake_bf16_data, "main", 4, sizeof(vk_op_snake_push_constants), {256, 1, 1}, {}, 1);
|
||||
@@ -6031,6 +6054,7 @@ static vk_device ggml_vk_get_device(size_t idx) {
|
||||
device->shader_core_count = 0;
|
||||
}
|
||||
device->float_controls_rte_fp16 = vk12_props.shaderRoundingModeRTEFloat16;
|
||||
device->float_controls_denorm_preserve_fp16 = vk12_props.shaderDenormPreserveFloat16;
|
||||
|
||||
device->subgroup_basic = (vk11_props.subgroupSupportedStages & vk::ShaderStageFlagBits::eCompute) &&
|
||||
(vk11_props.subgroupSupportedOperations & vk::SubgroupFeatureFlagBits::eBasic);
|
||||
@@ -10724,6 +10748,11 @@ static vk_pipeline ggml_vk_op_get_pipeline(ggml_backend_vk_context * ctx, const
|
||||
return ctx->device->pipeline_add_id_f32;
|
||||
}
|
||||
return nullptr;
|
||||
case GGML_OP_OUT_PROD:
|
||||
if (src0->type == GGML_TYPE_F32 && src1->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32) {
|
||||
return ctx->device->pipeline_out_prod_f32;
|
||||
}
|
||||
return nullptr;
|
||||
case GGML_OP_CONCAT: {
|
||||
if (src0->type != src1->type || src0->type != dst->type) {
|
||||
return nullptr;
|
||||
@@ -10843,10 +10872,17 @@ static vk_pipeline ggml_vk_op_get_pipeline(ggml_backend_vk_context * ctx, const
|
||||
case GGML_OP_DUP:
|
||||
return ggml_vk_get_cpy_pipeline(ctx, src0, dst, dst->type);
|
||||
case GGML_OP_SET_ROWS:
|
||||
if (src1->type == GGML_TYPE_I64) {
|
||||
return ctx->device->pipeline_set_rows_i64[dst->type];
|
||||
} else {
|
||||
return ctx->device->pipeline_set_rows_i32[dst->type];
|
||||
{
|
||||
if (src0->type != GGML_TYPE_F32 && src0->type != GGML_TYPE_F16) {
|
||||
return nullptr;
|
||||
}
|
||||
const int src_idx = src0->type == GGML_TYPE_F16;
|
||||
if (src1->type == GGML_TYPE_I64) {
|
||||
return ctx->device->pipeline_set_rows_i64[src_idx][dst->type];
|
||||
} else if (src1->type == GGML_TYPE_I32) {
|
||||
return ctx->device->pipeline_set_rows_i32[src_idx][dst->type];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
case GGML_OP_SILU_BACK:
|
||||
if (src0->type == GGML_TYPE_F32 && src1->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32) {
|
||||
@@ -11673,6 +11709,7 @@ static void ggml_vk_op_f32(ggml_backend_vk_context * ctx, vk_context& subctx, co
|
||||
case GGML_OP_DIV:
|
||||
case GGML_OP_MUL:
|
||||
case GGML_OP_ADD1:
|
||||
case GGML_OP_OUT_PROD:
|
||||
case GGML_OP_ARANGE:
|
||||
case GGML_OP_FILL:
|
||||
case GGML_OP_SCALE:
|
||||
@@ -11986,6 +12023,24 @@ static void ggml_vk_add(ggml_backend_vk_context * ctx, vk_context& subctx, const
|
||||
});
|
||||
}
|
||||
|
||||
static void ggml_vk_out_prod(ggml_backend_vk_context * ctx, vk_context& subctx, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst) {
|
||||
const uint32_t src0_type_size = ggml_type_size(src0->type);
|
||||
const uint32_t src1_type_size = ggml_type_size(src1->type);
|
||||
const uint32_t dst_type_size = ggml_type_size(dst->type);
|
||||
|
||||
ggml_vk_op_f32<vk_op_binary_push_constants>(ctx, subctx, src0, src1, nullptr, nullptr, dst, GGML_OP_OUT_PROD, {
|
||||
(uint32_t)ggml_nelements(dst),
|
||||
(uint32_t)src0->ne[0], (uint32_t)src0->ne[1], (uint32_t)src0->ne[2],(uint32_t)src0->ne[3],
|
||||
(uint32_t)src0->nb[0] / src0_type_size, (uint32_t)src0->nb[1] / src0_type_size, (uint32_t)src0->nb[2] / src0_type_size, (uint32_t)src0->nb[3] / src0_type_size,
|
||||
(uint32_t)src1->ne[0], (uint32_t)src1->ne[1], (uint32_t)src1->ne[2],(uint32_t)src1->ne[3],
|
||||
(uint32_t)src1->nb[0] / src1_type_size, (uint32_t)src1->nb[1] / src1_type_size, (uint32_t)src1->nb[2] / src1_type_size, (uint32_t)src1->nb[3] / src1_type_size,
|
||||
(uint32_t) dst->ne[0], (uint32_t) dst->ne[1], (uint32_t) dst->ne[2],(uint32_t) dst->ne[3],
|
||||
(uint32_t) dst->nb[0] / dst_type_size, (uint32_t) dst->nb[1] / dst_type_size, (uint32_t) dst->nb[2] / dst_type_size, (uint32_t) dst->nb[3] / dst_type_size,
|
||||
0,
|
||||
0.0f, 0.0f, 0,
|
||||
});
|
||||
}
|
||||
|
||||
static void ggml_vk_sub(ggml_backend_vk_context * ctx, vk_context& subctx, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst) {
|
||||
const uint32_t src0_type_size = ggml_type_size(src0->type);
|
||||
const uint32_t src1_type_size = ggml_type_size(src1->type);
|
||||
@@ -14773,6 +14828,9 @@ static bool ggml_vk_build_graph(ggml_backend_vk_context * ctx, ggml_cgraph * cgr
|
||||
ggml_vk_add(ctx, compute_ctx, src0, src1, node);
|
||||
}
|
||||
break;
|
||||
case GGML_OP_OUT_PROD:
|
||||
ggml_vk_out_prod(ctx, compute_ctx, src0, src1, node);
|
||||
break;
|
||||
case GGML_OP_SUB:
|
||||
ggml_vk_sub(ctx, compute_ctx, src0, src1, node);
|
||||
|
||||
@@ -17500,24 +17558,25 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm
|
||||
return op->type == GGML_TYPE_F32 && op->src[0]->type == GGML_TYPE_F32;
|
||||
case GGML_OP_SET_ROWS:
|
||||
{
|
||||
if (op->src[0]->type == GGML_TYPE_F32) {
|
||||
switch (op->type) {
|
||||
case GGML_TYPE_F32:
|
||||
case GGML_TYPE_F16:
|
||||
case GGML_TYPE_BF16:
|
||||
case GGML_TYPE_Q1_0:
|
||||
case GGML_TYPE_Q4_0:
|
||||
case GGML_TYPE_Q4_1:
|
||||
case GGML_TYPE_Q5_0:
|
||||
case GGML_TYPE_Q5_1:
|
||||
case GGML_TYPE_Q8_0:
|
||||
case GGML_TYPE_IQ4_NL:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if ((op->src[0]->type != GGML_TYPE_F32 && op->src[0]->type != GGML_TYPE_F16) ||
|
||||
(op->src[1]->type != GGML_TYPE_I32 && op->src[1]->type != GGML_TYPE_I64)) {
|
||||
return false;
|
||||
}
|
||||
switch (op->type) {
|
||||
case GGML_TYPE_F32:
|
||||
case GGML_TYPE_F16:
|
||||
case GGML_TYPE_BF16:
|
||||
case GGML_TYPE_Q1_0:
|
||||
case GGML_TYPE_Q4_0:
|
||||
case GGML_TYPE_Q4_1:
|
||||
case GGML_TYPE_Q5_0:
|
||||
case GGML_TYPE_Q5_1:
|
||||
case GGML_TYPE_Q8_0:
|
||||
case GGML_TYPE_IQ4_NL:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case GGML_OP_CONT:
|
||||
case GGML_OP_CPY:
|
||||
@@ -17626,6 +17685,10 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm
|
||||
case GGML_OP_OPT_STEP_ADAMW:
|
||||
case GGML_OP_OPT_STEP_SGD:
|
||||
return ggml_is_contiguous(op->src[0]) && op->src[0]->type == GGML_TYPE_F32;
|
||||
case GGML_OP_OUT_PROD:
|
||||
return ggml_is_contiguous(op->src[0]) && op->src[0]->type == GGML_TYPE_F32
|
||||
&& ggml_is_contiguous(op->src[1]) && op->src[1]->type == GGML_TYPE_F32
|
||||
&& op->type == GGML_TYPE_F32;
|
||||
case GGML_OP_LOG:
|
||||
case GGML_OP_TRI:
|
||||
case GGML_OP_DIAG:
|
||||
|
||||
@@ -10,7 +10,7 @@ layout(local_size_x = 32, local_size_y = 1, local_size_z = 1) in;
|
||||
const uint BLOCK_SIZE = 32;
|
||||
#endif
|
||||
|
||||
layout (binding = 0) readonly buffer S {float data_s[];};
|
||||
layout (binding = 0) readonly buffer S {S_TYPE data_s[];};
|
||||
|
||||
#if defined(SET_ROWS)
|
||||
#include "generic_binary_head.glsl"
|
||||
@@ -35,7 +35,7 @@ void quantize(uint dst_idx, uint src_idx)
|
||||
float vmax = 0.0;
|
||||
|
||||
[[unroll]] for (int j = 0; j < QUANT_K_Q4_0; ++j) {
|
||||
const float v = data_s[src_idx + j];
|
||||
const float v = float(data_s[src_idx + j]);
|
||||
if (amax < abs(v)) {
|
||||
amax = abs(v);
|
||||
vmax = v;
|
||||
@@ -48,8 +48,8 @@ void quantize(uint dst_idx, uint src_idx)
|
||||
data_q[dst_idx].d = float16_t(d);
|
||||
|
||||
[[unroll]] for (int j = 0; j < QUANT_K_Q4_0/2; ++j) {
|
||||
const float x0 = data_s[src_idx + 0 + j]*id;
|
||||
const float x1 = data_s[src_idx + QUANT_K_Q4_0/2 + j]*id;
|
||||
const float x0 = float(data_s[src_idx + 0 + j])*id;
|
||||
const float x1 = float(data_s[src_idx + QUANT_K_Q4_0/2 + j])*id;
|
||||
|
||||
const uint xi0 = min(15, int(x0 + 8.5));
|
||||
const uint xi1 = min(15, int(x1 + 8.5));
|
||||
@@ -66,7 +66,7 @@ void quantize(uint dst_idx, uint src_idx)
|
||||
float vmax = -vmin;
|
||||
|
||||
[[unroll]] for (int j = 0; j < QUANT_K_Q4_1; ++j) {
|
||||
const float v = data_s[src_idx + j];
|
||||
const float v = float(data_s[src_idx + j]);
|
||||
|
||||
if (v < vmin) vmin = v;
|
||||
if (v > vmax) vmax = v;
|
||||
@@ -79,8 +79,8 @@ void quantize(uint dst_idx, uint src_idx)
|
||||
data_q[dst_idx].m = float16_t(vmin);
|
||||
|
||||
[[unroll]] for (int j = 0; j < QUANT_K_Q4_1/2; ++j) {
|
||||
const float x0 = (data_s[src_idx + 0 + j] - vmin)*id;
|
||||
const float x1 = (data_s[src_idx + QUANT_K_Q4_1/2 + j] - vmin)*id;
|
||||
const float x0 = (float(data_s[src_idx + 0 + j]) - vmin)*id;
|
||||
const float x1 = (float(data_s[src_idx + QUANT_K_Q4_1/2 + j]) - vmin)*id;
|
||||
|
||||
const uint xi0 = min(15, int(x0 + 0.5));
|
||||
const uint xi1 = min(15, int(x1 + 0.5));
|
||||
@@ -97,7 +97,7 @@ void quantize(uint dst_idx, uint src_idx)
|
||||
float vmax = 0.0;
|
||||
|
||||
[[unroll]] for (int j = 0; j < QUANT_K_Q5_0; ++j) {
|
||||
const float v = data_s[src_idx + j];
|
||||
const float v = float(data_s[src_idx + j]);
|
||||
if (amax < abs(v)) {
|
||||
amax = abs(v);
|
||||
vmax = v;
|
||||
@@ -111,8 +111,8 @@ void quantize(uint dst_idx, uint src_idx)
|
||||
|
||||
uint32_t qh = 0;
|
||||
[[unroll]] for (int j = 0; j < QUANT_K_Q5_0/2; ++j) {
|
||||
const float x0 = data_s[src_idx + 0 + j]*id;
|
||||
const float x1 = data_s[src_idx + QUANT_K_Q5_0/2 + j]*id;
|
||||
const float x0 = float(data_s[src_idx + 0 + j])*id;
|
||||
const float x1 = float(data_s[src_idx + QUANT_K_Q5_0/2 + j])*id;
|
||||
|
||||
const uint xi0 = min(31, int(x0 + 16.5));
|
||||
const uint xi1 = min(31, int(x1 + 16.5));
|
||||
@@ -129,11 +129,11 @@ void quantize(uint dst_idx, uint src_idx)
|
||||
#if defined(DATA_A_Q5_1)
|
||||
void quantize(uint dst_idx, uint src_idx)
|
||||
{
|
||||
float min = data_s[src_idx + 0];
|
||||
float min = float(data_s[src_idx + 0]);
|
||||
float max = min;
|
||||
|
||||
[[unroll]] for (int j = 1; j < QUANT_K_Q5_1; ++j) {
|
||||
const float v = data_s[src_idx + j];
|
||||
const float v = float(data_s[src_idx + j]);
|
||||
min = v < min ? v : min;
|
||||
max = v > max ? v : max;
|
||||
}
|
||||
@@ -146,8 +146,8 @@ void quantize(uint dst_idx, uint src_idx)
|
||||
|
||||
uint32_t qh = 0;
|
||||
[[unroll]] for (int j = 0; j < QUANT_K_Q5_1/2; ++j) {
|
||||
const float x0 = (data_s[src_idx + 0 + j] - min)*id;
|
||||
const float x1 = (data_s[src_idx + QUANT_K_Q5_1/2 + j] - min)*id;
|
||||
const float x0 = (float(data_s[src_idx + 0 + j]) - min)*id;
|
||||
const float x1 = (float(data_s[src_idx + QUANT_K_Q5_1/2 + j]) - min)*id;
|
||||
|
||||
const uint xi0 = uint(x0 + 0.5);
|
||||
const uint xi1 = uint(x1 + 0.5);
|
||||
@@ -166,7 +166,7 @@ void quantize(uint dst_idx, uint src_idx)
|
||||
float amax = 0.0; // absolute max
|
||||
|
||||
[[unroll]] for (int j = 0; j < QUANT_K_Q8_0; j++) {
|
||||
const float v = data_s[src_idx + j];
|
||||
const float v = float(data_s[src_idx + j]);
|
||||
amax = max(amax, abs(v));
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ void quantize(uint dst_idx, uint src_idx)
|
||||
data_q[dst_idx].d = float16_t(d);
|
||||
|
||||
[[unroll]] for (int j = 0; j < QUANT_K_Q8_0; ++j) {
|
||||
const float x0 = data_s[src_idx + j]*id;
|
||||
const float x0 = float(data_s[src_idx + j])*id;
|
||||
|
||||
data_q[dst_idx].qs[j] = int8_t(round(x0));
|
||||
}
|
||||
@@ -189,7 +189,7 @@ void quantize(uint dst_idx, uint src_idx)
|
||||
float sum_abs = 0.0;
|
||||
|
||||
[[unroll]] for (int j = 0; j < QUANT_K_Q1_0; j++) {
|
||||
sum_abs += abs(data_s[src_idx + j]);
|
||||
sum_abs += abs(float(data_s[src_idx + j]));
|
||||
}
|
||||
|
||||
const float d = sum_abs / QUANT_K_Q1_0;
|
||||
@@ -201,7 +201,7 @@ void quantize(uint dst_idx, uint src_idx)
|
||||
}
|
||||
|
||||
[[unroll]] for (int j = 0; j < QUANT_K_Q1_0; ++j) {
|
||||
if (data_s[src_idx + j] >= 0.0) {
|
||||
if (float(data_s[src_idx + j]) >= 0.0) {
|
||||
data_q[dst_idx].qs[j / 8] |= uint8_t(1 << (j % 8));
|
||||
}
|
||||
}
|
||||
@@ -226,7 +226,7 @@ void quantize(uint dst_idx, uint src_idx)
|
||||
float vmax = 0.0;
|
||||
|
||||
[[unroll]] for (int j = 0; j < QUANT_K_IQ4_NL; ++j) {
|
||||
const float v = data_s[src_idx + j];
|
||||
const float v = float(data_s[src_idx + j]);
|
||||
if (amax < abs(v)) {
|
||||
amax = abs(v);
|
||||
vmax = v;
|
||||
@@ -238,16 +238,16 @@ void quantize(uint dst_idx, uint src_idx)
|
||||
|
||||
float sumqx = 0, sumq2 = 0;
|
||||
[[unroll]] for (int j = 0; j < QUANT_K_IQ4_NL/2; ++j) {
|
||||
const float x0 = data_s[src_idx + 0 + j]*id;
|
||||
const float x1 = data_s[src_idx + QUANT_K_IQ4_NL/2 + j]*id;
|
||||
const float x0 = float(data_s[src_idx + 0 + j])*id;
|
||||
const float x1 = float(data_s[src_idx + QUANT_K_IQ4_NL/2 + j])*id;
|
||||
const uint xi0 = best_index(x0);
|
||||
const uint xi1 = best_index(x1);
|
||||
data_q[dst_idx].qs[j] = uint8_t(xi0 | (xi1 << 4));
|
||||
const float v0 = kvalues_iq4nl[xi0];
|
||||
const float v1 = kvalues_iq4nl[xi1];
|
||||
const float w0 = data_s[src_idx + 0 + j]*data_s[src_idx + 0 + j];
|
||||
const float w1 = data_s[src_idx + QUANT_K_IQ4_NL/2 + j]*data_s[src_idx + QUANT_K_IQ4_NL/2 + j];
|
||||
sumqx += w0*v0*data_s[src_idx + j] + w1*v1*data_s[src_idx + QUANT_K_IQ4_NL/2 + j];
|
||||
const float w0 = float(data_s[src_idx + 0 + j])*float(data_s[src_idx + 0 + j]);
|
||||
const float w1 = float(data_s[src_idx + QUANT_K_IQ4_NL/2 + j])*float(data_s[src_idx + QUANT_K_IQ4_NL/2 + j]);
|
||||
sumqx += w0*v0*float(data_s[src_idx + j]) + w1*v1*float(data_s[src_idx + QUANT_K_IQ4_NL/2 + j]);
|
||||
sumq2 += w0*v0*v0 + w1*v1*v1;
|
||||
}
|
||||
|
||||
@@ -259,14 +259,14 @@ void quantize(uint dst_idx, uint src_idx)
|
||||
#if defined(DATA_A_F32) || defined(DATA_A_F16)
|
||||
void quantize(uint dst_idx, uint src_idx)
|
||||
{
|
||||
data_q[dst_idx] = A_TYPE(data_s[src_idx]);
|
||||
data_q[dst_idx] = A_TYPE(float(data_s[src_idx]));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(DATA_A_BF16)
|
||||
void quantize(uint dst_idx, uint src_idx)
|
||||
{
|
||||
data_q[dst_idx] = A_TYPE(fp32_to_bf16(data_s[src_idx]));
|
||||
data_q[dst_idx] = A_TYPE(fp32_to_bf16(float(data_s[src_idx])));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#version 450
|
||||
|
||||
#extension GL_EXT_shader_16bit_storage : require
|
||||
|
||||
layout (push_constant) uniform parameter
|
||||
{
|
||||
uint ne;
|
||||
uint ne00; uint ne01; uint ne02; uint ne03; uint nb00; uint nb01; uint nb02; uint nb03;
|
||||
uint ne10; uint ne11; uint ne12; uint ne13; uint nb10; uint nb11; uint nb12; uint nb13;
|
||||
uint ne20; uint ne21; uint ne22; uint ne23; uint nb20; uint nb21; uint nb22; uint nb23;
|
||||
uint misalign_offsets;
|
||||
float param1; float param2; int param3;
|
||||
} p;
|
||||
|
||||
layout (binding = 0) readonly buffer A {float data_a[];};
|
||||
layout (binding = 1) readonly buffer B {float data_b[];};
|
||||
layout (binding = 2) writeonly buffer D {float data_d[];};
|
||||
|
||||
uint get_idx() {
|
||||
return gl_GlobalInvocationID.z * 262144 + gl_GlobalInvocationID.y * 512 + gl_GlobalInvocationID.x;
|
||||
}
|
||||
|
||||
uint get_aoffset() { return p.misalign_offsets >> 16; }
|
||||
uint get_boffset() { return (p.misalign_offsets >> 8) & 0xFF; }
|
||||
uint get_doffset() { return p.misalign_offsets & 0xFF; }
|
||||
|
||||
layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
void main() {
|
||||
uint idx = get_idx();
|
||||
if (idx >= p.ne) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint tmp = idx;
|
||||
uint i0 = tmp % p.ne20; tmp /= p.ne20;
|
||||
uint i1 = tmp % p.ne21; tmp /= p.ne21;
|
||||
uint i2 = tmp % p.ne22; tmp /= p.ne22;
|
||||
uint i3 = tmp;
|
||||
|
||||
uint a_i0 = i0 % p.ne00;
|
||||
uint a_i2 = i2 / (p.ne22 / p.ne02);
|
||||
uint a_i3 = i3 / (p.ne23 / p.ne03);
|
||||
|
||||
uint b_i0 = i1 % p.ne10;
|
||||
uint b_i2 = i2;
|
||||
uint b_i3 = i3;
|
||||
|
||||
float sum = 0.0f;
|
||||
uint K = p.ne01;
|
||||
for (uint k = 0; k < K; k++) {
|
||||
uint aoff = get_aoffset() + a_i3*p.nb03 + a_i2*p.nb02 + k*p.nb01 + a_i0*p.nb00;
|
||||
uint boff = get_boffset() + b_i3*p.nb13 + b_i2*p.nb12 + k*p.nb11 + b_i0*p.nb10;
|
||||
sum += data_a[aoff] * data_b[boff];
|
||||
}
|
||||
|
||||
uint doff = get_doffset() + i3*p.nb23 + i2*p.nb22 + i1*p.nb21 + i0*p.nb20;
|
||||
data_d[doff] = sum;
|
||||
}
|
||||
@@ -824,13 +824,15 @@ void process_shaders() {
|
||||
string_to_spv("cpy_transpose_32", "copy_transpose.comp", {{"A_TYPE", "uint"}, {"D_TYPE", "uint"}});
|
||||
|
||||
for (std::string t : {"q1_0", "q4_0", "q4_1", "q5_0", "q5_1", "q8_0", "iq4_nl"}) {
|
||||
string_to_spv("cpy_f32_" + t, "copy_to_quant.comp", {{"DATA_A_" + to_uppercase(t), "1"}, {"D_TYPE", "float"}, {"FLOAT_TYPE", "float"}});
|
||||
string_to_spv("cpy_f32_" + t, "copy_to_quant.comp", {{"DATA_A_" + to_uppercase(t), "1"}, {"S_TYPE", "float"}, {"D_TYPE", "float"}, {"FLOAT_TYPE", "float"}});
|
||||
string_to_spv("cpy_" + t + "_f32", "copy_from_quant.comp", {{"DATA_A_" + to_uppercase(t), "1"}, {"D_TYPE", "float"}, {"FLOAT_TYPE", "float"}});
|
||||
}
|
||||
|
||||
for (std::string t : {"f32", "f16", "bf16", "q1_0", "q4_0", "q4_1", "q5_0", "q5_1", "q8_0", "iq4_nl"}) {
|
||||
string_to_spv("set_rows_" + t + "_i32", "copy_to_quant.comp", {{"SET_ROWS", "1"}, {"DATA_A_" + to_uppercase(t), "1"}, {"B_TYPE", "uint"}, {"B_SIZE", "32"}, {"D_TYPE", "float"}, {"FLOAT_TYPE", "float"}});
|
||||
string_to_spv("set_rows_" + t + "_i64", "copy_to_quant.comp", {{"SET_ROWS", "1"}, {"DATA_A_" + to_uppercase(t), "1"}, {"B_TYPE", "uvec2"}, {"B_SIZE", "64"}, {"D_TYPE", "float"}, {"FLOAT_TYPE", "float"}});
|
||||
for (auto src : {std::pair{"f32", "float"}, std::pair{"f16", "float16_t"}}) {
|
||||
for (std::string dst : {"f32", "f16", "bf16", "q1_0", "q4_0", "q4_1", "q5_0", "q5_1", "q8_0", "iq4_nl"}) {
|
||||
string_to_spv("set_rows_" + std::string(src.first) + "_" + dst + "_i32", "copy_to_quant.comp", {{"SET_ROWS", "1"}, {"DATA_A_" + to_uppercase(dst), "1"}, {"B_TYPE", "uint"}, {"B_SIZE", "32"}, {"S_TYPE", src.second}, {"D_TYPE", "float"}, {"FLOAT_TYPE", "float"}});
|
||||
string_to_spv("set_rows_" + std::string(src.first) + "_" + dst + "_i64", "copy_to_quant.comp", {{"SET_ROWS", "1"}, {"DATA_A_" + to_uppercase(dst), "1"}, {"B_TYPE", "uvec2"}, {"B_SIZE", "64"}, {"S_TYPE", src.second}, {"D_TYPE", "float"}, {"FLOAT_TYPE", "float"}});
|
||||
}
|
||||
}
|
||||
|
||||
auto get_type_str = [](bool f16) {
|
||||
@@ -1034,6 +1036,8 @@ void process_shaders() {
|
||||
}
|
||||
}
|
||||
|
||||
string_to_spv("out_prod_f32", "out_prod.comp", {});
|
||||
|
||||
string_to_spv("timestep_embedding_f32", "timestep_embedding.comp", merge_maps(base_dict, {{"A_TYPE", "float"}, {"D_TYPE", "float"}}));
|
||||
|
||||
string_to_spv("conv_transpose_1d_f32", "conv_transpose_1d.comp", {{"A_TYPE", "float"}, {"B_TYPE", "float"}, {"D_TYPE", "float"}});
|
||||
|
||||
+23
-11
@@ -1464,14 +1464,14 @@ bool ggml_is_transposed(const struct ggml_tensor * tensor) {
|
||||
return tensor->nb[0] > tensor->nb[1];
|
||||
}
|
||||
|
||||
static bool ggml_is_contiguous_n(const struct ggml_tensor * tensor, int n) {
|
||||
static bool ggml_is_contiguous_m_n(const struct ggml_tensor * tensor, int m, int n) {
|
||||
size_t next_nb = ggml_type_size(tensor->type);
|
||||
if (tensor->ne[0] != ggml_blck_size(tensor->type) && tensor->nb[0] != next_nb) {
|
||||
return false;
|
||||
}
|
||||
next_nb *= tensor->ne[0]/ggml_blck_size(tensor->type);
|
||||
for (int i = 1; i < GGML_MAX_DIMS; i++) {
|
||||
if (i > n) {
|
||||
for (int i = 1; i < n; i++) {
|
||||
if (i > m) {
|
||||
if (tensor->ne[i] != 1 && tensor->nb[i] != next_nb) {
|
||||
return false;
|
||||
}
|
||||
@@ -1489,15 +1489,27 @@ bool ggml_is_contiguous(const struct ggml_tensor * tensor) {
|
||||
}
|
||||
|
||||
bool ggml_is_contiguous_0(const struct ggml_tensor * tensor) {
|
||||
return ggml_is_contiguous_n(tensor, 0);
|
||||
return ggml_is_contiguous_m_n(tensor, 0, GGML_MAX_DIMS);
|
||||
}
|
||||
|
||||
bool ggml_is_contiguous_1(const struct ggml_tensor * tensor) {
|
||||
return ggml_is_contiguous_n(tensor, 1);
|
||||
return ggml_is_contiguous_m_n(tensor, 1, GGML_MAX_DIMS);
|
||||
}
|
||||
|
||||
bool ggml_is_contiguous_2(const struct ggml_tensor * tensor) {
|
||||
return ggml_is_contiguous_n(tensor, 2);
|
||||
return ggml_is_contiguous_m_n(tensor, 2, GGML_MAX_DIMS);
|
||||
}
|
||||
|
||||
bool ggml_is_contiguous_to_1(const struct ggml_tensor * tensor) {
|
||||
return ggml_is_contiguous_m_n(tensor, 0, 1);
|
||||
}
|
||||
|
||||
bool ggml_is_contiguous_to_2(const struct ggml_tensor * tensor) {
|
||||
return ggml_is_contiguous_m_n(tensor, 0, 2);
|
||||
}
|
||||
|
||||
bool ggml_is_contiguous_to_3(const struct ggml_tensor * tensor) {
|
||||
return ggml_is_contiguous_m_n(tensor, 0, 3);
|
||||
}
|
||||
|
||||
bool ggml_is_contiguously_allocated(const struct ggml_tensor * tensor) {
|
||||
@@ -4507,7 +4519,7 @@ struct ggml_tensor * ggml_conv_1d(
|
||||
int s0,
|
||||
int p0,
|
||||
int d0) {
|
||||
struct ggml_tensor * im2col = ggml_im2col(ctx, a, b, s0, 0, p0, 0, d0, 0, false, GGML_TYPE_F16); // [N, OL, IC * K]
|
||||
struct ggml_tensor * im2col = ggml_im2col(ctx, a, b, s0, 0, p0, 0, d0, 0, false, a->type == GGML_TYPE_BF16 ? GGML_TYPE_F32 : GGML_TYPE_F16); // [N, OL, IC * K]
|
||||
|
||||
struct ggml_tensor * result =
|
||||
ggml_mul_mat(ctx,
|
||||
@@ -4541,7 +4553,7 @@ struct ggml_tensor * ggml_conv_1d_dw(
|
||||
int d0) {
|
||||
struct ggml_tensor * new_b = ggml_reshape_4d(ctx, b, b->ne[0], 1, b->ne[1], b->ne[2]);
|
||||
|
||||
struct ggml_tensor * im2col = ggml_im2col(ctx, a, new_b, s0, 0, p0, 0, d0, 0, false, GGML_TYPE_F16);
|
||||
struct ggml_tensor * im2col = ggml_im2col(ctx, a, new_b, s0, 0, p0, 0, d0, 0, false, a->type == GGML_TYPE_BF16 ? GGML_TYPE_F32 : GGML_TYPE_F16);
|
||||
|
||||
struct ggml_tensor * result = ggml_mul_mat(ctx, im2col, a);
|
||||
|
||||
@@ -4647,7 +4659,7 @@ struct ggml_tensor * ggml_conv_2d(
|
||||
int p1,
|
||||
int d0,
|
||||
int d1) {
|
||||
struct ggml_tensor * im2col = ggml_im2col(ctx, a, b, s0, s1, p0, p1, d0, d1, true, a->type); // [N, OH, OW, IC * KH * KW]
|
||||
struct ggml_tensor * im2col = ggml_im2col(ctx, a, b, s0, s1, p0, p1, d0, d1, true, a->type == GGML_TYPE_BF16 ? GGML_TYPE_F32 : GGML_TYPE_F16); // [N, OH, OW, IC * KH * KW]
|
||||
|
||||
struct ggml_tensor * result =
|
||||
ggml_mul_mat(ctx,
|
||||
@@ -4729,7 +4741,7 @@ struct ggml_tensor * ggml_conv_3d(
|
||||
int d1, // dilation height
|
||||
int d2 // dilation depth
|
||||
) {
|
||||
struct ggml_tensor * im2col = ggml_im2col_3d(ctx, a, b, IC, s0, s1, s2, p0, p1, p2, d0, d1, d2, a->type); // [N*OD, OH, OW, IC * KD * KH * KW]
|
||||
struct ggml_tensor * im2col = ggml_im2col_3d(ctx, a, b, IC, s0, s1, s2, p0, p1, p2, d0, d1, d2, a->type == GGML_TYPE_BF16 ? GGML_TYPE_F32 : GGML_TYPE_F16); // [N*OD, OH, OW, IC * KD * KH * KW]
|
||||
|
||||
int64_t OC = a->ne[3] / IC;
|
||||
int64_t N = b->ne[3] / IC;
|
||||
@@ -4779,7 +4791,7 @@ struct ggml_tensor * ggml_conv_2d_dw(
|
||||
struct ggml_tensor * new_a = ggml_reshape_4d(ctx, a, a->ne[0], a->ne[1], 1, a->ne[2] * a->ne[3]);
|
||||
struct ggml_tensor * im2col = ggml_im2col(ctx, new_a,
|
||||
ggml_reshape_4d(ctx, b, b->ne[0], b->ne[1], 1, b->ne[2] * b->ne[3]),
|
||||
s0, s1, p0, p1, d0, d1, true, GGML_TYPE_F16); // [N * IC, OH, OW, KH * KW]
|
||||
s0, s1, p0, p1, d0, d1, true, a->type == GGML_TYPE_BF16 ? GGML_TYPE_F32 : GGML_TYPE_F16); // [N * IC, OH, OW, KH * KW]
|
||||
struct ggml_tensor * new_b = ggml_reshape_4d(ctx, im2col, im2col->ne[0], im2col->ne[2] * im2col->ne[1], b->ne[2], b->ne[3]); // [N * IC, OH, OW, KH * KW] => [N, IC, OH * OW, KH * KW]
|
||||
|
||||
new_a = ggml_reshape_4d(ctx, new_a, (new_a->ne[0] * new_a->ne[1]), new_a->ne[2], new_a->ne[3], 1); // [OC,1, KH, KW] => [1, OC, 1, KH * KW]
|
||||
|
||||
+85
-15
@@ -720,7 +720,7 @@ llama_dsv4_comp_state::llama_dsv4_comp_state(
|
||||
auto it = ctx_map.find(buft);
|
||||
if (it == ctx_map.end()) {
|
||||
ggml_init_params params = {
|
||||
/*.mem_size =*/ size_t(2u*hparams.n_layer()*ggml_tensor_overhead()),
|
||||
/*.mem_size =*/ size_t(2u*(1 + n_stream)*hparams.n_layer()*ggml_tensor_overhead()),
|
||||
/*.mem_buffer =*/ NULL,
|
||||
/*.no_alloc =*/ true,
|
||||
};
|
||||
@@ -767,9 +767,17 @@ llama_dsv4_comp_state::llama_dsv4_comp_state(
|
||||
ggml_format_name(kv, "dsv4_%s_state_kv_l%d", name, il);
|
||||
ggml_format_name(score, "dsv4_%s_state_score_l%d", name, il);
|
||||
|
||||
std::vector<ggml_tensor *> kv_stream;
|
||||
std::vector<ggml_tensor *> score_stream;
|
||||
|
||||
for (uint32_t s = 0; s < n_stream; ++s) {
|
||||
kv_stream.push_back(ggml_view_2d(ctx, kv, n_embd_state, state_size, kv->nb[1], s*kv->nb[2]));
|
||||
score_stream.push_back(ggml_view_2d(ctx, score, n_embd_state, state_size, score->nb[1], s*score->nb[2]));
|
||||
}
|
||||
|
||||
map_layer_ids[il] = layers.size();
|
||||
|
||||
layers.push_back({ il, kv, score });
|
||||
layers.push_back({ il, kv, score, std::move(kv_stream), std::move(score_stream) });
|
||||
}
|
||||
|
||||
for (auto & [buft, ctx] : ctx_map) {
|
||||
@@ -809,6 +817,30 @@ void llama_dsv4_comp_state::clear(llama_seq_id seq_id, bool data) {
|
||||
}
|
||||
}
|
||||
|
||||
void llama_dsv4_comp_state::seq_cp(llama_seq_id seq_id_src, llama_seq_id seq_id_dst) {
|
||||
GGML_ASSERT(seq_id_src >= 0 && (uint32_t) seq_id_src < n_stream);
|
||||
GGML_ASSERT(seq_id_dst >= 0 && (uint32_t) seq_id_dst < n_stream);
|
||||
|
||||
if (seq_id_src == seq_id_dst) {
|
||||
return;
|
||||
}
|
||||
|
||||
sc_info.ssrc.push_back((uint32_t) seq_id_src);
|
||||
sc_info.sdst.push_back((uint32_t) seq_id_dst);
|
||||
}
|
||||
|
||||
void llama_dsv4_comp_state::apply_copies(const stream_copy_info & sc_info) const {
|
||||
for (size_t i = 0; i < sc_info.ssrc.size(); ++i) {
|
||||
const uint32_t ssrc = sc_info.ssrc[i];
|
||||
const uint32_t sdst = sc_info.sdst[i];
|
||||
|
||||
for (const auto & layer : layers) {
|
||||
ggml_backend_tensor_copy(layer.kv_stream[ssrc], layer.kv_stream[sdst]);
|
||||
ggml_backend_tensor_copy(layer.score_stream[ssrc], layer.score_stream[sdst]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t llama_dsv4_comp_state::get_ratio() const {
|
||||
return ratio;
|
||||
}
|
||||
@@ -1154,7 +1186,13 @@ llama_memory_context_ptr llama_kv_cache_dsv4::init_full() {
|
||||
}
|
||||
|
||||
llama_memory_context_ptr llama_kv_cache_dsv4::init_update(llama_context * lctx, bool optimize) {
|
||||
return std::make_unique<llama_kv_cache_dsv4_context>(this, lctx, optimize);
|
||||
return std::make_unique<llama_kv_cache_dsv4_context>(
|
||||
this,
|
||||
lctx,
|
||||
optimize,
|
||||
std::move(csa_state->sc_info),
|
||||
std::move(hca_state->sc_info),
|
||||
std::move(lid_state->sc_info));
|
||||
}
|
||||
|
||||
bool llama_kv_cache_dsv4::get_can_shift() const {
|
||||
@@ -1174,14 +1212,19 @@ bool llama_kv_cache_dsv4::seq_rm(llama_seq_id seq_id, llama_pos p0, llama_pos p1
|
||||
}
|
||||
|
||||
if (p0 > 0) {
|
||||
// DSV4 compressed cache rows are derived from running compressor state,
|
||||
// so arbitrary rollback is not reconstructible from the raw cache alone.
|
||||
// Allow the common prompt-cache cleanup no-op: remove [end, infinity).
|
||||
if (seq_id >= 0 && p0 > kv_raw->seq_pos_max(seq_id)) {
|
||||
return true;
|
||||
if (seq_id < 0 || (uint32_t) seq_id >= n_seq_max ||
|
||||
p0 <= kv_raw->seq_pos_max(seq_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
bool res = true;
|
||||
|
||||
res = res & kv_raw->seq_rm(seq_id, p0, -1);
|
||||
res = res & kv_csa->seq_rm(seq_id, p0/DSV4_CSA_RATIO, -1);
|
||||
res = res & kv_hca->seq_rm(seq_id, p0/DSV4_HCA_RATIO, -1);
|
||||
res = res & kv_lid->seq_rm(seq_id, p0/DSV4_CSA_RATIO, -1);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
const bool res = kv_raw->seq_rm(seq_id, p0, p1);
|
||||
@@ -1194,7 +1237,16 @@ bool llama_kv_cache_dsv4::seq_rm(llama_seq_id seq_id, llama_pos p0, llama_pos p1
|
||||
}
|
||||
|
||||
void llama_kv_cache_dsv4::seq_cp(llama_seq_id seq_id_src, llama_seq_id seq_id_dst, llama_pos p0, llama_pos p1) {
|
||||
GGML_ASSERT(p0 <= 0 && p1 < 0 && "DSV4 only supports full sequence copies");
|
||||
|
||||
kv_raw->seq_cp(seq_id_src, seq_id_dst, p0, p1);
|
||||
kv_csa->seq_cp(seq_id_src, seq_id_dst, -1, -1);
|
||||
kv_hca->seq_cp(seq_id_src, seq_id_dst, -1, -1);
|
||||
kv_lid->seq_cp(seq_id_src, seq_id_dst, -1, -1);
|
||||
|
||||
csa_state->seq_cp(seq_id_src, seq_id_dst);
|
||||
hca_state->seq_cp(seq_id_src, seq_id_dst);
|
||||
lid_state->seq_cp(seq_id_src, seq_id_dst);
|
||||
}
|
||||
|
||||
void llama_kv_cache_dsv4::seq_keep(llama_seq_id seq_id) {
|
||||
@@ -1639,20 +1691,26 @@ llama_kv_cache_dsv4_context::llama_kv_cache_dsv4_context(
|
||||
llama_kv_cache_dsv4_context::llama_kv_cache_dsv4_context(
|
||||
llama_kv_cache_dsv4 * kv,
|
||||
llama_context * lctx,
|
||||
bool optimize) :
|
||||
bool optimize,
|
||||
stream_copy_info sc_info_csa,
|
||||
stream_copy_info sc_info_hca,
|
||||
stream_copy_info sc_info_lid) :
|
||||
ctx_raw(std::make_unique<llama_kv_cache_dsv4_raw_context>(kv->get_raw(), lctx, optimize)),
|
||||
ctx_csa_mem(kv->get_csa()->init_update(lctx, optimize)),
|
||||
ctx_hca_mem(kv->get_hca()->init_update(lctx, optimize)),
|
||||
ctx_lid_mem(kv->get_lid()->init_update(lctx, optimize)),
|
||||
ctx_csa(std::make_unique<llama_kv_cache_dsv4_comp_context>(kv->get_csa())),
|
||||
ctx_hca(std::make_unique<llama_kv_cache_dsv4_comp_context>(kv->get_hca())),
|
||||
ctx_lid(std::make_unique<llama_kv_cache_dsv4_comp_context>(kv->get_lid())),
|
||||
csa_state(kv->get_csa_state()),
|
||||
hca_state(kv->get_hca_state()),
|
||||
lid_state(kv->get_lid_state()),
|
||||
sc_info_csa(std::move(sc_info_csa)),
|
||||
sc_info_hca(std::move(sc_info_hca)),
|
||||
sc_info_lid(std::move(sc_info_lid)),
|
||||
status(llama_memory_status_combine(
|
||||
llama_memory_status_combine(ctx_raw->get_status(), ctx_csa_mem->get_status()),
|
||||
llama_memory_status_combine(ctx_hca_mem->get_status(), ctx_lid_mem->get_status()))) {
|
||||
llama_memory_status_combine(
|
||||
llama_memory_status_combine(ctx_raw->get_status(), ctx_csa_mem->get_status()),
|
||||
llama_memory_status_combine(ctx_hca_mem->get_status(), ctx_lid_mem->get_status())),
|
||||
this->sc_info_csa.empty() && this->sc_info_hca.empty() && this->sc_info_lid.empty() ?
|
||||
LLAMA_MEMORY_STATUS_NO_UPDATE : LLAMA_MEMORY_STATUS_SUCCESS)) {
|
||||
}
|
||||
|
||||
llama_kv_cache_dsv4_context::llama_kv_cache_dsv4_context(
|
||||
@@ -1720,6 +1778,18 @@ bool llama_kv_cache_dsv4_context::apply() {
|
||||
|
||||
res = res & ctx_raw->apply();
|
||||
|
||||
if (ctx_csa_mem) {
|
||||
res = res & ctx_csa_mem->apply();
|
||||
res = res & ctx_hca_mem->apply();
|
||||
res = res & ctx_lid_mem->apply();
|
||||
}
|
||||
|
||||
if (ubatches.empty()) {
|
||||
csa_state->apply_copies(sc_info_csa);
|
||||
hca_state->apply_copies(sc_info_hca);
|
||||
lid_state->apply_copies(sc_info_lid);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
|
||||
class llama_dsv4_comp_state {
|
||||
public:
|
||||
using stream_copy_info = llama_kv_cache::stream_copy_info;
|
||||
|
||||
stream_copy_info sc_info;
|
||||
|
||||
llama_dsv4_comp_state(
|
||||
const llama_model & model,
|
||||
bool offload,
|
||||
@@ -22,6 +26,8 @@ public:
|
||||
const llama_memory_i::layer_filter_cb & filter);
|
||||
|
||||
void clear(llama_seq_id seq_id, bool data);
|
||||
void seq_cp(llama_seq_id seq_id_src, llama_seq_id seq_id_dst);
|
||||
void apply_copies(const stream_copy_info & sc_info) const;
|
||||
|
||||
uint32_t get_ratio() const;
|
||||
uint32_t get_state_size() const;
|
||||
@@ -44,6 +50,9 @@ private:
|
||||
|
||||
ggml_tensor * kv;
|
||||
ggml_tensor * score;
|
||||
|
||||
std::vector<ggml_tensor *> kv_stream;
|
||||
std::vector<ggml_tensor *> score_stream;
|
||||
};
|
||||
|
||||
const uint32_t ratio;
|
||||
@@ -245,6 +254,7 @@ private:
|
||||
class llama_kv_cache_dsv4_context : public llama_memory_context_i {
|
||||
public:
|
||||
using slot_info_vec_t = llama_kv_cache::slot_info_vec_t;
|
||||
using stream_copy_info = llama_kv_cache::stream_copy_info;
|
||||
|
||||
struct comp_plan {
|
||||
// Per-ubatch recipe for updating compressor state, committing completed
|
||||
@@ -291,7 +301,10 @@ public:
|
||||
llama_kv_cache_dsv4_context(
|
||||
llama_kv_cache_dsv4 * kv,
|
||||
llama_context * lctx,
|
||||
bool optimize);
|
||||
bool optimize,
|
||||
stream_copy_info sc_info_csa,
|
||||
stream_copy_info sc_info_hca,
|
||||
stream_copy_info sc_info_lid);
|
||||
|
||||
llama_kv_cache_dsv4_context(
|
||||
llama_kv_cache_dsv4 * kv,
|
||||
@@ -351,9 +364,13 @@ private:
|
||||
const std::unique_ptr<llama_kv_cache_dsv4_comp_context> ctx_hca;
|
||||
const std::unique_ptr<llama_kv_cache_dsv4_comp_context> ctx_lid;
|
||||
|
||||
const llama_dsv4_comp_state * csa_state = nullptr;
|
||||
const llama_dsv4_comp_state * hca_state = nullptr;
|
||||
const llama_dsv4_comp_state * lid_state = nullptr;
|
||||
llama_dsv4_comp_state * csa_state = nullptr;
|
||||
llama_dsv4_comp_state * hca_state = nullptr;
|
||||
llama_dsv4_comp_state * lid_state = nullptr;
|
||||
|
||||
stream_copy_info sc_info_csa;
|
||||
stream_copy_info sc_info_hca;
|
||||
stream_copy_info sc_info_lid;
|
||||
|
||||
bool reserve_plans = false;
|
||||
mutable comp_plan reserve_plan_csa;
|
||||
|
||||
+2
-2
@@ -673,7 +673,7 @@ static ggml_type llama_tensor_get_type(quantize_state_impl & qs, const llama_mod
|
||||
ggml_type new_type = default_type;
|
||||
|
||||
// get more optimal quantization type based on the tensor shape, layer, etc.
|
||||
if (!params->pure && ggml_is_quantized(default_type)) {
|
||||
if (ggml_is_quantized(default_type)) {
|
||||
// if the user provided tensor types - use those
|
||||
bool manual = false;
|
||||
if (!qs.tensor_type_patterns.empty()) {
|
||||
@@ -692,7 +692,7 @@ static ggml_type llama_tensor_get_type(quantize_state_impl & qs, const llama_mod
|
||||
}
|
||||
|
||||
// if not manual - use the standard logic for choosing the quantization type based on the selected mixture
|
||||
if (!manual) {
|
||||
if (!manual && !params->pure) {
|
||||
new_type = llama_tensor_get_type_impl(qs, new_type, tensor, params->ftype, tm.category);
|
||||
}
|
||||
|
||||
|
||||
+14
-12
@@ -435,27 +435,29 @@ ggml_tensor * llama_model_deepseek4::graph::build_overlap_compressed_kv_from_sta
|
||||
kv_state = dsv4_append_zero_row(ctx0, kv_state, false);
|
||||
score_state = dsv4_append_zero_row(ctx0, score_state, true);
|
||||
|
||||
ggml_tensor * prev_idxs = dsv4_view_1d(ctx0, state_read_idxs, ratio*n_blocks, 0);
|
||||
ggml_tensor * cur_idxs = dsv4_view_1d(ctx0, state_read_idxs, ratio*n_blocks, ratio*n_blocks);
|
||||
const int64_t n_read = ratio*n_blocks;
|
||||
|
||||
ggml_tensor * kv_prev = ggml_get_rows(ctx0, kv_state, prev_idxs);
|
||||
kv_prev = ggml_cont(ctx0, ggml_view_2d(ctx0, kv_prev, n_embd_head, ratio*n_blocks, kv_prev->nb[1], 0));
|
||||
ggml_tensor * kv_rows = ggml_get_rows(ctx0, kv_state, state_read_idxs);
|
||||
ggml_tensor * score_rows = ggml_get_rows(ctx0, score_state, state_read_idxs);
|
||||
|
||||
ggml_tensor * kv_prev = ggml_cont(ctx0,
|
||||
ggml_view_2d(ctx0, kv_rows, n_embd_head, n_read, kv_rows->nb[1], 0));
|
||||
kv_prev = ggml_reshape_3d(ctx0, kv_prev, n_embd_head, ratio, n_blocks);
|
||||
cb(kv_prev, name, il);
|
||||
|
||||
ggml_tensor * score_prev = ggml_get_rows(ctx0, score_state, prev_idxs);
|
||||
score_prev = ggml_cont(ctx0, ggml_view_2d(ctx0, score_prev, n_embd_head, ratio*n_blocks, score_prev->nb[1], 0));
|
||||
ggml_tensor * score_prev = ggml_cont(ctx0,
|
||||
ggml_view_2d(ctx0, score_rows, n_embd_head, n_read, score_rows->nb[1], 0));
|
||||
score_prev = ggml_reshape_3d(ctx0, score_prev, n_embd_head, ratio, n_blocks);
|
||||
cb(score_prev, name, il);
|
||||
|
||||
ggml_tensor * kv_cur = ggml_get_rows(ctx0, kv_state, cur_idxs);
|
||||
kv_cur = ggml_cont(ctx0, ggml_view_2d(ctx0, kv_cur, n_embd_head, ratio*n_blocks, kv_cur->nb[1],
|
||||
ggml_row_size(kv_cur->type, n_embd_head)));
|
||||
ggml_tensor * kv_cur = ggml_cont(ctx0,
|
||||
ggml_view_2d(ctx0, kv_rows, n_embd_head, n_read, kv_rows->nb[1],
|
||||
n_read*kv_rows->nb[1] + ggml_row_size(kv_rows->type, n_embd_head)));
|
||||
kv_cur = ggml_reshape_3d(ctx0, kv_cur, n_embd_head, ratio, n_blocks);
|
||||
|
||||
ggml_tensor * score_cur = ggml_get_rows(ctx0, score_state, cur_idxs);
|
||||
score_cur = ggml_cont(ctx0, ggml_view_2d(ctx0, score_cur, n_embd_head, ratio*n_blocks, score_cur->nb[1],
|
||||
ggml_row_size(score_cur->type, n_embd_head)));
|
||||
ggml_tensor * score_cur = ggml_cont(ctx0,
|
||||
ggml_view_2d(ctx0, score_rows, n_embd_head, n_read, score_rows->nb[1],
|
||||
n_read*score_rows->nb[1] + ggml_row_size(score_rows->type, n_embd_head)));
|
||||
score_cur = ggml_reshape_3d(ctx0, score_cur, n_embd_head, ratio, n_blocks);
|
||||
|
||||
ggml_tensor * values = ggml_concat(ctx0, kv_prev, kv_cur, 1);
|
||||
|
||||
+24
-3
@@ -148,6 +148,8 @@ if (LLAMA_LLGUIDANCE)
|
||||
llama_build_and_test(test-grammar-llguidance.cpp ARGS ${PROJECT_SOURCE_DIR}/models/ggml-vocab-llama-bpe.gguf)
|
||||
endif ()
|
||||
|
||||
llama_build(test-recurrent-state-rollback.cpp get-model.cpp)
|
||||
|
||||
if (NOT WIN32 OR NOT BUILD_SHARED_LIBS)
|
||||
# these tests are disabled on Windows because they use internal functions not exported with LLAMA_API (when building with shared libraries)
|
||||
llama_build_and_test(test-sampling.cpp)
|
||||
@@ -193,6 +195,28 @@ if (NOT WIN32 OR NOT BUILD_SHARED_LIBS)
|
||||
# llama_build_and_test(test-double-float.cpp) # SLOW
|
||||
|
||||
llama_build_and_test(test-llama-archs.cpp)
|
||||
|
||||
set(MODEL_DIR "${CMAKE_CURRENT_BINARY_DIR}/test-models/")
|
||||
file(MAKE_DIRECTORY "${MODEL_DIR}")
|
||||
|
||||
llama_test(
|
||||
test-llama-archs
|
||||
NAME test-generate-models
|
||||
LABEL main
|
||||
ARGS -o "${MODEL_DIR}"
|
||||
)
|
||||
set_tests_properties(test-generate-models PROPERTIES
|
||||
FIXTURES_SETUP generate-models
|
||||
)
|
||||
|
||||
llama_test(
|
||||
test-recurrent-state-rollback
|
||||
LABEL main
|
||||
ARGS -m "${MODEL_DIR}/qwen35-dense.gguf"
|
||||
)
|
||||
set_tests_properties(test-recurrent-state-rollback PROPERTIES
|
||||
FIXTURES_REQUIRED generate-models
|
||||
)
|
||||
endif()
|
||||
|
||||
llama_build_and_test(test-chat-peg-parser.cpp peg-parser/simple-tokenize.cpp)
|
||||
@@ -250,9 +274,6 @@ llama_build_and_test(test-backend-sampler.cpp LABEL "model")
|
||||
llama_build_and_test(test-state-restore-fragmented.cpp LABEL "model" ARGS -m "${MODEL_DEST}")
|
||||
set_tests_properties(test-state-restore-fragmented PROPERTIES FIXTURES_REQUIRED test-download-model)
|
||||
|
||||
llama_build_and_test(test-recurrent-state-rollback.cpp LABEL "model" ARGS -m "${MODEL_DEST}")
|
||||
set_tests_properties(test-recurrent-state-rollback PROPERTIES FIXTURES_REQUIRED test-download-model)
|
||||
|
||||
# Test state save/load functionality
|
||||
llama_build_and_test(test-save-load-state.cpp LABEL "model" ARGS -m "${MODEL_DEST}")
|
||||
set_tests_properties(test-save-load-state PROPERTIES FIXTURES_REQUIRED test-download-model)
|
||||
|
||||
+37
-16
@@ -2423,11 +2423,10 @@ struct test_set_rows : public test_case {
|
||||
|
||||
void initialize_tensors(ggml_context * ctx) override {
|
||||
for (ggml_tensor * t = ggml_get_first_tensor(ctx); t != NULL; t = ggml_get_next_tensor(ctx, t)) {
|
||||
if (ggml_is_view_op(t->op)) {
|
||||
continue;
|
||||
}
|
||||
if (t->type == GGML_TYPE_I64 || t->type == GGML_TYPE_I32) {
|
||||
if (ggml_is_view_op(t->op)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
init_set_rows_row_ids(t, ne[1]);
|
||||
} else {
|
||||
init_tensor_uniform(t);
|
||||
@@ -2450,6 +2449,9 @@ struct test_set_rows : public test_case {
|
||||
err_estimate /= 8.0f;
|
||||
}
|
||||
err_estimate *= err_estimate;
|
||||
if (type_src == GGML_TYPE_F16) {
|
||||
err_estimate *= 16.0f;
|
||||
}
|
||||
err_estimate /= 0.25f*float(ne[0] * r * ne[2]*nr23[0] * ne[3]*nr23[1]);
|
||||
return err_estimate;
|
||||
}
|
||||
@@ -5553,7 +5555,7 @@ struct test_concat : public test_case {
|
||||
const std::array<int64_t, 4> ne_a;
|
||||
const int64_t ne_b_d;
|
||||
const int dim;
|
||||
const int v; // view (1 << 0: non-cont a, 1 << 1: non-cont b)
|
||||
const int v; // view (1 << 0: non-cont a (first 3 dim), 1 << 1: non-cont b (first 3 dim), 1 << 2: non-cont a (last 2 dim), 1 << 3: non-cont b (last 2 dim))
|
||||
|
||||
std::string vars() override {
|
||||
return VARS_TO_STR5(type, ne_a, ne_b_d, dim, v);
|
||||
@@ -5574,6 +5576,13 @@ struct test_concat : public test_case {
|
||||
a = ggml_new_tensor(ctx, type, 4, ne.data());
|
||||
ggml_set_name(a, "a");
|
||||
|
||||
a = ggml_view_4d(ctx, a, ne_a[0], ne_a[1], ne_a[2], ne_a[3], a->nb[1], a->nb[2], a->nb[3], 0);
|
||||
ggml_set_name(a, "view_of_a");
|
||||
} else if (v & 4) {
|
||||
auto ne = ne_a; ne[2] *= 2; ne[3] *= 4;
|
||||
a = ggml_new_tensor(ctx, type, 4, ne.data());
|
||||
ggml_set_name(a, "a");
|
||||
|
||||
a = ggml_view_4d(ctx, a, ne_a[0], ne_a[1], ne_a[2], ne_a[3], a->nb[1], a->nb[2], a->nb[3], 0);
|
||||
ggml_set_name(a, "view_of_a");
|
||||
} else {
|
||||
@@ -5586,6 +5595,13 @@ struct test_concat : public test_case {
|
||||
b = ggml_new_tensor(ctx, type, 4, ne.data());
|
||||
ggml_set_name(b, "b");
|
||||
|
||||
b = ggml_view_4d(ctx, b, ne_b[0], ne_b[1], ne_b[2], ne_b[3], b->nb[1], b->nb[2], b->nb[3], 0);
|
||||
ggml_set_name(b, "view_of_b");
|
||||
} else if (v & 8) {
|
||||
auto ne = ne_b; ne[2] *= 3; ne[3] *= 2;
|
||||
b = ggml_new_tensor(ctx, type, 4, ne.data());
|
||||
ggml_set_name(b, "b");
|
||||
|
||||
b = ggml_view_4d(ctx, b, ne_b[0], ne_b[1], ne_b[2], ne_b[3], b->nb[1], b->nb[2], b->nb[3], 0);
|
||||
ggml_set_name(b, "view_of_b");
|
||||
} else {
|
||||
@@ -7928,17 +7944,19 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, GGML_TYPE_F32, GGML_TYPE_I64, { 1, 8, 1, 3 }, { 1, 1 }, 2, false));
|
||||
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, GGML_TYPE_F32, GGML_TYPE_I32, { 1, 8, 1, 3 }, { 1, 1 }, 2, false));
|
||||
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, GGML_TYPE_Q8_0, GGML_TYPE_I32, { 256, 5, 1, 3 }, { 1, 1, }, 1, false));
|
||||
for (ggml_type type : all_types) {
|
||||
for (int b : {1, 7}) {
|
||||
for (bool v : {false, true}) {
|
||||
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, type, GGML_TYPE_I64, { 256, 5, b, 3 }, { 1, 1, }, 1, v));
|
||||
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, type, GGML_TYPE_I64, { 256, 11, 1, b }, { 2, 3, }, 7, v));
|
||||
for (ggml_type src_type : {GGML_TYPE_F16, GGML_TYPE_F32}) {
|
||||
for (ggml_type type : all_types) {
|
||||
for (int b : {1, 7}) {
|
||||
for (bool v : {false, true}) {
|
||||
test_cases.emplace_back(new test_set_rows(src_type, type, GGML_TYPE_I64, { 256, 5, b, 3 }, { 1, 1, }, 1, v));
|
||||
test_cases.emplace_back(new test_set_rows(src_type, type, GGML_TYPE_I64, { 256, 11, 1, b }, { 2, 3, }, 7, v));
|
||||
|
||||
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, type, GGML_TYPE_I64, { 3*ggml_blck_size(type), 3, b, 1 }, { 2, 3, }, 2, v));
|
||||
test_cases.emplace_back(new test_set_rows(src_type, type, GGML_TYPE_I64, { 3*ggml_blck_size(type), 3, b, 1 }, { 2, 3, }, 2, v));
|
||||
|
||||
if (ggml_blck_size(type) == 1) {
|
||||
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, type, GGML_TYPE_I64, { 31, 3, b, 1 }, { 2, 3, }, 2, v));
|
||||
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, type, GGML_TYPE_I64, { 33, 5, 1, b }, { 2, 3, }, 1, v));
|
||||
if (ggml_blck_size(type) == 1) {
|
||||
test_cases.emplace_back(new test_set_rows(src_type, type, GGML_TYPE_I64, { 31, 3, b, 1 }, { 2, 3, }, 2, v));
|
||||
test_cases.emplace_back(new test_set_rows(src_type, type, GGML_TYPE_I64, { 33, 5, 1, b }, { 2, 3, }, 1, v));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8013,6 +8031,7 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
|
||||
// im2col 2D
|
||||
test_cases.emplace_back(new test_im2col(GGML_TYPE_F32, GGML_TYPE_F32, GGML_TYPE_F32));
|
||||
test_cases.emplace_back(new test_im2col(GGML_TYPE_F32, GGML_TYPE_F32, GGML_TYPE_F16));
|
||||
test_cases.emplace_back(new test_im2col(GGML_TYPE_F32, GGML_TYPE_F16, GGML_TYPE_F32));
|
||||
test_cases.emplace_back(new test_im2col(GGML_TYPE_F32, GGML_TYPE_F16, GGML_TYPE_F16));
|
||||
for (int s0 : {1, 3}) {
|
||||
@@ -9084,8 +9103,10 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
}
|
||||
|
||||
for (ggml_type type_a : { GGML_TYPE_Q4_0, GGML_TYPE_Q4_1, GGML_TYPE_Q5_0, GGML_TYPE_Q5_1, GGML_TYPE_Q8_0 }) {
|
||||
for (int dim : { 0, 1, 2, 3, }) {
|
||||
test_cases.emplace_back(new test_concat(type_a, {128, 12, 13, 14}, dim == 0 ? 256 : 7, dim, 0));
|
||||
for (int v : { 0, 4, 8, 12 }) {
|
||||
for (int dim : { 0, 1, 2, 3, }) {
|
||||
test_cases.emplace_back(new test_concat(type_a, {128, 12, 13, 14}, dim == 0 ? 256 : 7, dim, v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -152,6 +152,10 @@ int main(int argc, char ** argv) {
|
||||
init_result = common_init_from_params(params);
|
||||
|
||||
ctx = init_result->context();
|
||||
if (!ctx) {
|
||||
LOG_ERR("failed to initialize params\n");
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
#ifdef LLAMA_HF_FETCH
|
||||
auto [hf_repo, hf_quant] = common_download_split_repo_tag(params.model.hf_repo);
|
||||
|
||||
@@ -465,7 +465,7 @@ static int save_models(const llm_arch target_arch, const size_t seed, const ggml
|
||||
if (!moe && moe_mandatory(arch)) {
|
||||
continue;
|
||||
}
|
||||
if (!llama_model_saver_supports_arch(arch)) {
|
||||
if (!llama_model_saver_supports_arch(arch) || !arch_supported(arch)) {
|
||||
LOG_INF("%s: %s model (%s) is unsupported, skipping\n", __func__, llm_arch_name(arch), moe ? "MoE" : "dense");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ static llama_context * make_ctx(const common_params & params, llama_model * mode
|
||||
static bool decode_tokens(llama_context * ctx, const std::vector<llama_token> & tokens, uint32_t count) {
|
||||
llama_batch batch = llama_batch_init(count, 0, 1);
|
||||
for (uint32_t pos = 0; pos < count; ++pos) {
|
||||
common_batch_add(batch, tokens[pos], pos, { 0 }, false);
|
||||
common_batch_add(batch, tokens[pos], pos, { 0 }, pos + 1 == count);
|
||||
}
|
||||
const bool ok = llama_decode(ctx, batch) == 0;
|
||||
llama_batch_free(batch);
|
||||
@@ -79,7 +79,12 @@ int main(int argc, char ** argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<llama_token> tokens = common_tokenize(ctx_src, "The quick brown fox jumps", true);
|
||||
std::vector<llama_token> tokens;
|
||||
if (llama_vocab_type(vocab) == LLAMA_VOCAB_TYPE_NONE) {
|
||||
tokens = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
} else {
|
||||
tokens = common_tokenize(ctx_src, "The quick brown fox jumps", true);
|
||||
}
|
||||
const uint32_t n_rs_seq = llama_n_rs_seq(ctx_src);
|
||||
if (tokens.size() > n_rs_seq + 1) {
|
||||
tokens.resize(n_rs_seq + 1);
|
||||
|
||||
@@ -207,6 +207,9 @@ public:
|
||||
|
||||
bool empty() const { return tokens.empty(); }
|
||||
|
||||
// true if the sequence actually contains image/audio chunks.
|
||||
bool has_media() const { return !map_idx_to_media.empty(); }
|
||||
|
||||
void clear() {
|
||||
map_idx_to_media.clear();
|
||||
tokens.clear();
|
||||
|
||||
@@ -220,8 +220,6 @@ struct server_slot {
|
||||
return false;
|
||||
}
|
||||
|
||||
GGML_ASSERT(prompt.data.size() == 0);
|
||||
|
||||
const size_t cur_size_tgt = llama_state_seq_get_size_ext(ctx_tgt, id, LLAMA_STATE_SEQ_FLAGS_NONE);
|
||||
const size_t cur_size_dft = ctx_dft ? llama_state_seq_get_size_ext(ctx_dft, id, LLAMA_STATE_SEQ_FLAGS_NONE) : 0;
|
||||
|
||||
@@ -252,11 +250,7 @@ struct server_slot {
|
||||
return res;
|
||||
}
|
||||
|
||||
void prompt_clear(bool allow_processing) {
|
||||
if (!allow_processing) {
|
||||
GGML_ASSERT(!is_processing());
|
||||
}
|
||||
|
||||
void prompt_clear() {
|
||||
SLT_TRC(*this, "clearing prompt with %zu tokens\n", prompt.tokens.size());
|
||||
|
||||
common_context_seq_rm(ctx_tgt, id, -1, -1);
|
||||
@@ -264,7 +258,7 @@ struct server_slot {
|
||||
common_context_seq_rm(ctx_dft, id, -1, -1);
|
||||
}
|
||||
|
||||
prompt.tokens.clear();
|
||||
prompt.clear();
|
||||
}
|
||||
|
||||
std::vector<common_adapter_lora_info> lora;
|
||||
@@ -493,7 +487,7 @@ struct server_slot {
|
||||
|
||||
// do not keep context of the child slots - the parent's context is enough
|
||||
if (task->is_child()) {
|
||||
prompt_clear(false);
|
||||
prompt_clear();
|
||||
}
|
||||
|
||||
reset();
|
||||
@@ -1626,7 +1620,7 @@ private:
|
||||
ret->prompt_save(*prompt_cache);
|
||||
|
||||
if (!ret->prompt_load(*prompt_cache, task.tokens)) {
|
||||
ret->prompt_clear(false);
|
||||
ret->prompt_clear();
|
||||
}
|
||||
|
||||
prompt_cache->update();
|
||||
@@ -1658,7 +1652,7 @@ private:
|
||||
if (slot.prompt.n_tokens() > 0) {
|
||||
SRV_WRN("purging slot %d with %zu tokens\n", slot.id, slot.prompt.tokens.size());
|
||||
|
||||
slot.prompt_clear(false);
|
||||
slot.prompt_clear();
|
||||
|
||||
res = true;
|
||||
|
||||
@@ -1691,7 +1685,7 @@ private:
|
||||
// if lora has changed, check to see if the cache should be cleared
|
||||
if (lora_should_clear_cache(slot.lora, task_loras)) {
|
||||
SLT_TRC(slot, "clearing cache for lora change. %zu loras -> %zu loras\n", slot.lora.size(), task.params.lora.size());
|
||||
slot.prompt.tokens.clear();
|
||||
slot.prompt.clear();
|
||||
} else {
|
||||
SLT_TRC(slot, "keeping cache for alora. %zu target loras\n", task_loras.size());
|
||||
}
|
||||
@@ -2017,10 +2011,13 @@ private:
|
||||
queue_results.send(std::move(res));
|
||||
}
|
||||
|
||||
// if multimodal is enabled, send an error and return false
|
||||
bool check_no_mtmd(const int id_task) {
|
||||
if (mctx) {
|
||||
send_error(id_task, "This feature is not supported by multimodal", ERROR_TYPE_NOT_SUPPORTED);
|
||||
// Gate slot save/restore/erase on slot content (does it hold media),
|
||||
// not model capability: a multimodal model may hold a pure-text slot.
|
||||
bool check_slot_no_media(const server_slot & slot, const int id_task) {
|
||||
if (slot.prompt.tokens.has_media()) {
|
||||
send_error(id_task,
|
||||
"This operation is not supported while the slot holds image/audio tokens (a pure-text prefix is supported)",
|
||||
ERROR_TYPE_NOT_SUPPORTED);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -2405,7 +2402,7 @@ private:
|
||||
|
||||
if (params_base.kv_unified) {
|
||||
// [TAG_IDLE_SLOT_CLEAR]
|
||||
slot.prompt_clear(false);
|
||||
slot.prompt_clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2508,16 +2505,15 @@ private:
|
||||
} break;
|
||||
case SERVER_TASK_TYPE_SLOT_SAVE:
|
||||
{
|
||||
if (!check_no_mtmd(task.id)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const int id_slot = task.slot_action.id_slot;
|
||||
server_slot * slot = get_slot_by_id(id_slot);
|
||||
if (slot == nullptr) {
|
||||
send_error(task, "Invalid slot ID", ERROR_TYPE_INVALID_REQUEST);
|
||||
break;
|
||||
}
|
||||
if (!check_slot_no_media(*slot, task.id)) {
|
||||
break;
|
||||
}
|
||||
if (slot->is_processing()) {
|
||||
// if requested slot is unavailable, we defer this task for processing later
|
||||
SRV_DBG("requested slot is unavailable, defer task, id_task = %d\n", task.id);
|
||||
@@ -2525,13 +2521,13 @@ private:
|
||||
break;
|
||||
}
|
||||
|
||||
const size_t token_count = slot->prompt.tokens.size();
|
||||
const int64_t t_start = ggml_time_us();
|
||||
|
||||
std::string filename = task.slot_action.filename;
|
||||
std::string filepath = task.slot_action.filepath;
|
||||
|
||||
const llama_tokens & tokens = slot->prompt.tokens.get_tokens();
|
||||
const llama_tokens tokens = slot->prompt.tokens.get_text_tokens();
|
||||
const size_t token_count = tokens.size();
|
||||
const size_t nwrite = llama_state_seq_save_file(ctx_tgt, filepath.c_str(), slot->id, tokens.data(), token_count);
|
||||
|
||||
const int64_t t_end = ggml_time_us();
|
||||
@@ -2549,7 +2545,6 @@ private:
|
||||
} break;
|
||||
case SERVER_TASK_TYPE_SLOT_RESTORE:
|
||||
{
|
||||
if (!check_no_mtmd(task.id)) break;
|
||||
const int id_slot = task.slot_action.id_slot;
|
||||
server_slot * slot = get_slot_by_id(id_slot);
|
||||
if (slot == nullptr) {
|
||||
@@ -2573,12 +2568,12 @@ private:
|
||||
size_t token_count = 0;
|
||||
size_t nread = llama_state_seq_load_file(ctx_tgt, filepath.c_str(), slot->id, tokens.data(), tokens.size(), &token_count);
|
||||
if (nread == 0) {
|
||||
slot->prompt.tokens.clear(); // KV may already been invalidated?
|
||||
slot->prompt.clear(); // KV may already been invalidated?
|
||||
send_error(task, "Unable to restore slot, no available space in KV cache or invalid slot save file", ERROR_TYPE_INVALID_REQUEST);
|
||||
break;
|
||||
}
|
||||
tokens.resize(token_count);
|
||||
slot->prompt.tokens.clear();
|
||||
slot->prompt.clear();
|
||||
slot->prompt.tokens.insert(tokens);
|
||||
|
||||
const int64_t t_end = ggml_time_us();
|
||||
@@ -2596,15 +2591,16 @@ private:
|
||||
} break;
|
||||
case SERVER_TASK_TYPE_SLOT_ERASE:
|
||||
{
|
||||
if (!check_no_mtmd(task.id)) {
|
||||
break;
|
||||
}
|
||||
const int id_slot = task.slot_action.id_slot;
|
||||
server_slot * slot = get_slot_by_id(id_slot);
|
||||
if (slot == nullptr) {
|
||||
send_error(task, "Invalid slot ID", ERROR_TYPE_INVALID_REQUEST);
|
||||
break;
|
||||
}
|
||||
// Gate on slot content, consistent with save/restore.
|
||||
if (!check_slot_no_media(*slot, task.id)) {
|
||||
break;
|
||||
}
|
||||
if (slot->is_processing()) {
|
||||
// if requested slot is unavailable, we defer this task for processing later
|
||||
SRV_DBG("requested slot is unavailable, defer task, id_task = %d\n", task.id);
|
||||
@@ -2615,7 +2611,7 @@ private:
|
||||
// Erase token cache
|
||||
const size_t n_erased = slot->prompt.tokens.size();
|
||||
|
||||
slot->prompt_clear(false);
|
||||
slot->prompt_clear();
|
||||
|
||||
auto res = std::make_unique<server_task_result_slot_erase>();
|
||||
res->id = task.id;
|
||||
@@ -2775,6 +2771,27 @@ private:
|
||||
abort_all_slots("pre_decode() failed: " + std::string(e.what()));
|
||||
}
|
||||
|
||||
GGML_ASSERT(batch.slot_batched || batch.size() == 0);
|
||||
|
||||
if (batch.slot_batched) {
|
||||
auto & slot_batched = batch.slot_batched;
|
||||
auto & alora_scale = batch.alora_scale;
|
||||
auto & alora_disabled_id = batch.alora_disabled_id;
|
||||
|
||||
// TODO @ngxson : alora handling is too messy, need to refactor it to be more clear and maintainable
|
||||
// apply lora, only need to do it once per batch
|
||||
common_set_adapter_lora(ctx_tgt, slot_batched->lora);
|
||||
|
||||
// if the lora is temporarily disabled for an alora, re-enable it
|
||||
// for next time
|
||||
if (alora_scale > 0.0f) {
|
||||
SRV_DBG("re-enabling alora with scale %f\n", alora_scale);
|
||||
slot_batched->lora[alora_disabled_id].scale = alora_scale;
|
||||
}
|
||||
|
||||
llama_set_embeddings(ctx_tgt, slot_batched->need_embd());
|
||||
}
|
||||
|
||||
llama_batch batch_view;
|
||||
int32_t off_next = 0;
|
||||
int32_t n_batch = llama_n_batch(ctx_tgt);
|
||||
@@ -2814,7 +2831,6 @@ private:
|
||||
abort_all_slots("post_decode() failed: " + std::string(e.what()));
|
||||
break; // stop any further processing
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2880,7 +2896,7 @@ private:
|
||||
|
||||
new_tokens.resize(slot.prompt.tokens.size() - n_discard);
|
||||
|
||||
slot.prompt.tokens.clear();
|
||||
slot.prompt.clear();
|
||||
slot.prompt.tokens.insert(new_tokens);
|
||||
}
|
||||
|
||||
@@ -3556,25 +3572,6 @@ private:
|
||||
bool decode(int32_t & n_batch, int32_t off, llama_batch & batch_view) {
|
||||
SRV_DBG("n_batch (effective) = %d, off = %d\n", n_batch, off);
|
||||
|
||||
auto & slot_batched = batch.slot_batched;
|
||||
auto & alora_scale = batch.alora_scale;
|
||||
auto & alora_disabled_id = batch.alora_disabled_id;
|
||||
|
||||
// TODO @ngxson : alora handling is too messy, need to refactor it to be more clear and maintainable
|
||||
if (slot_batched) {
|
||||
// apply lora, only need to do it once per batch
|
||||
common_set_adapter_lora(ctx_tgt, slot_batched->lora);
|
||||
|
||||
// if the lora is temporarily disabled for an alora, re-enable it
|
||||
// for next time
|
||||
if (alora_scale > 0.0f) {
|
||||
SRV_DBG("re-enabling alora with scale %f\n", alora_scale);
|
||||
slot_batched->lora[alora_disabled_id].scale = alora_scale;
|
||||
}
|
||||
|
||||
llama_set_embeddings(ctx_tgt, slot_batched->need_embd());
|
||||
}
|
||||
|
||||
if (batch.size() == 0) {
|
||||
SRV_WRN("%s", "no tokens to decode\n");
|
||||
|
||||
@@ -3622,7 +3619,7 @@ private:
|
||||
|
||||
// note: it's complicated to keep track of how much of the current batch has been
|
||||
// processed before the error occurred, so we simply clear the entire context
|
||||
slot.prompt_clear(false);
|
||||
slot.prompt_clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,16 @@ static void log_server_request(const httplib::Request & req, const httplib::Resp
|
||||
SRV_DBG("response: %s\n", res.body.c_str());
|
||||
}
|
||||
|
||||
// returns true if the Origin header value's host is localhost / 127.0.0.1 / ::1 (any port)
|
||||
static bool origin_is_localhost(const std::string & origin) {
|
||||
try {
|
||||
const std::string host = common_http_parse_url(origin).host;
|
||||
return host == "localhost" || host == "127.0.0.1" || host == "::1";
|
||||
} catch (const std::exception &) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// For Google Cloud Platform deployment compatibility
|
||||
struct gcp_params {
|
||||
bool enabled;
|
||||
@@ -266,13 +276,26 @@ bool server_http_context::init(const common_params & params) {
|
||||
};
|
||||
|
||||
// register server middlewares
|
||||
srv->set_pre_routing_handler([middleware_validate_api_key, middleware_server_state](const httplib::Request & req, httplib::Response & res) {
|
||||
res.set_header("Access-Control-Allow-Origin", req.get_header_value("Origin"));
|
||||
srv->set_pre_routing_handler([¶ms, middleware_validate_api_key, middleware_server_state](const httplib::Request & req, httplib::Response & res) {
|
||||
if (params.cors_credentials && params.cors_origins == "*") {
|
||||
// special case: echo back the Origin header to allow any origin to access the server with credentials
|
||||
res.set_header("Access-Control-Allow-Origin", req.get_header_value("Origin"));
|
||||
} else if (params.cors_origins == "localhost") {
|
||||
// special case: only reflect the Origin header if it is a localhost origin
|
||||
std::string origin = req.get_header_value("Origin");
|
||||
if (!origin.empty() && origin_is_localhost(origin)) {
|
||||
res.set_header("Access-Control-Allow-Origin", origin);
|
||||
} else if (!origin.empty()) {
|
||||
SRV_WRN("(CORS) skip non-localhost origin: %s\n", origin.c_str());
|
||||
}
|
||||
} else {
|
||||
res.set_header("Access-Control-Allow-Origin", params.cors_origins);
|
||||
}
|
||||
// If this is OPTIONS request, skip validation because browsers don't include Authorization header
|
||||
if (req.method == "OPTIONS") {
|
||||
res.set_header("Access-Control-Allow-Credentials", "true");
|
||||
res.set_header("Access-Control-Allow-Methods", "GET, POST");
|
||||
res.set_header("Access-Control-Allow-Headers", "*");
|
||||
res.set_header("Access-Control-Allow-Credentials", params.cors_credentials ? "true" : "false");
|
||||
res.set_header("Access-Control-Allow-Methods", params.cors_methods);
|
||||
res.set_header("Access-Control-Allow-Headers", params.cors_headers);
|
||||
res.set_content("", "text/html"); // blank response, no data
|
||||
return httplib::Server::HandlerResponse::Handled; // skip further processing
|
||||
}
|
||||
|
||||
@@ -1646,16 +1646,16 @@ size_t server_prompt_cache::n_tokens() const {
|
||||
size_t res = 0;
|
||||
|
||||
for (const auto & state : states) {
|
||||
res += state.n_tokens();
|
||||
res += state.prompt.n_tokens();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
server_prompt * server_prompt_cache::alloc(const server_prompt & prompt, size_t state_size_tgt, size_t state_size_dft) {
|
||||
server_prompt_cache_state * server_prompt_cache::alloc(const server_prompt & prompt, size_t state_size_tgt, size_t state_size_dft) {
|
||||
// first check if the current state is contained fully in the cache
|
||||
for (auto it = states.begin(); it != states.end(); ++it) {
|
||||
const int cur_lcp_len = it->tokens.get_common_prefix(prompt.tokens);
|
||||
const int cur_lcp_len = it->prompt.tokens.get_common_prefix(prompt.tokens);
|
||||
|
||||
if (cur_lcp_len == (int) prompt.tokens.size()) {
|
||||
SRV_TRC("%s", " - prompt is already in the cache, skipping\n");
|
||||
@@ -1680,9 +1680,9 @@ server_prompt * server_prompt_cache::alloc(const server_prompt & prompt, size_t
|
||||
|
||||
// remove any cached prompts that are fully contained in the current prompt
|
||||
for (auto it = states.begin(); it != states.end();) {
|
||||
const int len = it->tokens.get_common_prefix(prompt.tokens);
|
||||
const int len = it->prompt.tokens.get_common_prefix(prompt.tokens);
|
||||
|
||||
if (len == (int) it->tokens.size()) {
|
||||
if (len == (int) it->prompt.tokens.size()) {
|
||||
SRV_TRC(" - removing obsolete cached prompt with length %d\n", len);
|
||||
|
||||
it = states.erase(it);
|
||||
@@ -1721,12 +1721,14 @@ server_prompt * server_prompt_cache::alloc(const server_prompt & prompt, size_t
|
||||
}
|
||||
|
||||
states.push_back({
|
||||
/*.tokens =*/ prompt.tokens.clone(),
|
||||
/*.data =*/ {
|
||||
/*.prompt =*/ {
|
||||
/*.tokens =*/ prompt.tokens.clone(),
|
||||
/*.checkpoints =*/ prompt.checkpoints,
|
||||
},
|
||||
/*.data =*/ {
|
||||
/*.main =*/ std::move(state_data_tgt),
|
||||
/*.drft =*/ std::move(state_data_dft),
|
||||
},
|
||||
/*.checkpoints =*/ prompt.checkpoints,
|
||||
});
|
||||
|
||||
return &states.back();
|
||||
@@ -1744,9 +1746,9 @@ bool server_prompt_cache::load(server_prompt & prompt, const server_tokens & tok
|
||||
|
||||
// find the most similar cached prompt, that would also preserve the most context
|
||||
for (auto it = states.begin(); it != states.end(); ++it) {
|
||||
const int lcp_cur = it->tokens.get_common_prefix(tokens_new);
|
||||
const int lcp_cur = it->prompt.tokens.get_common_prefix(tokens_new);
|
||||
|
||||
const float f_keep_cur = float(lcp_cur) / it->tokens.size();
|
||||
const float f_keep_cur = float(lcp_cur) / it->prompt.tokens.size();
|
||||
const float sim_cur = float(lcp_cur) / tokens_new.size();
|
||||
|
||||
// don't trash large prompts
|
||||
@@ -1799,7 +1801,7 @@ bool server_prompt_cache::load(server_prompt & prompt, const server_tokens & tok
|
||||
}
|
||||
}
|
||||
|
||||
prompt = std::move(*it_best);
|
||||
prompt = std::move(it_best->prompt);
|
||||
|
||||
states.erase(it_best);
|
||||
}
|
||||
@@ -1836,6 +1838,6 @@ void server_prompt_cache::update() {
|
||||
|
||||
for (const auto & state : states) {
|
||||
SRV_TRC(" - prompt %p: %7d tokens, checkpoints: %2zu, %9.3f MiB\n",
|
||||
(const void *)&state, state.n_tokens(), state.checkpoints.size(), state.size() / (1024.0 * 1024.0));
|
||||
(const void *)&state, state.prompt.n_tokens(), state.prompt.checkpoints.size(), state.size() / (1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
|
||||
+29
-24
@@ -584,32 +584,14 @@ struct server_task_result_apply_lora : server_task_result {
|
||||
virtual json to_json() override;
|
||||
};
|
||||
|
||||
struct server_prompt_data {
|
||||
std::vector<uint8_t> main;
|
||||
std::vector<uint8_t> drft;
|
||||
|
||||
size_t size() const {
|
||||
return main.size() + drft.size();
|
||||
}
|
||||
};
|
||||
|
||||
struct server_prompt {
|
||||
server_tokens tokens;
|
||||
|
||||
server_prompt_data data;
|
||||
|
||||
std::list<common_prompt_checkpoint> checkpoints;
|
||||
|
||||
size_t size() const {
|
||||
size_t res = 0;
|
||||
|
||||
res += data.size();
|
||||
|
||||
for (const auto & ckpt : checkpoints) {
|
||||
res += ckpt.size();
|
||||
}
|
||||
|
||||
return res;
|
||||
void clear() {
|
||||
tokens.clear();
|
||||
checkpoints.clear();
|
||||
}
|
||||
|
||||
int n_tokens() const {
|
||||
@@ -619,19 +601,42 @@ struct server_prompt {
|
||||
server_prompt clone() const {
|
||||
return server_prompt {
|
||||
tokens.clone(),
|
||||
data,
|
||||
checkpoints,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
struct server_prompt_data {
|
||||
std::vector<uint8_t> main;
|
||||
std::vector<uint8_t> drft;
|
||||
|
||||
size_t size() const {
|
||||
return main.size() + drft.size();
|
||||
}
|
||||
};
|
||||
|
||||
struct server_prompt_cache_state {
|
||||
server_prompt prompt;
|
||||
server_prompt_data data;
|
||||
|
||||
size_t size() const {
|
||||
size_t res = data.size();
|
||||
|
||||
for (const auto & ckpt : prompt.checkpoints) {
|
||||
res += ckpt.size();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
struct server_prompt_cache {
|
||||
server_prompt_cache(int32_t limit_size_mib, size_t limit_tokens) {
|
||||
this->limit_size = 1024ull*1024ull*(limit_size_mib < 0 ? 0 : limit_size_mib);
|
||||
this->limit_tokens = limit_tokens;
|
||||
}
|
||||
|
||||
std::list<server_prompt> states;
|
||||
std::list<server_prompt_cache_state> states;
|
||||
|
||||
// in bytes, 0 = no limit
|
||||
size_t limit_size = 0;
|
||||
@@ -643,7 +648,7 @@ struct server_prompt_cache {
|
||||
|
||||
size_t n_tokens() const;
|
||||
|
||||
server_prompt * alloc(const server_prompt & prompt, size_t state_size_main, size_t state_size_drft);
|
||||
server_prompt_cache_state * alloc(const server_prompt & prompt, size_t state_size_main, size_t state_size_drft);
|
||||
|
||||
bool load(server_prompt & prompt, const server_tokens & tokens_new, llama_context * ctx_main, llama_context * ctx_drft, int32_t id_slot);
|
||||
|
||||
|
||||
@@ -289,7 +289,7 @@ struct server_tool_read_file : server_tool {
|
||||
{"function", {
|
||||
{"name", name},
|
||||
{"description", "Read the contents of a file. Optionally specify a 1-based line range. "
|
||||
"If append_loc is true, each line is prefixed with its line number (e.g. \"1\u2192 ...\")."},
|
||||
"If append_loc is true, each line is prefixed with its line number (e.g. \"1\u2192...\")."},
|
||||
{"parameters", {
|
||||
{"type", "object"},
|
||||
{"properties", {
|
||||
@@ -339,7 +339,7 @@ struct server_tool_read_file : server_tool {
|
||||
|
||||
std::string out_line;
|
||||
if (append_loc) {
|
||||
out_line = std::to_string(lineno) + "\u2192 " + line + "\n";
|
||||
out_line = std::to_string(lineno) + "\u2192" + line + "\n";
|
||||
} else {
|
||||
out_line = line + "\n";
|
||||
}
|
||||
|
||||
+25
-11
@@ -303,14 +303,24 @@ int llama_server(common_params & params, int argc, char ** argv) {
|
||||
return res;
|
||||
};
|
||||
|
||||
if (params.cors_origins == "*" && params.api_keys.empty()) {
|
||||
SRV_WRN("%s", "-----------------\n");
|
||||
SRV_WRN("%s", "CORS is set to allow all origins ('*') and no API key is set\n");
|
||||
SRV_WRN("%s", "this can be a security risk (cross-origin attacks)\n");
|
||||
SRV_WRN("%s", "more info: https://github.com/ggml-org/llama.cpp/pull/25655\n");
|
||||
SRV_WRN("%s", "-----------------\n");
|
||||
}
|
||||
|
||||
// CORS proxy (EXPERIMENTAL, only used by the Web UI for MCP)
|
||||
std::vector<std::string> warn_names;
|
||||
if (is_router_server) {
|
||||
warn_names.push_back("router mode");
|
||||
}
|
||||
|
||||
if (params.ui_mcp_proxy) {
|
||||
SRV_WRN("%s", "-----------------\n");
|
||||
SRV_WRN("%s", "CORS proxy is enabled, do not expose server to untrusted environments\n");
|
||||
SRV_WRN("%s", "This feature is EXPERIMENTAL and may be removed or changed in future versions\n");
|
||||
SRV_WRN("%s", "-----------------\n");
|
||||
ctx_http.get ("/cors-proxy", ex_wrapper(proxy_handler_get));
|
||||
ctx_http.post("/cors-proxy", ex_wrapper(proxy_handler_post));
|
||||
warn_names.push_back("MCP proxy (experimental)");
|
||||
} else {
|
||||
ctx_http.get ("/cors-proxy", ex_wrapper(res_403));
|
||||
ctx_http.post("/cors-proxy", ex_wrapper(res_403));
|
||||
@@ -324,17 +334,24 @@ int llama_server(common_params & params, int argc, char ** argv) {
|
||||
SRV_ERR("tools setup failed: %s\n", e.what());
|
||||
return 1;
|
||||
}
|
||||
SRV_WRN("%s", "-----------------\n");
|
||||
SRV_WRN("%s", "Built-in tools are enabled, do not expose server to untrusted environments\n");
|
||||
SRV_WRN("%s", "This feature is EXPERIMENTAL and may be changed in the future\n");
|
||||
SRV_WRN("%s", "-----------------\n");
|
||||
ctx_http.get ("/tools", ex_wrapper(tools.handle_get));
|
||||
ctx_http.post("/tools", ex_wrapper(tools.handle_post));
|
||||
warn_names.push_back("built-in tools (experimental)");
|
||||
} else {
|
||||
ctx_http.get ("/tools", ex_wrapper(res_403));
|
||||
ctx_http.post("/tools", ex_wrapper(res_403));
|
||||
}
|
||||
|
||||
if (warn_names.size() > 0) {
|
||||
SRV_WRN("%s", "-----------------\n");
|
||||
SRV_WRN("%s", "the following feature(s) are enabled:\n");
|
||||
for (const auto & name : warn_names) {
|
||||
SRV_WRN(" %s\n", name.c_str());
|
||||
}
|
||||
SRV_WRN("%s", "do not expose the server to untrusted environments\n");
|
||||
SRV_WRN("%s", "-----------------\n");
|
||||
}
|
||||
|
||||
//
|
||||
// Handle downloading model
|
||||
//
|
||||
@@ -452,9 +469,6 @@ int llama_server(common_params & params, int argc, char ** argv) {
|
||||
SRV_INF("listening on %s\n", ctx_http.listening_address.c_str());
|
||||
|
||||
if (is_router_server) {
|
||||
SRV_WRN("%s", "NOTE: router mode is experimental\n");
|
||||
SRV_WRN("%s", " it is not recommended to use this mode in untrusted environments\n");
|
||||
|
||||
if (!params.models_preset_hf.empty()) {
|
||||
SRV_WRN( "NOTE: using preset.ini from HF repo '%s'\n", params.models_preset_hf.c_str());
|
||||
SRV_WRN("%s", " please only use presets that you can trust! Unknown presets may be unsafe\n");
|
||||
|
||||
@@ -91,7 +91,7 @@ def test_openai_library_correct_api_key():
|
||||
("localhost", "Access-Control-Allow-Origin", "localhost"),
|
||||
("web.mydomain.fr", "Access-Control-Allow-Origin", "web.mydomain.fr"),
|
||||
("origin", "Access-Control-Allow-Credentials", "true"),
|
||||
("web.mydomain.fr", "Access-Control-Allow-Methods", "GET, POST"),
|
||||
("web.mydomain.fr", "Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS"),
|
||||
("web.mydomain.fr", "Access-Control-Allow-Headers", "*"),
|
||||
])
|
||||
def test_cors_options(origin: str, cors_header: str, cors_header_value: str):
|
||||
@@ -107,6 +107,70 @@ def test_cors_options(origin: str, cors_header: str, cors_header_value: str):
|
||||
assert res.headers[cors_header] == cors_header_value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("origin", [
|
||||
"http://localhost",
|
||||
"http://localhost:8080",
|
||||
"http://127.0.0.1",
|
||||
"http://127.0.0.1:3000",
|
||||
"http://[::1]",
|
||||
"http://[::1]:3000",
|
||||
])
|
||||
def test_cors_origins_localhost_reflects(origin: str):
|
||||
global server
|
||||
server = ServerPreset.router()
|
||||
server.cors_origins = "localhost"
|
||||
server.start()
|
||||
res = server.make_request("OPTIONS", "/completions", headers={
|
||||
"Origin": origin,
|
||||
"Access-Control-Request-Method": "POST",
|
||||
"Access-Control-Request-Headers": "Authorization",
|
||||
})
|
||||
assert res.status_code == 200
|
||||
assert res.headers["Access-Control-Allow-Origin"] == origin
|
||||
|
||||
|
||||
@pytest.mark.parametrize("origin", [
|
||||
"http://web.mydomain.fr",
|
||||
"http://evil.com",
|
||||
"http://notlocalhost",
|
||||
"http://localhost.evil.com",
|
||||
])
|
||||
def test_cors_origins_localhost_rejects(origin: str):
|
||||
global server
|
||||
server = ServerPreset.router()
|
||||
server.cors_origins = "localhost"
|
||||
server.start()
|
||||
res = server.make_request("OPTIONS", "/completions", headers={
|
||||
"Origin": origin,
|
||||
"Access-Control-Request-Method": "POST",
|
||||
"Access-Control-Request-Headers": "Authorization",
|
||||
})
|
||||
assert res.status_code == 200
|
||||
assert "Access-Control-Allow-Origin" not in res.headers
|
||||
|
||||
|
||||
def test_cors_origins_defaults_to_localhost_with_tools_enabled():
|
||||
global server
|
||||
server = ServerPreset.router()
|
||||
server.server_tools = "all"
|
||||
server.start()
|
||||
res = server.make_request("OPTIONS", "/completions", headers={
|
||||
"Origin": "http://localhost:8080",
|
||||
"Access-Control-Request-Method": "POST",
|
||||
"Access-Control-Request-Headers": "Authorization",
|
||||
})
|
||||
assert res.status_code == 200
|
||||
assert res.headers["Access-Control-Allow-Origin"] == "http://localhost:8080"
|
||||
|
||||
res = server.make_request("OPTIONS", "/completions", headers={
|
||||
"Origin": "http://evil.com",
|
||||
"Access-Control-Request-Method": "POST",
|
||||
"Access-Control-Request-Headers": "Authorization",
|
||||
})
|
||||
assert res.status_code == 200
|
||||
assert "Access-Control-Allow-Origin" not in res.headers
|
||||
|
||||
|
||||
def test_cors_proxy_only_forwards_explicit_proxy_headers():
|
||||
class CaptureHeadersHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import pytest
|
||||
from utils import *
|
||||
import base64
|
||||
import requests
|
||||
|
||||
server = ServerPreset.tinyllama2()
|
||||
|
||||
@@ -96,3 +98,127 @@ def test_slot_erase():
|
||||
assert res.status_code == 200
|
||||
assert match_regex("(Whiskers|Flana)+", res.body["content"])
|
||||
assert res.body["timings"]["prompt_n"] == 21 # all tokens are processed
|
||||
|
||||
|
||||
#
|
||||
# Multimodal server (mmproj loaded) slot save/restore.
|
||||
#
|
||||
# Regression coverage for issue #21133: slot save/restore/erase must be gated on
|
||||
# the slot's CONTENT (does it actually hold image/audio tokens) rather than the
|
||||
# model's CAPABILITY (is an mmproj loaded). A pure-text slot on a multimodal
|
||||
# server must save/restore/erase normally; a slot that actually holds an image
|
||||
# must be rejected with ERROR_TYPE_NOT_SUPPORTED (HTTP 501).
|
||||
#
|
||||
|
||||
IMG_URL_CAT = "https://huggingface.co/ggml-org/tinygemma3-GGUF/resolve/main/test/91_cat.png"
|
||||
|
||||
|
||||
def _get_img_base64(url: str) -> str:
|
||||
response = requests.get(url)
|
||||
response.raise_for_status() # Raise an exception for bad status codes
|
||||
return base64.b64encode(response.content).decode("utf-8")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mmproj_server():
|
||||
# tinygemma3 is a small multimodal model: the mmproj is provided by the HF
|
||||
# registry API and auto-downloaded on first run.
|
||||
os.environ['LLAMA_MEDIA_MARKER'] = '<__media__>'
|
||||
mm_server = ServerPreset.tinygemma3()
|
||||
mm_server.slot_save_path = "./tmp"
|
||||
mm_server.temperature = 0.0
|
||||
return mm_server
|
||||
|
||||
|
||||
def test_slot_save_restore_text_only_on_multimodal(mmproj_server):
|
||||
server = mmproj_server
|
||||
server.start()
|
||||
|
||||
# A pure-text prompt processed on slot 1 of a multimodal server.
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": "The quick brown fox jumps over the lazy dog.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
prompt_n = res.body["timings"]["prompt_n"]
|
||||
assert prompt_n > 0 # all tokens are processed
|
||||
|
||||
# Saving a pure-text slot must succeed even though an mmproj is loaded.
|
||||
res = server.make_request("POST", "/slots/1?action=save", data={
|
||||
"filename": "mm_slot1.bin",
|
||||
})
|
||||
assert res.status_code == 200
|
||||
n_saved = res.body["n_saved"]
|
||||
assert n_saved > 0 # the slot KV (prompt + generated tokens) was written
|
||||
|
||||
# Restore the saved state into slot 0; it must round-trip exactly.
|
||||
res = server.make_request("POST", "/slots/0?action=restore", data={
|
||||
"filename": "mm_slot1.bin",
|
||||
})
|
||||
assert res.status_code == 200
|
||||
assert res.body["n_restored"] == n_saved
|
||||
|
||||
# The restored slot is usable for a follow-up completion. We do NOT assert
|
||||
# prefix reuse here: tinygemma3 is a SWA model, which forces full prompt
|
||||
# re-processing after a restore (a model property, not the save/restore gate
|
||||
# under test).
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": "The quick brown fox jumps over the lazy dog.",
|
||||
"id_slot": 0,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
|
||||
|
||||
def test_slot_save_rejected_when_slot_holds_image(mmproj_server):
|
||||
server = mmproj_server
|
||||
server.start()
|
||||
|
||||
# Process a prompt that actually contains an image on slot 1.
|
||||
res = server.make_request("POST", "/completions", data={
|
||||
"temperature": 0.0,
|
||||
"top_k": 1,
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
"prompt": {
|
||||
"prompt_string": "What is this: <__media__>\n",
|
||||
"multimodal_data": [ _get_img_base64(IMG_URL_CAT) ],
|
||||
},
|
||||
})
|
||||
assert res.status_code == 200
|
||||
|
||||
# Saving a slot that holds image tokens must be rejected (HTTP 501,
|
||||
# not_supported_error).
|
||||
res = server.make_request("POST", "/slots/1?action=save", data={
|
||||
"filename": "mm_slot_image.bin",
|
||||
})
|
||||
assert res.status_code != 200
|
||||
assert res.body["error"]["type"] == "not_supported_error"
|
||||
|
||||
|
||||
def test_slot_erase_text_only_on_multimodal(mmproj_server):
|
||||
server = mmproj_server
|
||||
server.start()
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": "The quick brown fox jumps over the lazy dog.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
prompt_n = res.body["timings"]["prompt_n"]
|
||||
assert prompt_n > 0 # all tokens are processed
|
||||
|
||||
# Erasing a pure-text slot must succeed even though an mmproj is loaded.
|
||||
res = server.make_request("POST", "/slots/1?action=erase")
|
||||
assert res.status_code == 200
|
||||
|
||||
# Re-running the same prompt should process all tokens again.
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": "The quick brown fox jumps over the lazy dog.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
assert res.body["timings"]["prompt_n"] == prompt_n # all tokens are processed again
|
||||
|
||||
@@ -12,8 +12,9 @@ def create_server():
|
||||
server = ServerPreset.stories15m_moe()
|
||||
# set default values
|
||||
server.model_draft = download_file(MODEL_DRAFT_FILE_URL)
|
||||
server.draft_min = 4
|
||||
server.draft_max = 8
|
||||
server.spec_type = "draft-simple"
|
||||
server.spec_draft_n_min = 4
|
||||
server.spec_draft_n_max = 8
|
||||
server.fa = "off"
|
||||
|
||||
|
||||
@@ -25,6 +26,7 @@ def fixture_create_server():
|
||||
def test_with_and_without_draft():
|
||||
global server
|
||||
server.model_draft = None # disable draft model
|
||||
server.spec_type = None
|
||||
server.start()
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": "I believe the meaning of life is",
|
||||
@@ -46,6 +48,7 @@ def test_with_and_without_draft():
|
||||
"n_predict": 16,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
assert res.body["timings"]["draft_n"] > 0
|
||||
content_draft = res.body["content"]
|
||||
|
||||
assert content_no_draft == content_draft
|
||||
@@ -63,8 +66,8 @@ def test_different_draft_min_draft_max():
|
||||
last_content = None
|
||||
for draft_min, draft_max in test_values:
|
||||
server.stop()
|
||||
server.draft_min = draft_min
|
||||
server.draft_max = draft_max
|
||||
server.spec_draft_n_min = draft_min
|
||||
server.spec_draft_n_max = draft_max
|
||||
server.start()
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": "I believe the meaning of life is",
|
||||
|
||||
@@ -95,6 +95,7 @@ class ServerProcess:
|
||||
no_models_autoload: bool | None = None
|
||||
lora_files: List[str] | None = None
|
||||
enable_ctx_shift: int | None = False
|
||||
spec_type: str | None = None
|
||||
spec_draft_n_min: int | None = None
|
||||
spec_draft_n_max: int | None = None
|
||||
no_ui: bool | None = None
|
||||
@@ -114,6 +115,7 @@ class ServerProcess:
|
||||
backend_sampling: bool = False
|
||||
gcp_compat: bool = False
|
||||
server_tools: str | None = None
|
||||
cors_origins: str | None = None
|
||||
|
||||
# session variables
|
||||
process: subprocess.Popen | None = None
|
||||
@@ -170,6 +172,8 @@ class ServerProcess:
|
||||
server_args.extend(["--models-max", self.models_max])
|
||||
if self.models_preset:
|
||||
server_args.extend(["--models-preset", self.models_preset])
|
||||
if self.cors_origins:
|
||||
server_args.extend(["--cors-origins", self.cors_origins])
|
||||
if self.n_batch:
|
||||
server_args.extend(["--batch-size", self.n_batch])
|
||||
if self.n_ubatch:
|
||||
@@ -223,6 +227,8 @@ class ServerProcess:
|
||||
server_args.extend(["--lora", lora_file])
|
||||
if self.enable_ctx_shift:
|
||||
server_args.append("--context-shift")
|
||||
if self.spec_type:
|
||||
server_args.extend(["--spec-type", self.spec_type])
|
||||
if self.api_key:
|
||||
server_args.extend(["--api-key", self.api_key])
|
||||
if self.spec_draft_n_max:
|
||||
@@ -359,7 +365,7 @@ class ServerProcess:
|
||||
if parse_body:
|
||||
try:
|
||||
result.body = response.json()
|
||||
except JSONDecodeError:
|
||||
except (JSONDecodeError, requests.exceptions.JSONDecodeError):
|
||||
result.body = response.text
|
||||
else:
|
||||
result.body = None
|
||||
|
||||
+62
-259
@@ -1,5 +1,6 @@
|
||||
#include "arg.h"
|
||||
#include "common.h"
|
||||
//#include "log.h" // TODO: start using log.h
|
||||
#include "log.h"
|
||||
#include "llama.h"
|
||||
|
||||
#include <clocale>
|
||||
@@ -8,115 +9,22 @@
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream> // TODO: remove me
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <shellapi.h> // For CommandLineToArgvW
|
||||
#endif
|
||||
|
||||
static void print_usage_information(const char * argv0) {
|
||||
printf("usage: %s [options]\n\n", argv0);
|
||||
printf("The tokenize program tokenizes a prompt using a given model,\n");
|
||||
printf("and prints the resulting tokens to standard output.\n\n");
|
||||
printf("It needs a model file, a prompt, and optionally other flags\n");
|
||||
printf("to control the behavior of the tokenizer.\n\n");
|
||||
printf(" The possible options are:\n");
|
||||
printf("\n");
|
||||
printf(" -h, --help print this help and exit\n");
|
||||
printf(" -m MODEL_PATH, --model MODEL_PATH path to model.\n");
|
||||
printf(" --ids if given, only print numerical token IDs, and not token strings.\n");
|
||||
printf(" The output format looks like [1, 2, 3], i.e. parseable by Python.\n");
|
||||
printf(" -f PROMPT_FNAME, --file PROMPT_FNAME read prompt from a file.\n");
|
||||
printf(" -p PROMPT, --prompt PROMPT read prompt from the argument.\n");
|
||||
printf(" --stdin read prompt from standard input.\n");
|
||||
printf(" --no-bos do not ever add a BOS token to the prompt, even if normally the model uses a BOS token.\n");
|
||||
printf(" --no-escape do not escape input (such as \\n, \\t, etc.).\n");
|
||||
printf(" --no-parse-special do not parse control tokens.\n");
|
||||
printf(" --log-disable disable logs. Makes stderr quiet when loading the model.\n");
|
||||
printf(" --show-count print the total number of tokens.\n");
|
||||
}
|
||||
static void print_usage(int argc, char ** argv) {
|
||||
(void) argc;
|
||||
|
||||
static void llama_log_callback_null(ggml_log_level level, const char * text, void * user_data) {
|
||||
(void) level;
|
||||
(void) text;
|
||||
(void) user_data;
|
||||
}
|
||||
|
||||
static std::string read_prompt_from_file(const char * filepath, bool & success) {
|
||||
success = false;
|
||||
|
||||
std::ifstream in(filepath, std::ios::binary);
|
||||
if (!in) {
|
||||
fprintf(stderr, "%s: could not open file '%s' for reading: %s\n", __func__, filepath, strerror(errno));
|
||||
return std::string();
|
||||
}
|
||||
// do not assume the file is seekable (e.g. /dev/stdin)
|
||||
std::stringstream buffer;
|
||||
buffer << in.rdbuf();
|
||||
if (in.fail()) {
|
||||
fprintf(stderr, "%s: could not read the entire file '%s': %s\n", __func__, filepath, strerror(errno));
|
||||
return std::string();
|
||||
}
|
||||
|
||||
success = true;
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
//
|
||||
// Function: ingest_args(...) -> vector<string>
|
||||
//
|
||||
// Takes argc and argv arguments, and converts them to a vector of UTF-8 encoded
|
||||
// strings, as an STL vector<string>.
|
||||
//
|
||||
// In particular, it handles character encoding shenanigans on Windows.
|
||||
//
|
||||
// Note: raw_argc and raw_argv are not actually read at all on Windows.
|
||||
// On Windows we call GetCommandLineW to get the arguments in wchar_t
|
||||
// format, ignoring the regular argc/argv arguments to main().
|
||||
//
|
||||
// TODO: potential opportunity to roll common stuff into common/console.cpp
|
||||
// in relation to Windows wchar_t shenanigans.
|
||||
static std::vector<std::string> ingest_args(int raw_argc, char ** raw_argv) {
|
||||
std::vector<std::string> argv;
|
||||
|
||||
// Handle Windows, if given non-ASCII arguments.
|
||||
// We convert wchar_t arguments into UTF-8 char* on this platform.
|
||||
// Lets you invoke 'tokenize' on Windows cmd.exe with non-ASCII characters
|
||||
// without throwing tantrums.
|
||||
#if defined(_WIN32)
|
||||
int argc;
|
||||
const LPWSTR cmdline_wargv = GetCommandLineW();
|
||||
LPWSTR * wargv = CommandLineToArgvW(cmdline_wargv, &argc);
|
||||
|
||||
// silence unused arg warnings
|
||||
(void) raw_argc;
|
||||
(void) raw_argv;
|
||||
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
int length_needed = WideCharToMultiByte(CP_UTF8, 0, wargv[i], wcslen(wargv[i]), 0, 0, NULL, NULL);
|
||||
char * output_buf = (char *) calloc(length_needed+1, sizeof(char));
|
||||
GGML_ASSERT(output_buf);
|
||||
|
||||
WideCharToMultiByte(CP_UTF8, 0, wargv[i], wcslen(wargv[i]), output_buf, length_needed, NULL, NULL);
|
||||
output_buf[length_needed] = '\0';
|
||||
|
||||
argv.push_back(output_buf);
|
||||
free(output_buf);
|
||||
}
|
||||
|
||||
LocalFree((HLOCAL) wargv);
|
||||
#else
|
||||
int argc = raw_argc;
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
argv.push_back(raw_argv[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
GGML_ASSERT((unsigned int) argc == argv.size());
|
||||
|
||||
return argv;
|
||||
LOG("\nexample usage:\n");
|
||||
LOG("\n %s -m your_model.gguf -p \"Hello world\"\n", argv[0]);
|
||||
LOG("\n %s -m your_model.gguf -f prompt.txt --ids\n", argv[0]);
|
||||
LOG("\n cat prompt.txt | %s -m your_model.gguf --stdin --show-count\n", argv[0]);
|
||||
LOG("\n");
|
||||
}
|
||||
|
||||
//
|
||||
@@ -184,166 +92,61 @@ static void write_utf8_cstr_to_stdout(const char * str, bool & invalid_utf8) {
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int raw_argc, char ** raw_argv) {
|
||||
int main(int argc, char ** argv) {
|
||||
std::setlocale(LC_NUMERIC, "C");
|
||||
|
||||
const std::vector<std::string> argv = ingest_args(raw_argc, raw_argv);
|
||||
const int argc = argv.size();
|
||||
common_params params;
|
||||
|
||||
if (argc <= 1) {
|
||||
print_usage_information(argv[0].c_str());
|
||||
common_init();
|
||||
|
||||
if (!common_params_parse(argc, argv, params, LLAMA_EXAMPLE_TOKENIZE, print_usage)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
//////
|
||||
// Read out all the command line arguments.
|
||||
//////
|
||||
// -f and -p both land in params.prompt; -f also sets prompt_file. -f and -p
|
||||
// resolve like the other tools (no mutual exclusion), --stdin takes precedence.
|
||||
const bool use_stdin = params.tokenize_stdin;
|
||||
const bool use_file = !params.prompt_file.empty();
|
||||
|
||||
// variables where to put any arguments we see.
|
||||
bool printing_ids = false;
|
||||
bool no_bos = false;
|
||||
bool no_escape = false;
|
||||
bool no_parse_special = false;
|
||||
bool disable_logging = false;
|
||||
bool show_token_count = false;
|
||||
const char * model_path = NULL;
|
||||
const char * prompt_path = NULL;
|
||||
const char * prompt_arg = NULL;
|
||||
|
||||
// track which arguments were explicitly given
|
||||
// used for sanity checking down the line
|
||||
bool model_path_set = false;
|
||||
bool prompt_path_set = false;
|
||||
bool prompt_set = false;
|
||||
bool stdin_set = false;
|
||||
|
||||
int iarg = 1;
|
||||
for (; iarg < argc; ++iarg) {
|
||||
std::string arg{argv[iarg]};
|
||||
if (arg == "-h" || arg == "--help") {
|
||||
print_usage_information(argv[0].c_str());
|
||||
return 0;
|
||||
}
|
||||
else if (arg == "--ids") {
|
||||
printing_ids = true;
|
||||
}
|
||||
else if (arg == "-m" || arg == "--model") {
|
||||
if (model_path_set) {
|
||||
fprintf(stderr, "Error: -m or --model specified multiple times.\n");
|
||||
return 1;
|
||||
}
|
||||
model_path = argv[++iarg].c_str();
|
||||
model_path_set = true;
|
||||
}
|
||||
else if (arg == "--no-bos") {
|
||||
no_bos = true;
|
||||
}
|
||||
else if (arg == "--no-escape") {
|
||||
no_escape = true;
|
||||
}
|
||||
else if (arg == "--no-parse-special") {
|
||||
no_parse_special = true;
|
||||
}
|
||||
else if (arg == "-p" || arg == "--prompt") {
|
||||
if (prompt_set) {
|
||||
fprintf(stderr, "Error: -p or --prompt specified multiple times.\n");
|
||||
return 1;
|
||||
}
|
||||
prompt_arg = argv[++iarg].c_str();
|
||||
prompt_set = true;
|
||||
}
|
||||
else if (arg == "-f" || arg == "--file") {
|
||||
if (prompt_path_set) {
|
||||
fprintf(stderr, "Error: -f or --file specified multiple times.\n");
|
||||
return 1;
|
||||
}
|
||||
prompt_path = argv[++iarg].c_str();
|
||||
prompt_path_set = true;
|
||||
}
|
||||
else if (arg == "--stdin") {
|
||||
stdin_set = true;
|
||||
}
|
||||
else if (arg == "--log-disable") {
|
||||
disable_logging = true;
|
||||
}
|
||||
else if (arg == "--show-count") {
|
||||
show_token_count = true;
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "Error: unknown option '%s'\n", argv[iarg].c_str());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
//////
|
||||
// Sanity check the command line arguments.
|
||||
//////
|
||||
|
||||
// Check that we have the required stuff set.
|
||||
if (model_path_set && model_path == NULL) {
|
||||
fprintf(stderr, "Error: --model requires an argument.\n");
|
||||
// must have some prompt
|
||||
if (!use_stdin && !use_file && params.prompt.empty()) {
|
||||
LOG_ERR("error: must specify one of: --stdin, --file or --prompt\n");
|
||||
return 1;
|
||||
}
|
||||
if (!model_path_set) {
|
||||
fprintf(stderr, "Error: must specify --model.\n");
|
||||
return 1;
|
||||
}
|
||||
if (prompt_path_set && prompt_path == NULL) {
|
||||
fprintf(stderr, "Error: --file requires an argument.\n");
|
||||
return 1;
|
||||
}
|
||||
if (prompt_set && prompt_arg == NULL) {
|
||||
fprintf(stderr, "Error: --prompt requires an argument.\n");
|
||||
return 1;
|
||||
}
|
||||
const int prompts_set = !!(prompt_path_set) + !!(prompt_set) + !!(stdin_set);
|
||||
if (prompts_set > 1) {
|
||||
fprintf(stderr, "Error: --stdin, --file and --prompt are mutually exclusive.\n");
|
||||
return 1;
|
||||
}
|
||||
// Must have some prompt.
|
||||
if (prompts_set == 0) {
|
||||
fprintf(stderr, "Error: must specify one of: --stdin, --file or --prompt.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
GGML_ASSERT(model_path);
|
||||
GGML_ASSERT(prompt_path || prompt_arg || stdin_set);
|
||||
|
||||
//////
|
||||
// Figure out where will the prompt come from.
|
||||
//////
|
||||
|
||||
std::string prompt;
|
||||
if (prompt_path_set) {
|
||||
bool success = false;
|
||||
prompt = read_prompt_from_file(prompt_path, success);
|
||||
if (!success) {
|
||||
if (use_file) {
|
||||
// read the file verbatim: common's -f handler strips a single trailing
|
||||
// newline, but for a tokenizer the input bytes must be preserved exactly
|
||||
// (a trailing newline is itself a token). escapes are applied locally
|
||||
// to match the behavior of -p/--prompt and --stdin.
|
||||
std::ifstream in(params.prompt_file, std::ios::binary);
|
||||
if (!in) {
|
||||
LOG_ERR("error: could not open file '%s' for reading\n", params.prompt_file.c_str());
|
||||
return 1;
|
||||
}
|
||||
} else if (prompt_set) {
|
||||
prompt = prompt_arg;
|
||||
} else {
|
||||
GGML_ASSERT(stdin_set);
|
||||
// we read stdin *after* loading model (early exit if model cannot
|
||||
// be loaded, which can be a nicer user experience)
|
||||
}
|
||||
|
||||
//////
|
||||
// Start actually doing the tokenizing stuff.
|
||||
//////
|
||||
|
||||
if (disable_logging) {
|
||||
llama_log_set(llama_log_callback_null, NULL);
|
||||
std::stringstream ss;
|
||||
ss << in.rdbuf();
|
||||
prompt = ss.str();
|
||||
if (params.escape) {
|
||||
string_process_escapes(prompt);
|
||||
}
|
||||
} else if (!use_stdin) {
|
||||
// -p/--prompt is already escape-processed by common_params_parse()
|
||||
// (controlled by --escape/--no-escape), so use it verbatim here.
|
||||
prompt = params.prompt;
|
||||
}
|
||||
// else: we read stdin *after* loading the model (early exit if the
|
||||
// model cannot be loaded, which is a nicer user experience)
|
||||
|
||||
llama_backend_init();
|
||||
|
||||
// load only the vocabulary (no weights), since tokenizing does not need them
|
||||
llama_model_params model_params = llama_model_default_params();
|
||||
model_params.vocab_only = true;
|
||||
llama_model * model = llama_model_load_from_file(model_path, model_params);
|
||||
llama_model * model = llama_model_load_from_file(params.model.path.c_str(), model_params);
|
||||
if (!model) {
|
||||
fprintf(stderr, "Error: could not load model from file '%s'.\n", model_path);
|
||||
LOG_ERR("error: could not load model from file '%s'.\n", params.model.path.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -352,42 +155,41 @@ int main(int raw_argc, char ** raw_argv) {
|
||||
llama_context_params ctx_params = llama_context_default_params();
|
||||
llama_context * ctx = llama_init_from_model(model, ctx_params);
|
||||
if (!ctx) {
|
||||
fprintf(stderr, "Error: could not create context.\n");
|
||||
LOG_ERR("error: could not create context.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// read entire prompt from stdin?
|
||||
if (stdin_set) {
|
||||
GGML_ASSERT(!prompt_path_set && !prompt_set);
|
||||
|
||||
if (params.tokenize_stdin) {
|
||||
std::stringstream stdin_buffer;
|
||||
stdin_buffer << std::cin.rdbuf();
|
||||
if (std::cin.fail()) {
|
||||
fprintf(stderr, "Error: could not read the entire standard input.\n");
|
||||
LOG_ERR("error: could not read the entire standard input.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
prompt = stdin_buffer.str();
|
||||
|
||||
// stdin is not seen by common_params_parse(), so apply escape handling
|
||||
// here to match the behavior of -p/--prompt and -f/--file.
|
||||
if (params.escape) {
|
||||
string_process_escapes(prompt);
|
||||
}
|
||||
}
|
||||
|
||||
const bool model_wants_add_bos = llama_vocab_get_add_bos(vocab);
|
||||
const bool add_bos = model_wants_add_bos && !no_bos;
|
||||
const bool parse_special = !no_parse_special;
|
||||
const bool escape = !no_escape;
|
||||
|
||||
if (escape) {
|
||||
string_process_escapes(prompt);
|
||||
}
|
||||
const bool add_bos = model_wants_add_bos && !params.tokenize_no_bos;
|
||||
const bool parse_special = params.parse_special;
|
||||
|
||||
std::vector<llama_token> tokens;
|
||||
tokens = common_tokenize(vocab, prompt, add_bos, parse_special);
|
||||
|
||||
if (printing_ids) {
|
||||
if (params.tokenize_ids) {
|
||||
printf("[");
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int) tokens.size(); i++) {
|
||||
if (printing_ids) {
|
||||
if (params.tokenize_ids) {
|
||||
if (i > 0) {
|
||||
printf(", ");
|
||||
}
|
||||
@@ -404,13 +206,14 @@ int main(int raw_argc, char ** raw_argv) {
|
||||
}
|
||||
}
|
||||
|
||||
if (printing_ids) {
|
||||
if (params.tokenize_ids) {
|
||||
printf("]\n");
|
||||
}
|
||||
|
||||
if (show_token_count) {
|
||||
if (params.tokenize_show_count) {
|
||||
printf("Total number of tokens: %zu\n", tokens.size());
|
||||
}
|
||||
|
||||
// silence valgrind
|
||||
llama_free(ctx);
|
||||
llama_model_free(model);
|
||||
|
||||
@@ -29,6 +29,13 @@ export default ts.config(
|
||||
// This app uses hash-based routing (#/) where resolve() from $app/paths does not apply
|
||||
'svelte/no-navigation-without-resolve': 'off',
|
||||
|
||||
// Snippet bodies often ignore one or more of the parent's params
|
||||
// (e.g. `{#snippet children(_meta, ctx)}` when only ctx is read).
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' }
|
||||
],
|
||||
|
||||
// Enforce empty line at end of file
|
||||
'eol-last': 'error'
|
||||
}
|
||||
|
||||
@@ -193,6 +193,33 @@
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.shimmer-text {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--muted-foreground),
|
||||
var(--foreground),
|
||||
var(--muted-foreground)
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
font-weight: 500;
|
||||
animation: shimmer 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
to {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.shimmer-text {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mermaidTooltip {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { Copy } from '@lucide/svelte';
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
import ActionIcon from './ActionIcon.svelte';
|
||||
@@ -11,7 +12,7 @@
|
||||
<ActionIcon
|
||||
icon={Copy}
|
||||
tooltip={ariaLabel}
|
||||
iconSize="h-4 w-4"
|
||||
iconSize={ICON_CLASS_DEFAULT}
|
||||
disabled={!canCopy}
|
||||
onclick={() => canCopy && copyToClipboard(text)}
|
||||
/>
|
||||
|
||||
+3
-2
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { X, Music, Video } from '@lucide/svelte';
|
||||
import {
|
||||
formatFileSize,
|
||||
@@ -109,9 +110,9 @@
|
||||
class="flex h-8 w-8 items-center justify-center rounded bg-primary/10 text-xs font-medium text-primary"
|
||||
>
|
||||
{#if isAudio}
|
||||
<Music class="h-4 w-4 text-white/70" />
|
||||
<Music class="{ICON_CLASS_DEFAULT} text-white/70" />
|
||||
{:else if isVideo}
|
||||
<Video class="h-4 w-4 text-white/70" />
|
||||
<Video class="{ICON_CLASS_DEFAULT} text-white/70" />
|
||||
{:else}
|
||||
{fileTypeLabel}
|
||||
{/if}
|
||||
|
||||
+5
-4
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import type { ChatAttachmentDisplayItem } from '$lib/types';
|
||||
import { FileText, Eye, Info } from '@lucide/svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
@@ -88,7 +89,7 @@
|
||||
onclick={() => (pdfViewMode = PdfViewMode.TEXT)}
|
||||
disabled={pdfImagesLoading}
|
||||
>
|
||||
<FileText class="mr-1 h-4 w-4" />
|
||||
<FileText class="mr-1 {ICON_CLASS_DEFAULT}" />
|
||||
Text
|
||||
</Button>
|
||||
|
||||
@@ -100,10 +101,10 @@
|
||||
>
|
||||
{#if pdfImagesLoading}
|
||||
<div
|
||||
class="mr-1 h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent"
|
||||
class="mr-1 {ICON_CLASS_DEFAULT} animate-spin rounded-full border-2 border-current border-t-transparent"
|
||||
></div>
|
||||
{:else}
|
||||
<Eye class="mr-1 h-4 w-4" />
|
||||
<Eye class="mr-1 {ICON_CLASS_DEFAULT}" />
|
||||
{/if}
|
||||
Pages
|
||||
</Button>
|
||||
@@ -111,7 +112,7 @@
|
||||
|
||||
{#if !hasVisionModality && activeModelId && currentItem}
|
||||
<Alert.Root class="mb-4 max-w-4xl">
|
||||
<Info class="h-4 w-4" />
|
||||
<Info class={ICON_CLASS_DEFAULT} />
|
||||
<Alert.Title>Preview only</Alert.Title>
|
||||
<Alert.Description>
|
||||
<span class="inline-flex">
|
||||
|
||||
+4
-3
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { Music, Video, FileText } from '@lucide/svelte';
|
||||
import { HorizontalScrollCarousel } from '$lib/components/app/misc';
|
||||
|
||||
@@ -49,11 +50,11 @@
|
||||
class="bg-foreground-muted/50 flex h-12 w-12 flex-col items-center justify-center gap-0.5 py-1"
|
||||
>
|
||||
{#if item.isAudio}
|
||||
<Music class="h-4 w-4 text-white/70" />
|
||||
<Music class="{ICON_CLASS_DEFAULT} text-white/70" />
|
||||
{:else if item.isVideo}
|
||||
<Video class="h-4 w-4 text-white/70" />
|
||||
<Video class="{ICON_CLASS_DEFAULT} text-white/70" />
|
||||
{:else}
|
||||
<FileText class="h-4 w-4 text-white/70" />
|
||||
<FileText class="{ICON_CLASS_DEFAULT} text-white/70" />
|
||||
{/if}
|
||||
|
||||
<span class="font-mono text-[9px] text-white/60">{getFileExtension(item.name)}</span>
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { Plus } from '@lucide/svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
@@ -23,7 +24,7 @@
|
||||
>
|
||||
<span class="sr-only">{ATTACHMENT_TOOLTIP_TEXT}</span>
|
||||
|
||||
<Plus class="h-4 w-4" />
|
||||
<Plus class={ICON_CLASS_DEFAULT} />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
|
||||
|
||||
+8
-7
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { Plus, File, MessageSquare, Zap, FolderOpen } from '@lucide/svelte';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
@@ -83,7 +84,7 @@
|
||||
>
|
||||
<span class="sr-only">{ATTACHMENT_TOOLTIP_TEXT}</span>
|
||||
|
||||
<Plus class="h-4 w-4" />
|
||||
<Plus class={ICON_CLASS_DEFAULT} />
|
||||
</DropdownMenu.Trigger>
|
||||
{/snippet}
|
||||
</Tooltip.Trigger>
|
||||
@@ -100,7 +101,7 @@
|
||||
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
|
||||
<File class="h-4 w-4" />
|
||||
<File class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>Add files</span>
|
||||
</DropdownMenu.SubTrigger>
|
||||
@@ -113,7 +114,7 @@
|
||||
class="{item.class ?? ''} flex cursor-pointer items-center gap-2"
|
||||
onclick={() => attachmentMenu.callbacks[item.action]()}
|
||||
>
|
||||
<item.icon class="h-4 w-4" />
|
||||
<item.icon class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>{item.label}</span>
|
||||
</DropdownMenu.Item>
|
||||
@@ -126,7 +127,7 @@
|
||||
class="{item.class ?? ''} flex items-center gap-2"
|
||||
disabled
|
||||
>
|
||||
<item.icon class="h-4 w-4" />
|
||||
<item.icon class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>{item.label}</span>
|
||||
</DropdownMenu.Item>
|
||||
@@ -147,7 +148,7 @@
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
onclick={onSystemPromptClick}
|
||||
>
|
||||
<MessageSquare class="h-4 w-4" />
|
||||
<MessageSquare class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>System Message</span>
|
||||
</DropdownMenu.Item>
|
||||
@@ -163,7 +164,7 @@
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
onclick={onMcpPromptClick}
|
||||
>
|
||||
<Zap class="h-4 w-4" />
|
||||
<Zap class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>MCP Prompt</span>
|
||||
</DropdownMenu.Item>
|
||||
@@ -174,7 +175,7 @@
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
onclick={onMcpResourcesClick}
|
||||
>
|
||||
<FolderOpen class="h-4 w-4" />
|
||||
<FolderOpen class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>MCP Resources</span>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user