Skip to content

Commit 625449a

Browse files
committed
vulkan : use ninja to compile shaders when GGML_VULKAN_SHADER_DEV is set
* allows incremental builds * tracks shader dependencies * requires ninja to be installed
1 parent 0cc76d8 commit 625449a

File tree

2 files changed

+96
-26
lines changed

2 files changed

+96
-26
lines changed

ggml/src/ggml-vulkan/CMakeLists.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,14 @@ if (Vulkan_FOUND)
175175
"${_ggml_vk_input_dir}/*.h")
176176

177177
if(GGML_VULKAN_SHADER_DEV)
178-
set(_ggml_vk_shader_dev_args --no-embed --ninja)
178+
set(_ggml_vk_shader_ninja_file "${CMAKE_CURRENT_BINARY_DIR}/vulkan-shaders.ninja")
179+
set(_ggml_vk_shader_dev_args --no-embed --ninja ${_ggml_vk_shader_ninja_file})
179180
endif()
180181

181182
add_custom_command(
182183
OUTPUT ${_ggml_vk_header}
183184
${_ggml_vk_source}
185+
${_ggml_vk_shader_ninja_file}
184186

185187
COMMAND ${_ggml_vk_genshaders_cmd}
186188
--glslc ${Vulkan_GLSLC_EXECUTABLE}
@@ -198,6 +200,16 @@ if (Vulkan_FOUND)
198200
COMMENT "Generate vulkan shaders"
199201
)
200202

203+
if(GGML_VULKAN_SHADER_DEV)
204+
add_custom_target(ggml-vulkan-shaders-build
205+
DEPENDS ${_ggml_vk_shader_ninja_file}
206+
${_ggml_vk_shader_files}
207+
COMMAND ninja -f ${_ggml_vk_shader_ninja_file}
208+
COMMENT "Build vulkan shaders"
209+
)
210+
add_dependencies(ggml-vulkan ggml-vulkan-shaders-build)
211+
endif()
212+
201213
target_sources(ggml-vulkan PRIVATE ${_ggml_vk_source} ${_ggml_vk_header})
202214

203215
else()

ggml/src/ggml-vulkan/vulkan-shaders/vulkan-shaders-gen.cpp

Lines changed: 83 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,60 @@ std::string basename(const std::string &path) {
235235
return path.substr(path.find_last_of("/\\") + 1);
236236
}
237237

238+
std::stringstream make_generic_stringstream() {
239+
std::stringstream ss;
240+
ss.imbue(c_locale);
241+
return ss;
242+
}
243+
244+
struct compile_command {
245+
std::string name;
246+
std::string input_filepath;
247+
std::string output_filepath;
248+
std::string flags;
249+
};
250+
251+
// Code for writing a ninja build file
252+
253+
static std::stringstream ninja_build;
254+
255+
std::string ninja_escape(const std::string& str) {
256+
std::string result;
257+
for (char c : str) {
258+
if (c == ' ' || c == '$' || c == ':') {
259+
result += '$';
260+
}
261+
result += c;
262+
}
263+
return result;
264+
}
265+
266+
void ninja_init() {
267+
ninja_build = make_generic_stringstream();
268+
ninja_build << "ninja_required_version = 1.3\n\n";
269+
ninja_build << "rule glslc\n";
270+
ninja_build << " command = " << GLSLC << " $FLAGS -MD -MF $out.d $in -o $out\n";
271+
ninja_build << " depfile = $out.d\n";
272+
ninja_build << " deps = gcc\n";
273+
ninja_build << " description = Building Vulkan shader ${NAME}.spv\n\n";
274+
}
275+
276+
void ninja_add_build_command(const compile_command& cmd) {
277+
ninja_build << "build " << ninja_escape(cmd.output_filepath) << ": glslc " << ninja_escape(cmd.input_filepath) << "\n";
278+
ninja_build << " NAME = " << cmd.name << "\n";
279+
ninja_build << " FLAGS = " << cmd.flags << "\n\n";
280+
shader_fnames.push_back(std::make_pair(cmd.name, cmd.output_filepath));
281+
}
282+
283+
238284
// variables to track number of compiles in progress
239285
static uint32_t compile_count = 0;
240286
static std::mutex compile_count_mutex;
241287
static std::condition_variable compile_count_cond;
242288

243-
void string_to_spv_func(const std::string& _name, const std::string& in_fname, const std::map<std::string, std::string>& defines, bool fp16 = true, bool coopmat = false, bool coopmat2 = false, bool f16acc = false) {
289+
compile_command create_compile_command(const std::string& _name, const std::string& in_fname, const std::map<std::string, std::string>& defines, bool fp16, bool coopmat, bool coopmat2, bool f16acc) {
244290
std::string name = _name + (f16acc ? "_f16acc" : "") + (coopmat ? "_cm1" : "") + (coopmat2 ? "_cm2" : (fp16 ? "" : "_fp32"));
245-
std::string out_fname = join_paths(output_dir, name + ".spv");
291+
std::string out_path = join_paths(output_dir, name + ".spv");
246292
std::string in_path = join_paths(input_dir, in_fname);
247293

248294
std::string target_env = (name.find("_cm2") != std::string::npos) ? "--target-env=vulkan1.3" : "--target-env=vulkan1.2";
@@ -251,25 +297,24 @@ void string_to_spv_func(const std::string& _name, const std::string& in_fname, c
251297
// disable spirv-opt for bf16 shaders for https://github.com/ggml-org/llama.cpp/issues/15344
252298
std::string opt_level = (coopmat || name.find("bf16") != std::string::npos) ? "" : "-O";
253299

254-
#ifdef _WIN32
255-
std::vector<std::string> cmd = {GLSLC, "-fshader-stage=compute", target_env, opt_level, "\"" + in_path + "\"", "-o", "\"" + out_fname + "\""};
256-
#else
257-
std::vector<std::string> cmd = {GLSLC, "-fshader-stage=compute", target_env, opt_level, in_path, "-o", out_fname};
258-
#endif
300+
std::vector<std::string> flags = {"-fshader-stage=compute", target_env, opt_level};
259301

260302
#ifdef GGML_VULKAN_SHADER_DEBUG_INFO
261-
cmd.push_back("-g");
303+
flags.push_back("-g");
262304
#endif
263305

264306
for (const auto& define : defines) {
265-
cmd.push_back("-D" + define.first + "=" + define.second);
307+
flags.push_back("-D" + define.first + "=" + define.second);
266308
}
267309

268-
std::string command;
269-
for (const auto& part : cmd) {
270-
command += part + " ";
310+
std::string flags_str;
311+
for (const auto& part : flags) {
312+
flags_str += part + " ";
271313
}
314+
return {std::move(name), std::move(in_path), std::move(out_path), std::move(flags_str)};
315+
}
272316

317+
void execute_compile_command(compile_command cmd) {
273318
std::string stdout_str, stderr_str;
274319
try {
275320
// std::cout << "Executing command: ";
@@ -278,16 +323,22 @@ void string_to_spv_func(const std::string& _name, const std::string& in_fname, c
278323
// }
279324
// std::cout << std::endl;
280325

326+
#ifdef _WIN32
327+
std::string command = GLSLC + " " + cmd.flags + " \"" + cmd.input_filepath + "\" -o \"" + cmd.output_filepath + "\"";
328+
#else
329+
std::string command = GLSLC + " " + cmd.flags + " " + cmd.input_filepath + " -o " + cmd.output_filepath;
330+
#endif
281331
execute_command(command, stdout_str, stderr_str);
332+
282333
if (!stderr_str.empty()) {
283-
std::cerr << "cannot compile " << name << "\n\n" << command << "\n\n" << stderr_str << std::endl;
334+
std::cerr << "cannot compile " << cmd.name << "\n\n" << command << "\n\n" << stderr_str << std::endl;
284335
return;
285336
}
286337

287338
std::lock_guard<std::mutex> guard(lock);
288-
shader_fnames.push_back(std::make_pair(name, out_fname));
339+
shader_fnames.push_back(std::make_pair(cmd.name, cmd.output_filepath));
289340
} catch (const std::exception& e) {
290-
std::cerr << "Error executing command for " << name << ": " << e.what() << std::endl;
341+
std::cerr << "Error executing command for " << cmd.name << ": " << e.what() << std::endl;
291342
}
292343
{
293344
std::lock_guard<std::mutex> guard(compile_count_mutex);
@@ -305,6 +356,12 @@ std::map<std::string, std::string> merge_maps(const std::map<std::string, std::s
305356

306357
static std::vector<std::future<void>> compiles;
307358
void string_to_spv(const std::string& _name, const std::string& in_fname, const std::map<std::string, std::string>& defines, bool fp16 = true, bool coopmat = false, bool coopmat2 = false, bool f16acc = false) {
359+
compile_command cmd = create_compile_command(_name, in_fname, defines, fp16, coopmat, coopmat2, f16acc);
360+
361+
if (!ninja_build_file.empty()) {
362+
ninja_add_build_command(cmd);
363+
return;
364+
}
308365
{
309366
// wait until fewer than N compiles are in progress.
310367
// 16 is an arbitrary limit, the goal is to avoid "failed to create pipe" errors.
@@ -315,7 +372,7 @@ void string_to_spv(const std::string& _name, const std::string& in_fname, const
315372
}
316373
compile_count++;
317374
}
318-
compiles.push_back(std::async(string_to_spv_func, _name, in_fname, defines, fp16, coopmat, coopmat2, f16acc));
375+
compiles.push_back(std::async(execute_compile_command, std::move(cmd)));
319376
}
320377

321378
void matmul_shaders(bool fp16, MatMulIdType matmul_id_type, bool coopmat, bool coopmat2, bool f16acc) {
@@ -765,12 +822,6 @@ void process_shaders() {
765822
}
766823
}
767824

768-
std::stringstream make_generic_stringstream() {
769-
std::stringstream ss;
770-
ss.imbue(c_locale);
771-
return ss;
772-
}
773-
774825
std::vector<unsigned char> read_binary_file(const std::string& path, bool may_not_exist = false) {
775826
FILE* f = fopen(path.c_str(), "rb");
776827
if (!f) {
@@ -810,10 +861,14 @@ void write_binary_file(const std::string& path, const unsigned char * data, size
810861
}
811862
}
812863

864+
void write_binary_file(const std::string& path, const std::string& content) {
865+
write_binary_file(path, (const unsigned char *)content.data(), content.size());
866+
}
867+
813868
void write_file_if_changed(const std::string& path, const std::string& content) {
814869
std::vector<unsigned char> existing = read_binary_file(path, true);
815870
if (existing.size() != content.size() || memcmp(existing.data(), content.data(), content.size()) != 0) {
816-
write_binary_file(path, (const unsigned char *)content.data(), content.size());
871+
write_binary_file(path, content);
817872
}
818873
}
819874

@@ -944,8 +999,10 @@ void write_output_files() {
944999
if (no_embed) {
9451000
write_file_if_changed(target_cpp, src.str());
9461001
} else {
947-
std::string src_str = src.str();
948-
write_binary_file(target_cpp, (const unsigned char *)src_str.data(), src_str.size());
1002+
write_binary_file(target_cpp, src.str());
1003+
}
1004+
if (!ninja_build_file.empty()) {
1005+
write_file_if_changed(ninja_build_file, ninja_build.str());
9491006
}
9501007
}
9511008

@@ -988,6 +1045,7 @@ int main(int argc, char** argv) {
9881045
}
9891046
if (args.find("--ninja") != args.end()) {
9901047
ninja_build_file = args["--ninja"]; // Write a ninja build file instead of invoking glslc directly
1048+
ninja_init();
9911049
}
9921050

9931051
if (!directory_exists(input_dir)) {

0 commit comments

Comments
 (0)