{"slug": "deepjit-header-only-c-20-jit-runtime-for-nvidia-cuda-and-huawei-ascend", "title": "DeepJIT: Header-Only C++20 JIT Runtime for Nvidia CUDA and Huawei Ascend", "summary": "Developers guyan364, kurisu6912 and LyricZhao released DeepJIT, a header-only C++20 JIT runtime that gives C++ and Python extension authors one shared interface for compiling, caching, loading and launching kernels on NVIDIA CUDA GPUs and Huawei Ascend NPUs. DeepJIT requires CUDA headers 12.4+ and NVCC 12.9+ for the CUDA backend, and CANN with bin/bisheng, bin/ld.lld and Ascend adv_api headers for the Ascend backend, with both backends sharing source and include hashing, in-memory and on-disk caches, and lazy initialization. Two features, cache warmup from history and a Python compilation API, are under development and not yet available.", "body_md": "DeepJIT is a lightweight, header-only C++20 JIT runtime for **NVIDIA CUDA GPUs** and **HUAWEI Ascend (昇腾) NPUs**. It gives C++/Python extension authors a shared interface for compiling kernel source at runtime, caching the resulting binaries, loading them onto the device, and launching them with backend-specific options.\n\nDeepJIT handles the JIT infrastructure so that kernel libraries can focus on their device code. Both backends share runtime configuration, source and include hashing, in-memory and on-disk caches, and lazy initialization. Kernel source and compiler/launch options remain specific to the selected backend.\n\n**Main authors:** [@guyan364](https://github.com/guyan364), [@kurisu6912](https://github.com/kurisu6912), [@LyricZhao](https://github.com/LyricZhao).\n\n- **CUDA and Ascend backends:** use`deep_jit::Runtime<deep_jit::CUDA>` or`deep_jit::Runtime<deep_jit::Ascend>` with the same compile/load/launch workflow.\n- **Kernel caching:** reuse loaded kernels in memory and compiled artifacts on disk. Cache keys account for source, tracked includes, compiler versions, effective compiler options, and an application-provided dependency signature.\n- **Distributed filesystems and shared caches:** share one cache directory across users, processes, and nodes to reuse compiled kernels. Both backends support local and distributed filesystems with the required POSIX filesystem semantics; see[Shared cache](#shared-cache) for configuration.\n- **Lazy initialization:** defer device and compiler discovery until the runtime is first used.\n- **PyTorch integration:** use the current PyTorch CUDA or`torch_npu` stream by default, and expose the configured runtime through pybind11 with`get_jit()` .\n- **Compilation controls and diagnostics:** configure runtime defaults and per-kernel overrides, inspect compilation metadata, and dump CUDA PTX/SASS or Ascend assembly. CUDA also supports a Python post-compilation hook.\n\n- **Cache warmup from history:** use historical cache entries to anticipate kernels that future runs may need and warm up their cache in advance, reducing compilation delays during execution. This feature is under development and is not yet available.\n- **Python compilation API:** pass kernel source code directly from Python to compile CUDA or Ascend kernels. This feature is under development and is not yet available.\n\n| Backend | Device toolchain and runtime | Integration requirements | \n|---|---|---|\n| **CUDA** | NVCC compiles CUDA source to CUBIN; the CUDA Driver API loads and launches kernels. | CUDA headers 12.4+, NVCC 12.9+, and PyTorch with CUDA support. | \n| **Ascend** | Bisheng and ld.lld compile and link Ascend kernel source; ACL loads and launches kernels. | CANN with `bin/bisheng` ,`bin/ld.lld` , and the Ascend`adv_api` headers; ACL and`torch_npu` headers and runtime. | \n\nThe host environment must provide Linux, a C++20 compiler and standard library with `std::format` support, Python, pybind11, and the dependencies for the selected backend. DeepJIT is intended to be embedded into your extension as a header-only dependency.\n\nSee [Integration](#integration) for setup, [CUDA](#cuda) for GPU usage, and [Ascend](#ascend) for NPU usage.\n\nCUDA and Ascend use the same disk-cache implementation. It supports local and distributed filesystems that provide atomic directory rename within a filesystem and file/directory `fsync`. Builds use unique temporary directories, synchronize their contents, and publish complete entries through an atomic rename. Concurrent processes can compile the same entry and reuse the published result.\n\nMultiple users, processes, and nodes can point to the same cache directory:\n\n```\nexport DJ_JIT_CACHE_DIR=/shared/deep_jit\n```\n\nConfigure directory permissions so participating users can read shared artifacts and writers can create and publish entries under the cache root. As with all DeepJIT caches, use a trusted shared directory. Matching compilation inputs and cache tags allow users to reuse each other's compiled kernels.\n\nYou can also combine a writable personal cache with a shared lookup cache:\n\n```\nexport DJ_JIT_CACHE_DIR=\"$HOME/.dj:/shared/deep_jit\"\n```\n\nDeepJIT searches all roots in order and writes cache misses only to the first root. The shared lookup cache can be read-only. To configure a single consumer library, use its prefix instead, for example `MYLIB_JIT_CACHE_DIR`.\n\n| Path | Contents | \n|---|---|\n| [`include/deep_jit/runtime/`](/deepseek-ai/DeepJIT/blob/main/include/deep_jit/runtime) | Shared runtime and configuration. | \n| [`include/deep_jit/backend/cuda/`](/deepseek-ai/DeepJIT/blob/main/include/deep_jit/backend/cuda) | CUDA compiler, device queries, kernel loading, and launch options. | \n| [`include/deep_jit/backend/ascend/`](/deepseek-ai/DeepJIT/blob/main/include/deep_jit/backend/ascend) | Ascend compiler/linker integration, device queries, kernel loading, and launch options. | \n| [`include/deep_jit/cache/`](/deepseek-ai/DeepJIT/blob/main/include/deep_jit/cache) | In-memory and on-disk kernel caches. | \n| [`include/deep_jit/python_api.hpp`](/deepseek-ai/DeepJIT/blob/main/include/deep_jit/python_api.hpp) | pybind11 registration for a consumer library's runtime. | \n| [`tests/`](/deepseek-ai/DeepJIT/blob/main/tests) | CUDA and Ascend integration tests, example extensions, and device kernels. | \n\nThe root `CMakeLists.txt` is for debugging and IDE indexing. Integrate the headers into your own extension as described below; the projects under [`tests/test_cuda_proj/`](/deepseek-ai/DeepJIT/blob/main/tests/test_cuda_proj) and [` tests/test_ascend_proj/`](/deepseek-ai/DeepJIT/blob/main/tests/test_ascend_proj) provide working integration examples.\n\nAdd `DeepJIT/include` to the include path of the host target, then include:\n\n```\ntarget_include_directories(my_target PRIVATE third-party/deep_jit/include)\n```\n\nInclude exactly one backend entry header. For CUDA:\n\n```\n#include <deep_jit/backend/cuda/backend.hpp>\n```\n\nFor Ascend:\n\n```\n#include <deep_jit/backend/ascend/backend.hpp>\n```\n\nThe selected header exposes its backend type:\n\n```\nusing JIT = deep_jit::Runtime<deep_jit::CUDA>;\n```\n\nFor the Ascend header, use `deep_jit::Runtime<deep_jit::Ascend>` instead.\n\n`create_lazy_jit` delays construction of the runtime until its first use. This also delays device and compiler discovery:\n\n```\ninline auto jit = deep_jit::create_lazy_jit<deep_jit::CUDA>(\n    deep_jit::Config(\"/absolute/path/to/my_library\", \"MYLIB\"));\n```\n\nIf configuration is only known during library initialization, start with an empty lazy object and assign its factory later:\n\n```\n#include <filesystem>\n#include <string>\n\n#include <cutlass/version.h>\n#include <deep_jit/backend/cuda/backend.hpp>\n\nnamespace my_library {\n\ninline deep_jit::LazyInit<deep_jit::Runtime<deep_jit::CUDA>> jit(nullptr);\n\ninline void init_jit(const std::string& library_root) {\n    const auto library_root_path = std::filesystem::absolute(library_root);\n    const auto include_dir = library_root_path / \"include\";\n\n    jit = deep_jit::create_lazy_jit<deep_jit::CUDA>(\n        deep_jit::Config(\n            library_root_path,\n            \"MYLIB\",\n            \"cutlass-\" + std::to_string(CUTLASS_VERSION),\n            {include_dir},\n            {\"my_library/\"}));\n}\n\n}  // namespace my_library\n```\n\nThe lazy object must receive a factory before `jit->...` or Python `get_jit()` is called.\n\n`deep_jit::Config` has the following constructor:\n\n```\nConfig(std::filesystem::path python_library_root,\n       std::string env_prefix,\n       std::string extra_signature = {},\n       std::vector<std::filesystem::path> include_dirs = {},\n       std::vector<std::string> include_prefixes = {});\n```\n\n- `python_library_root` resolves relative`post_hook` paths. It must be non-empty and absolute.\n- `env_prefix` selects the library-specific environment-variable prefix. It must be non-empty and cannot be`DJ` , which is reserved for global defaults.\n- `extra_signature` represents dependencies that affect generated code but are not tracked by the include parser. Change it when such a dependency changes, for example`\"cutlass-\" + std::to_string(CUTLASS_VERSION)` .\n- `include_dirs` are passed to the compiler and searched by the include parser. Every path must be absolute.\n- `include_prefixes` select which angle-bracket includes are recursively tracked. For example,`\"my_library/\"` tracks`#include <my_library/kernel.cuh>` .\n\nConfiguration is snapshotted when `Runtime` is constructed. Do not mutate `config`, `backend`, `parser`, `disk_cache`, or `hash_base` afterward. The supported mutable per-runtime settings are `default_compiler_options` and `default_launch_options`.\n\nCompiler, cache, include, and hook paths, as well as free-form compiler flags, are currently passed through a simple shell command and therefore must not contain whitespace or shell metacharacters.\n\nFor a pybind11 extension, DeepJIT can register the runtime type and `get_jit()` directly. The host library does not need to implement its own `get_jit` binding:\n\n```\n#include <pybind11/pybind11.h>\n\n#include <deep_jit/python_api.hpp>\n\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, module) {\n    deep_jit::register_python_api(module, my_library::jit);\n}\n```\n\nPython can then obtain the same process-local runtime object:\n\n```\njit = my_library._C.get_jit()\n```\n\nCalling `get_jit()` initializes the lazy runtime if it has not already been initialized.\n\nThe CUDA backend requires CUDA headers 12.4 or newer and NVCC 12.9 or newer. It uses NVCC to generate a CUBIN. Loading through `compile()` requires exactly one CUDA kernel; `compile_without_load()` only builds the artifact and does not perform that check.\n\n``` php\nconst auto kernel = jit->compile(\"scale\", R\"(\nextern \"C\" __global__ void scale(float* output, const float* input, int count) {\n    const int index = static_cast<int>(blockIdx.x * blockDim.x + threadIdx.x);\n    if (index < count)\n        output[index] = input[index] * 2.0f;\n}\n)\");\n\njit->launch(\n    kernel,\n    {\n        .grid_dim = dim3((count + 255) / 256, 1, 1),\n        .block_dim = dim3(256, 1, 1),\n    },\n    output,\n    input,\n    count);\n```\n\nThe compile tag must contain only letters, digits, and underscores. `compile()` compiles on a cache miss, loads the CUBIN, and returns a process-cached `std::shared_ptr<deep_jit::cuda::Kernel>`. `compile_without_load()` only returns the artifact directory.\n\nUnset `deep_jit::cuda::CompilerOptions` fields inherit from the runtime defaults:\n\n```\ndeep_jit::cuda::CompilerOptions options {\n    .optimize_level = \"3\",\n    .fast_math = true,\n    .check_no_spills = true,\n    .arch = jit->device.get_arch(/* use_arch_family = */ false),\n    .extra_nvcc_flags = {\"-DMY_OPTION=1\"},\n    .post_hook = \"hooks/hook_1.py\",\n};\n\nconst auto kernel = jit->compile(\"scale\", source, options);\n```\n\n`nvcc_flags` replaces the default free-form NVCC flag list; structured options such as `optimize_level` are generated separately. `extra_nvcc_flags` appends per-kernel flags such as `-D` definitions. To extend the runtime defaults, append flags directly to `*jit->default_compiler_options.nvcc_flags` during initialization.\n\nCUDA launch options include the stream, dynamic shared-memory size, grid, block, cluster, cooperative-launch, PDL, and non-portable cluster controls. Unset fields inherit from `jit->default_launch_options`, following the same override model as compiler options. Do not clear the initialized runtime defaults back to `std::nullopt`. Grid and block dimensions are required and must be positive; dynamic shared-memory size cannot be negative. An unset effective stream uses the current PyTorch CUDA stream; an explicitly supplied null stream remains the CUDA default stream.\n\nOnly one-dimensional clusters are supported (`cluster_dim.y == cluster_dim.z == 1`). Enabling `nonportable_cluster_size_allowed` sets a persistent CUDA function attribute; later launches with the option disabled do not reset that attribute.\n\nCompile and load kernels before CUDA Graph capture. Launching an already loaded kernel is capture-compatible, including when the stream is inherited from the current PyTorch stream.\n\nLibrary-wide launch defaults can be changed once during initialization:\n\n``` php\njit->default_launch_options.enable_pdl = true;\n```\n\nDevice information is available from `jit->device`:\n\n``` php\nconst int num_sms = jit->device.get_num_sms();\nconst int l2_bytes = jit->device.get_num_l2_cache_bytes();\nconst int smem_bytes = jit->device.get_num_smem_bytes();\nconst int64_t clock_rate = jit->device.get_clock_rate();\nconst auto [major, minor] = jit->device.get_arch_pair();\nconst std::string family_arch = jit->device.get_arch();\nconst std::string concrete_arch = jit->device.get_arch(false);\n```\n\nA runtime snapshots the current CUDA device when its default architecture is initialized. Use a separate runtime per CUDA device, construct and use it while that device is current, and do not move a loaded kernel between device contexts.\n\n`CompilerOptions::post_hook` selects one optional Python file under `Config::python_library_root`. For example:\n\n```\nmy_library/hooks/hook_1.py\n```\n\nPass the file's relative path through the compiler options:\n\n``` php\nconst auto kernel = jit->compile(\"scale\", source, {\n    .post_hook = \"hooks/hook_1.py\",\n});\n```\n\n`post_hook` inherits from `jit->default_compiler_options` like every other compiler option and is unset by default. Only `std::nullopt` disables it; an empty string is still treated as a configured hook. Once a default hook is set, the current override model cannot disable it for one kernel. The caller must provide a trusted relative path. When set, it runs after NVCC produces the CUBIN and before the artifact is published:\n\n```\ncd <temporary-artifact-directory>\npython <absolute-python-library-root>/hooks/hook_1.py <absolute-cubin-path>\n```\n\nThe script receives the absolute CUBIN path as its only argument and must modify that file in place. The configured relative `post_hook` path and file-content hash are included in the kernel cache digest; the path is also recorded in `meta.json`. Hook hashes are cached per thread, so restart the process after changing a hook file.\n\nA hook must be deterministic for a given input CUBIN and tracked signature. Imported Python modules, auxiliary files, Python/package versions, environment variables, random state, time, and network data are not discovered automatically; represent every such dependency in `extra_signature` if it can affect output.\n\nThe CUDA cache digest is built, in order, from:\n\n1. `Config::extra_signature` .\n2. The hash of the complete `nvcc --version` output.\n3. The effective compiler flags returned by `CompilerOptions::get_flags()` . Paths in`Config::include_dirs` are intentionally excluded, while any`-I...` placed directly in`nvcc_flags` or`extra_nvcc_flags` remains part of the digest.\n4. The selected `post_hook` path and file-content hash.\n5. The parser digest of the source and its tracked include tree.\n\nEach component is prefixed by its fixed-width byte length before it is added to the two-state FNV-1a hash, so boundaries remain unambiguous even for binary strings containing zero bytes. The final digest is a 32-character hexadecimal string. This is a fast cache checksum, not a cryptographic hash.\n\nThe compile tag is not part of the digest. A disk entry is stored as:\n\n```\n<cache-root>/cache/<tag>.<digest>/\n```\n\nCache roots must be trusted. A directory carrying a `.committed` marker is treated as a completed artifact and its CUBIN may be loaded directly.\n\nAn entry contains `kernel.cu`, `kernel.cubin`, `meta.json`, `.committed`, and optional `kernel.ptx` or `kernel.sass` files. `meta.json` records the NVCC command arguments used in the temporary build directory, config fields, compiler information, and effective compiler options, including the selected `post_hook` path. Temporary paths in that command may no longer exist after publication. It does not record the surrounding `cd`, stderr redirection, post-hook command, or dump commands.\n\n`dump_ptx` and `dump_sass` control extra artifacts but do not affect the cache digest. They only run when compilation actually occurs; a pre-existing cache hit is not rebuilt to add missing dumps.\n\nThe cache key assumes the external compiler environment is stable. Variables such as `NVCC_PREPEND_FLAGS`, `NVCC_APPEND_FLAGS`, `CPATH`, host-compiler selection, and transitive third-party header changes are not discovered automatically. Avoid such implicit inputs or represent them in `extra_signature`/explicit compiler flags. Excluding tracked include-directory paths is safe only when moving the same header contents does not change compilation behavior such as embedded `__FILE__` strings.\n\nThe root source is always hashed. An included file is recursively tracked only when both of the following are true:\n\n- The directive uses a literal angle-bracket include: `#include <...>` .\n- The included filename starts with one of `Config::include_prefixes` .\n\nThe parser is intentionally a line-oriented scanner, not a C preprocessor. A tracked directive must use the canonical single-line form `#include <...>` (whitespace around `#`, `include`, and the filename is allowed). It does not interpret comments between tokens, macro-expanded includes, backslash continuations, or conditional compilation; an include inside `#if 0` is still scanned.\n\nTracked files are resolved by checking `include_dirs` in order and using the first matching path. A tracked file that cannot be found is an error. The same rules are applied recursively to includes inside tracked files.\n\nQuoted includes such as `#include \"kernel.cuh\"` are rejected. Macro-based includes are not tracked. Angle-bracket includes that do not match any configured prefix are accepted by the compiler but ignored by the parser.\n\nFor example, with:\n\n```\ndeep_jit::Config(\n    std::filesystem::path(\"/opt/my_library\"),\n    \"MYLIB\",\n    \"cutlass-40000\",\n    {std::filesystem::path(\"/opt/my_library/include\")},\n    {\"my_library/\"});\n```\n\n`#include <my_library/kernel.cuh>` is tracked, while `#include <cutlass/cutlass.h>` is not. The CUTLASS version is instead represented by `extra_signature`.\n\nTracked include graphs must not contain cycles. Header digests are cached for the lifetime of a runtime, so recreate the runtime after changing tracked header files.\n\nFor `Config(\"/absolute/path/to/my_library\", \"MYLIB\")`, every DeepJIT setting is resolved in this order:\n\n```\nMYLIB_<SUFFIX> > DJ_<SUFFIX> > built-in default\n```\n\nFor example:\n\n```\nMYLIB_JIT_CACHE_DIR > DJ_JIT_CACHE_DIR > $HOME/.dj\n```\n\nThe library prefix therefore allows one consumer to be configured independently, while the reserved `DJ_` prefix provides process-wide defaults. Boolean values accept `true`/` false`, `yes`/` no`, or any integer, case-insensitively.\n\nOnly the library-prefixed and `DJ_` forms are read. An unprefixed variable such as `JIT_CACHE_DIR` or `JIT_DEBUG` has no effect.\n\nSet JIT environment variables before the first runtime construction (normally before the first `get_jit()` or `jit->...`). Cache roots, compiler selection, C++ standard, and default compiler options are snapshotted then. Compiler-command printing and load-time diagnostics are read again when compile/load runs; changing variables after initialization can therefore produce a mixed configuration and is unsupported.\n\n| Suffix | Default | Behavior | \n|---|---|---|\n| `JIT_CACHE_DIR` | `$HOME/.dj` | Cache root, or a colon-separated list. All roots are searched in order; misses are compiled into the first root. Empty values or empty list elements are rejected. | \n| `JIT_DEBUG` | `0` | Enables compiler-command and load diagnostics. CUDA also enables PTXAS output, line info, and PTX/SASS dumps. | \n| `JIT_NVCC_COMPILER` | `<discovered-toolkit>/bin/nvcc` | Overrides the NVCC executable. | \n| `JIT_CPP_STANDARD` | `20` | Selects the C++ standard passed to NVCC as `-std=c++<value>` . | \n| `JIT_KERNEL_DEBUG_INFO` | `0` | Adds Bisheng kernel debug information. | \n| `JIT_LAUNCH_TIMEOUT` | `10` | Sets the Ascend kernel launch timeout in seconds; `0` disables it. | \n| `JIT_PRINT_COMPILER_COMMAND` | `0` | Prints compiler and disassembler commands. | \n| `JIT_PTXAS_VERBOSE` | `0` | Adds verbose PTXAS output and prints it after compilation. | \n| `JIT_CHECK_NO_SPILLS` | `0` | Adds `--warn-on-spills` and rejects register spills. | \n| `JIT_CHECK_NO_LOCAL_MEMORY` | `0` | Adds `--warn-on-local-memory-usage` and rejects any local-memory usage. | \n| `JIT_PRINT_LOAD_TIME` | `0` | Prints kernel-binary loading time. | \n| `JIT_WITH_LINEINFO` | `0` | Adds CUDA source line information. | \n| `JIT_DUMP_ASM` | `0` | Generates CUDA PTX/SASS or Ascend assembly artifacts on a cache miss. | \n| `JIT_DUMP_PTX` | `0` | Generates a PTX artifact on a cache miss. | \n| `JIT_DUMP_SASS` | `0` | Generates a SASS artifact on a cache miss. | \n\nCUDA toolkit and cache discovery also use these standard environment variables:\n\n| Variable | Behavior | \n|---|---|\n| `HOME` | Required for the default `$HOME/.dj` cache path. | \n| `CUDA_HOME` | First CUDA toolkit-root candidate. | \n| `CUDA_PATH` | CUDA toolkit-root fallback when `CUDA_HOME` is unset or empty. | \n| `PATH` | Used by `which nvcc` when neither CUDA root variable identifies a toolkit. | \n\nIf both CUDA root variables are unset or empty and `which nvcc` fails, DeepJIT tries `/usr/local/cuda`. A non-empty but invalid `CUDA_HOME` or `CUDA_PATH` is treated as an error rather than skipped.\n\n`JIT_NVCC_COMPILER` overrides the executable after CUDA-home discovery. A valid CUDA root must still be discoverable through `CUDA_HOME`, `CUDA_PATH`, `PATH`, or `/usr/local/cuda`. SASS dumping additionally requires an executable `cuobjdump` under that discovered toolkit root.\n\nThe Ascend backend requires ACL and torch_npu headers plus a CANN toolkit containing `bin/bisheng` and `bin/ld.lld`. It compiles `kernel.asc` to `kernel.rel.o`, links `kernel.o`, parses the unique `.ascend.meta.*` kernel name, and loads it through ACL.\n\n```\ninline auto jit = deep_jit::create_lazy_jit<deep_jit::Ascend>(\n    deep_jit::Config(\n        \"/absolute/path/to/my_library\",\n        \"MYLIB\",\n        {},\n        {\"/absolute/path/to/my_library/include\"},\n        {\"my_library/\"}));\n\nconst auto kernel = jit->compile(\"scale\", source);\njit->launch(kernel, {.num_blocks = num_blocks}, output, input, count);\n```\n\nAn unset stream uses the current torch_npu stream. `num_blocks` is required. `num_ubuf_bytes` controls dynamic UB size, and `num_launch_timeout_secs` defaults to `JIT_LAUNCH_TIMEOUT`.\n\nWhen `ASCEND_LAUNCH_BLOCKING` is enabled, DeepJIT synchronizes the device after every launch.\n\n`deep_jit::ascend::CompilerOptions` supports the optimization level, `dav-*` architecture, debug information, assembly dumping, and replace/append lists for Bisheng and linker flags. The defaults are `-O2`, `--cce-aicore-only`, VF loop unrolling, and `ld.lld -m aicorelinux -Ttext 0 --no-mmap-output-file`. `<toolkit>/aarch64-linux/asc/include/adv_api` is required and added automatically.\n\nAssembly dumping writes Bisheng saved intermediates under the artifact's `asm/` directory. Like CUDA's PTX/SASS dumps, it does not change the cache digest and only runs on a cache miss.\n\nDevice queries are available through `jit->device`, including `get_npu_arch()`, `get_num_sms()`/` get_num_aicore_cores()`, vector and cube core counts, UB size, and L2 size.\n\nToolkit discovery checks `ASCEND_HOME_PATH`, `ASCEND_TOOLKIT_HOME`, `/usr/local/Ascend/ascend-toolkit/latest`, and `/usr/local/Ascend/cann`, in that order.", "url": "https://wpnews.pro/news/deepjit-header-only-c-20-jit-runtime-for-nvidia-cuda-and-huawei-ascend", "canonical_source": "https://github.com/deepseek-ai/DeepJIT", "published_at": "2026-09-10 10:36:30+00:00", "updated_at": "2026-09-10 10:59:43.296369+00:00", "lang": "en", "topics": ["ai-infrastructure", "ai-chips", "developer-tools", "mlops"], "entities": ["DeepJIT", "NVIDIA CUDA", "Huawei Ascend", "guyan364", "kurisu6912", "LyricZhao", "PyTorch", "torch_npu"], "alternates": {"html": "https://wpnews.pro/news/deepjit-header-only-c-20-jit-runtime-for-nvidia-cuda-and-huawei-ascend", "markdown": "https://wpnews.pro/news/deepjit-header-only-c-20-jit-runtime-for-nvidia-cuda-and-huawei-ascend.md", "text": "https://wpnews.pro/news/deepjit-header-only-c-20-jit-runtime-for-nvidia-cuda-and-huawei-ascend.txt", "jsonld": "https://wpnews.pro/news/deepjit-header-only-c-20-jit-runtime-for-nvidia-cuda-and-huawei-ascend.jsonld"}}