426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
364 lines
12 KiB
C++
364 lines
12 KiB
C++
#include "ggml.h"
|
|
#include "ggml-backend.h"
|
|
#include "ggml-cpu.h"
|
|
#include "ggml-metal.h"
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
struct Config {
|
|
int src_w = 48;
|
|
int src_h = 48;
|
|
int dst_w = 92;
|
|
int dst_h = 92;
|
|
int channels = 1152;
|
|
int iters = 1;
|
|
std::string mode = "bilinear-aa";
|
|
std::string out;
|
|
bool run_cpu = true;
|
|
bool require_metal_support = false;
|
|
};
|
|
|
|
struct Graph {
|
|
ggml_context * ctx = nullptr;
|
|
ggml_tensor * src = nullptr;
|
|
ggml_tensor * dst = nullptr;
|
|
ggml_cgraph * gf = nullptr;
|
|
};
|
|
|
|
static const char * usage() {
|
|
return
|
|
"Usage: ./metal_upscale_probe [options]\n"
|
|
"\n"
|
|
"Builds a GGML graph containing one GGML_OP_UPSCALE and probes Metal support.\n"
|
|
"The default shape mirrors the Eliza-1 vision-projector CLIP warmup: src=[48 48 1152 1], dst=[92 92 1152 1].\n"
|
|
"\n"
|
|
"Options:\n"
|
|
" --mode nearest|bilinear|bilinear-aa|bicubic Upscale mode; default bilinear-aa\n"
|
|
" --src-w N --src-h N --dst-w N --dst-h N Override spatial shape\n"
|
|
" --channels N Override channel count\n"
|
|
" --iters N Benchmark iterations; default 1\n"
|
|
" --no-cpu Skip CPU fallback timing\n"
|
|
" --require-metal-support Exit non-zero when Metal rejects the op\n"
|
|
" --out PATH Write JSON report\n"
|
|
" --help Show this help\n";
|
|
}
|
|
|
|
static bool parse_int_arg(const char * name, const char * value, int * out) {
|
|
char * end = nullptr;
|
|
long parsed = std::strtol(value, &end, 10);
|
|
if (!end || *end != '\0' || parsed <= 0 || parsed > 1'000'000) {
|
|
std::fprintf(stderr, "%s must be a positive integer, got '%s'\n", name, value);
|
|
return false;
|
|
}
|
|
*out = (int) parsed;
|
|
return true;
|
|
}
|
|
|
|
static bool parse_args(int argc, char ** argv, Config * cfg) {
|
|
for (int i = 1; i < argc; ++i) {
|
|
const std::string arg = argv[i];
|
|
auto next = [&](const char * name) -> const char * {
|
|
if (++i >= argc) {
|
|
std::fprintf(stderr, "missing value for %s\n", name);
|
|
std::exit(2);
|
|
}
|
|
return argv[i];
|
|
};
|
|
|
|
if (arg == "--help" || arg == "-h") {
|
|
std::puts(usage());
|
|
std::exit(0);
|
|
} else if (arg == "--mode") {
|
|
cfg->mode = next("--mode");
|
|
} else if (arg == "--src-w") {
|
|
if (!parse_int_arg("--src-w", next("--src-w"), &cfg->src_w)) return false;
|
|
} else if (arg == "--src-h") {
|
|
if (!parse_int_arg("--src-h", next("--src-h"), &cfg->src_h)) return false;
|
|
} else if (arg == "--dst-w") {
|
|
if (!parse_int_arg("--dst-w", next("--dst-w"), &cfg->dst_w)) return false;
|
|
} else if (arg == "--dst-h") {
|
|
if (!parse_int_arg("--dst-h", next("--dst-h"), &cfg->dst_h)) return false;
|
|
} else if (arg == "--channels") {
|
|
if (!parse_int_arg("--channels", next("--channels"), &cfg->channels)) return false;
|
|
} else if (arg == "--iters") {
|
|
if (!parse_int_arg("--iters", next("--iters"), &cfg->iters)) return false;
|
|
} else if (arg == "--out") {
|
|
cfg->out = next("--out");
|
|
} else if (arg == "--no-cpu") {
|
|
cfg->run_cpu = false;
|
|
} else if (arg == "--require-metal-support") {
|
|
cfg->require_metal_support = true;
|
|
} else {
|
|
std::fprintf(stderr, "unknown argument: %s\n", arg.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return cfg->mode == "nearest" ||
|
|
cfg->mode == "bilinear" ||
|
|
cfg->mode == "bilinear-aa" ||
|
|
cfg->mode == "bicubic";
|
|
}
|
|
|
|
static uint32_t mode_flags(const std::string & mode) {
|
|
if (mode == "nearest") {
|
|
return GGML_SCALE_MODE_NEAREST;
|
|
}
|
|
if (mode == "bilinear") {
|
|
return GGML_SCALE_MODE_BILINEAR;
|
|
}
|
|
if (mode == "bilinear-aa") {
|
|
return GGML_SCALE_MODE_BILINEAR | GGML_SCALE_FLAG_ANTIALIAS;
|
|
}
|
|
if (mode == "bicubic") {
|
|
return GGML_SCALE_MODE_BICUBIC;
|
|
}
|
|
|
|
std::fprintf(stderr, "unsupported mode: %s\n", mode.c_str());
|
|
std::exit(2);
|
|
}
|
|
|
|
static const char * mode_label(uint32_t flags) {
|
|
const uint32_t mode = flags & 0xFFu;
|
|
const bool aa = (flags & GGML_SCALE_FLAG_ANTIALIAS) != 0;
|
|
if (mode == GGML_SCALE_MODE_NEAREST) return "nearest";
|
|
if (mode == GGML_SCALE_MODE_BILINEAR && aa) return "bilinear-aa";
|
|
if (mode == GGML_SCALE_MODE_BILINEAR) return "bilinear";
|
|
if (mode == GGML_SCALE_MODE_BICUBIC) return "bicubic";
|
|
return "unknown";
|
|
}
|
|
|
|
static std::vector<float> make_input(const Config & cfg) {
|
|
const size_t n = (size_t) cfg.src_w * cfg.src_h * cfg.channels;
|
|
std::vector<float> input(n);
|
|
for (size_t i = 0; i < n; ++i) {
|
|
input[i] = 0.25f * std::sin((float) i * 0.017f) +
|
|
0.75f * std::cos((float) i * 0.003f);
|
|
}
|
|
return input;
|
|
}
|
|
|
|
static Graph make_graph(const Config & cfg, uint32_t flags) {
|
|
const size_t meta = 32ull * 1024ull * 1024ull;
|
|
ggml_context * ctx = ggml_init({ meta, nullptr, true });
|
|
if (!ctx) {
|
|
std::fprintf(stderr, "ggml_init failed\n");
|
|
std::exit(1);
|
|
}
|
|
|
|
ggml_tensor * src = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, cfg.src_w, cfg.src_h, cfg.channels, 1);
|
|
ggml_tensor * dst = ggml_interpolate(ctx, src, cfg.dst_w, cfg.dst_h, cfg.channels, 1, flags);
|
|
ggml_set_name(src, "upscale_src");
|
|
ggml_set_name(dst, "upscale_dst");
|
|
ggml_cgraph * gf = ggml_new_graph(ctx);
|
|
ggml_build_forward_expand(gf, dst);
|
|
|
|
return { ctx, src, dst, gf };
|
|
}
|
|
|
|
static double now_ms() {
|
|
using clock = std::chrono::steady_clock;
|
|
return std::chrono::duration<double, std::milli>(clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
struct RunResult {
|
|
bool attempted = false;
|
|
bool ok = false;
|
|
int status = -1;
|
|
double avg_ms = 0.0;
|
|
std::vector<float> output;
|
|
};
|
|
|
|
static RunResult run_backend(
|
|
ggml_backend_t backend,
|
|
const Config & cfg,
|
|
uint32_t flags,
|
|
const std::vector<float> & input) {
|
|
RunResult result;
|
|
result.attempted = true;
|
|
|
|
Graph g = make_graph(cfg, flags);
|
|
ggml_backend_buffer_t buffer = ggml_backend_alloc_ctx_tensors(g.ctx, backend);
|
|
if (!buffer) {
|
|
std::fprintf(stderr, "ggml_backend_alloc_ctx_tensors failed\n");
|
|
ggml_free(g.ctx);
|
|
return result;
|
|
}
|
|
|
|
ggml_backend_tensor_set(g.src, input.data(), 0, input.size() * sizeof(float));
|
|
|
|
double total_ms = 0.0;
|
|
int status = (int) GGML_STATUS_SUCCESS;
|
|
for (int i = 0; i < cfg.iters; ++i) {
|
|
const double t0 = now_ms();
|
|
status = (int) ggml_backend_graph_compute(backend, g.gf);
|
|
ggml_backend_synchronize(backend);
|
|
const double t1 = now_ms();
|
|
total_ms += t1 - t0;
|
|
if (status != (int) GGML_STATUS_SUCCESS) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
result.status = status;
|
|
result.ok = status == (int) GGML_STATUS_SUCCESS;
|
|
result.avg_ms = total_ms / std::max(1, cfg.iters);
|
|
if (result.ok) {
|
|
result.output.resize((size_t) cfg.dst_w * cfg.dst_h * cfg.channels);
|
|
ggml_backend_tensor_get(g.dst, result.output.data(), 0, result.output.size() * sizeof(float));
|
|
}
|
|
|
|
ggml_backend_buffer_free(buffer);
|
|
ggml_free(g.ctx);
|
|
return result;
|
|
}
|
|
|
|
static double max_abs_diff(const std::vector<float> & a, const std::vector<float> & b) {
|
|
if (a.size() != b.size()) return INFINITY;
|
|
double out = 0.0;
|
|
for (size_t i = 0; i < a.size(); ++i) {
|
|
out = std::max(out, (double) std::fabs(a[i] - b[i]));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static std::string json_report(
|
|
const Config & cfg,
|
|
uint32_t flags,
|
|
bool metal_init_ok,
|
|
bool metal_supports,
|
|
const RunResult & cpu,
|
|
const RunResult & metal,
|
|
double max_diff) {
|
|
char diff_buf[64];
|
|
if (std::isfinite(max_diff)) {
|
|
std::snprintf(diff_buf, sizeof(diff_buf), "%.9g", max_diff);
|
|
} else {
|
|
std::snprintf(diff_buf, sizeof(diff_buf), "null");
|
|
}
|
|
|
|
char buf[4096];
|
|
std::snprintf(buf, sizeof(buf),
|
|
"{\n"
|
|
" \"schemaVersion\": 1,\n"
|
|
" \"metric\": \"metal_upscale_probe\",\n"
|
|
" \"shape\": {\n"
|
|
" \"src\": [%d, %d, %d, 1],\n"
|
|
" \"dst\": [%d, %d, %d, 1]\n"
|
|
" },\n"
|
|
" \"mode\": \"%s\",\n"
|
|
" \"modeFlags\": %u,\n"
|
|
" \"metal\": {\n"
|
|
" \"backendInit\": %s,\n"
|
|
" \"supportsOp\": %s,\n"
|
|
" \"attempted\": %s,\n"
|
|
" \"ok\": %s,\n"
|
|
" \"status\": %d,\n"
|
|
" \"avgMs\": %.3f\n"
|
|
" },\n"
|
|
" \"cpu\": {\n"
|
|
" \"attempted\": %s,\n"
|
|
" \"ok\": %s,\n"
|
|
" \"status\": %d,\n"
|
|
" \"avgMs\": %.3f\n"
|
|
" },\n"
|
|
" \"comparison\": {\n"
|
|
" \"metalVsCpuMaxAbsDiff\": %s\n"
|
|
" },\n"
|
|
" \"finding\": \"%s\"\n"
|
|
"}\n",
|
|
cfg.src_w, cfg.src_h, cfg.channels,
|
|
cfg.dst_w, cfg.dst_h, cfg.channels,
|
|
mode_label(flags),
|
|
flags,
|
|
metal_init_ok ? "true" : "false",
|
|
metal_supports ? "true" : "false",
|
|
metal.attempted ? "true" : "false",
|
|
metal.ok ? "true" : "false",
|
|
metal.status,
|
|
metal.avg_ms,
|
|
cpu.attempted ? "true" : "false",
|
|
cpu.ok ? "true" : "false",
|
|
cpu.status,
|
|
cpu.avg_ms,
|
|
diff_buf,
|
|
metal_supports
|
|
? "Metal accepts this UPSCALE op; compare output/timing against CPU."
|
|
: "Metal rejects this UPSCALE op, so GGML scheduler must split it to CPU for CLIP graphs.");
|
|
return std::string(buf);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char ** argv) {
|
|
Config cfg;
|
|
if (!parse_args(argc, argv, &cfg)) {
|
|
std::fputs(usage(), stderr);
|
|
return 2;
|
|
}
|
|
|
|
const uint32_t flags = mode_flags(cfg.mode);
|
|
const std::vector<float> input = make_input(cfg);
|
|
|
|
ggml_backend_t metal_backend = ggml_backend_metal_init();
|
|
const bool metal_init_ok = metal_backend != nullptr;
|
|
bool metal_supports = false;
|
|
if (metal_backend) {
|
|
Graph support_graph = make_graph(cfg, flags);
|
|
metal_supports = ggml_backend_supports_op(metal_backend, support_graph.dst);
|
|
ggml_free(support_graph.ctx);
|
|
}
|
|
|
|
RunResult cpu;
|
|
if (cfg.run_cpu) {
|
|
ggml_backend_t cpu_backend = ggml_backend_cpu_init();
|
|
if (!cpu_backend) {
|
|
std::fprintf(stderr, "ggml_backend_cpu_init failed\n");
|
|
} else {
|
|
ggml_backend_cpu_set_n_threads(cpu_backend, 1);
|
|
cpu = run_backend(cpu_backend, cfg, flags, input);
|
|
ggml_backend_free(cpu_backend);
|
|
}
|
|
}
|
|
|
|
RunResult metal;
|
|
if (metal_backend && metal_supports) {
|
|
metal = run_backend(metal_backend, cfg, flags, input);
|
|
}
|
|
|
|
double diff = INFINITY;
|
|
if (cpu.ok && metal.ok) {
|
|
diff = max_abs_diff(cpu.output, metal.output);
|
|
}
|
|
|
|
if (metal_backend) {
|
|
ggml_backend_free(metal_backend);
|
|
}
|
|
|
|
const std::string report = json_report(cfg, flags, metal_init_ok, metal_supports, cpu, metal, diff);
|
|
if (!cfg.out.empty()) {
|
|
std::ofstream out(cfg.out);
|
|
if (!out) {
|
|
std::fprintf(stderr, "cannot write %s\n", cfg.out.c_str());
|
|
return 1;
|
|
}
|
|
out << report;
|
|
}
|
|
std::fputs(report.c_str(), stdout);
|
|
|
|
if (cfg.require_metal_support && !metal_supports) {
|
|
return 3;
|
|
}
|
|
return 0;
|
|
}
|