Skip to content

Assignment #4: CUDA integration (NVIDIA + Iluvatar CoreX) - #67

Open
tangjinchi23-cmd wants to merge 41 commits into
InfiniTensor:mainfrom
tangjinchi23-cmd:main
Open

Assignment #4: CUDA integration (NVIDIA + Iluvatar CoreX)#67
tangjinchi23-cmd wants to merge 41 commits into
InfiniTensor:mainfrom
tangjinchi23-cmd:main

Conversation

@tangjinchi23-cmd

Copy link
Copy Markdown

Summary

  • 实现 Assignment README Typo #3(Qwen2 推理,CPU)+ Assignment 推理时间过长,超出 CI 时长 #4(CUDA 集成)
  • 双平台适配:NVIDIA(cuBLAS + 手写 flash attention prefill/decode + split-KV)、天数智芯 Iluvatar CoreX(全 9 个算子,含 bf16 cuBLAS 兼容修复)
  • 详见仓库根目录 REPORT.md(环境说明、复现步骤、逐算子正确性表、性能优化数据)

Test plan

  • test/ops/*.py --device nvidia 全部通过
  • test/ops/*.py --device iluvatar 全部通过(远程验证)
  • test/test_infer.py --device nvidia --test 与 HF transformers 逐 token 匹配
  • test/test_infer.py --device iluvatar --test 远程端到端验证通过

Tangjinchi added 30 commits July 24, 2026 15:28
Add CPU kernels for add, argmax, embedding, linear, rms_norm, rope,
self_attention (causal + GQA + numerically stable softmax), and swiglu,
plus supporting tensor/op-dispatch changes. Add docs/SELF_ATTENTION_ZH.md
recording the self_attention implementation process.
Adds full Nvidia device support: runtime API, xmake/nvidia.lua build
config, and CUDA kernels for all 9 ops (add, argmax, embedding, linear,
rearrange stub, rms_norm, rope, self_attention, swiglu). Wires the
Qwen2 model (src/llaisys/models/qwen2.cc, python bindings) through to
run on --device nvidia, plus doc/reference notes from the Assignment
wooway777#3/wooway777#4 work.
…mbing

DeviceResource is now created per Runtime and reaches ops through
context().runtime().resource(); nvidia::Resource owns a cublasHandle_t.
linear's F32/F16/BF16 cases now call cublasSgemm/cublasSgemmEx with a
bias broadcast-add kernel afterward, instead of the hand-written
tiled-GEMM kernel (kept commented out for reference).
CI's Linux runner has no GPU, so ENABLE_NVIDIA_API isn't defined and the
NVIDIA case in linear's switch compiles out entirely, leaving `resource`
declared but unused under -Werror. Move the declaration inside the
NVIDIA-only case block so it doesn't exist at all on CPU-only builds.
…as fallback

Head dims that are multiples of 8 now go through a cudnn_frontend SDPA
graph (causal + native GQA support) instead of the hand-written V1
kernel; V1 stays as the fallback for head dims that aren't. Resource
gains a cudnnHandle_t alongside the existing cublasHandle_t, and
xmake links cudnn/nvrtc for the device and ops nvidia targets.

Update CLAUDE.md: the self_attention section now documents the cuDNN
path and marks the earlier staged flash-attention plan as superseded.
…el comments

Prep for debugging on remote GPU server: sync CLAUDE.md notes, drop
stale argmax TODO block in favor of a comment explaining why the V1
kernel was kept, and add a matching note to rope's V1 kernel.
…DNN 9.24.0 SDPA regression on sm_120 and Iluvatar CoreX compat findings

- self_attention_cuda.cu: close the #if CUDNN_MAJOR >= 8 guard right after
  the cudnn_frontend include instead of leaving it open to EOF.
- CLAUDE.md: record the cuDNN 9.24.0 SDPA runtime regression on sm_120
  (fixed by reverting to 9.20.0) and the Iluvatar CoreX toolchain findings
  relevant to the corex-cudnn-compat branch.
Adds LLAISYS_DEVICE_ILUVATAR device type and per-op src/ops/*/iluvatar/
directories (verbatim copies of nvidia/ sources, renamed to
llaisys::ops::iluvatar / llaisys::device::iluvatar namespaces) plus
xmake/iluvatar.lua wiring the clang++ -x ivcore toolchain used by the
remote Iluvatar CoreX box. Dispatch layer (runtime_api.cpp,
device_resource.cpp, op.cpp) and test scripts are not yet wired to the
new device type; still bootstrap state pending build validation on
the remote box.
set_languages("cxx17") only propagates to the cc/cxx tools, not the
custom cu tool (bare clang++ invoked with -x ivcore). Without an
explicit -std=c++17 cuflag, C++17 features (std::byte, if constexpr,
nested namespace defs) used by the copied nvidia/ sources failed to
compile on the remote box. Found via test/ops/add.py-style xmake
build of add_iluvatar.cu alone.
add_iluvatar.cu now compiles and archives cleanly through xmake on the
remote box after the -std=c++17 cuflag fix. Records the xmake
install/root gotchas hit along the way and sets the next step: widen
back to the full op glob plus llaisys-device-iluvatar.
Checks in the two on-box test scripts used so far so they can be
pulled directly on the remote Iluvatar box instead of pasted by hand:
verify_add_narrow.sh (standalone clang++ compile + narrowed xmake
build of just add_iluvatar.cu) and verify_full.sh (full 9-op +
device-iluvatar xmake build, the next step to run).
All 9 ops plus llaisys-device-iluvatar now compile cleanly on the
remote box with no source changes needed beyond the -std=c++17 cuflag
fix. Confirms the CUDNN_MAJOR>=8 guard correctly falls back to V1 for
self_attention/linear on this box's cuDNN 7.6.5. Source-level
compilation is fully verified; remaining work is aggregate xmake deps
+ dispatch-layer wiring, not more toolchain debugging.
Adds ENABLE_ILUVATAR_API-guarded LLAISYS_DEVICE_ILUVATAR branches to
runtime_api.{hpp,cpp} and device_resource.{hpp,cpp}, and to all 9 ops'
op.cpp (mirroring the existing NVIDIA case 1:1 - every iluvatar/*.cuh
signature already matched its nvidia counterpart exactly). Hooks
llaisys-device-iluvatar/llaisys-ops-iluvatar into the llaisys-device/
llaisys-ops aggregate targets via has_config("iluvatar-gpu"). Adds an
"iluvatar" branch to test_utils.py's device mapping functions and an
"iluvatar" --device choice to all 10 test scripts.

Verified no regression by rebuilding locally with --nv-gpu=y (ENABLE_
ILUVATAR_API is undefined here so the new branches compile out) and
rerunning add/self_attention/linear --device nvidia - all still pass.
Not yet build- or run-tested on the actual Iluvatar box.
Full xmake build llaisys --iluvatar-gpu=y (not narrowed to one
target), install, then test/ops/*.py --device iluvatar for all 8 ops
plus test_runtime.py --device iluvatar. This exercises the dispatch
layer wired in the previous commit for the first time.
xmake's built-in cuda.env rule unconditionally adds -lcudadevrt (and
-lcudart_static unless cudart/cudart_static is already linked) to any
target containing .cu files, regardless of the cuda.rdc setting.
Iluvatar's corex SDK only ships libcudart.so (no static variant) and
no libcudadevrt at all.

Fix: explicitly add_links("cudart") in xmake/iluvatar.lua so the real
shared library satisfies xmake's check and cudart_static never gets
auto-added. cudadevrt has no such escape hatch and is genuinely absent
on this platform, but also genuinely unneeded (cuda.rdc = false, no
relocatable device code linking) - verify_ops.sh now creates an empty
stub libcudadevrt.a via `ar rcs` before building, which satisfies the
linker without needing any real symbols.
xmake install only copies the .so into python/llaisys/libllaisys/;
`import llaisys` also needs the editable pip install. Script now
checks importability and runs pip install -e ./python if needed,
instead of leaving that as a manual step to remember each time.
test/ops/linear.py --device iluvatar fails numerically on bf16 (f32/
f16 pass). The cublasSgemmEx status return value was never checked;
asserting it will show whether the call is silently failing on
Iluvatar's CUDA-10.2-era cuBLAS compat lib, which may predate
CUDA_R_16BF support in cublasSgemmEx.
assert() compiles out under xmake's release build (-DNDEBUG), so it
never actually ran. printf isn't compiled out, giving a real signal.
Confirmed cublasSgemmEx returns status 15 (CUBLAS_STATUS_NOT_SUPPORTED)
for bf16 on Iluvatar's CUDA-10.2-era cuBLAS compat lib - it doesn't
support CUDA_R_16BF in this call. Tagging the print with "bf16" makes
that unambiguous in future runs.
7 of 8 ops plus test_runtime.py now pass --device iluvatar on the
actual remote box. linear's bf16 failure is root-caused to Iluvatar's
CUDA-10.2-era cuBLAS compat lib not supporting CUDA_R_16BF in
cublasSgemmEx (confirmed via a status printf - assert() was silently
compiled out under xmake's release -DNDEBUG build). Fix approach not
yet decided.
Confirmed cublasSgemmEx returns CUBLAS_STATUS_NOT_SUPPORTED (15) for
CUDA_R_16BF on Iluvatar's CUDA-10.2-era cuBLAS. Before deciding on a
fix, this dumps what the actual headers on the box declare: whether
cublasGemmEx (the more general function, distinct from cublasSgemmEx)
exists and what computeType it expects, whether cublasComputeType_t
exists at all (a newer enum vs. the older cudaDataType-based
signature), and whether CUDA_R_16BF/bf16 appear anywhere else.
cublasSgemmEx returns CUBLAS_STATUS_NOT_SUPPORTED (15) for CUDA_R_16BF
on Iluvatar's cuBLAS. Headers show cublasGemmEx is a distinct, more
general function that might support bf16 via an explicit computeType.
Fixed a type mismatch in the switch: cublasGemmEx's computeType param
is declared as cudaDataType (not cublasComputeType_t) in this header,
so it needs CUDA_R_32F, not CUBLAS_COMPUTE_32F - passing the wrong
enum type there wouldn't compile.
cublasGemmEx (with the correct cudaDataType-typed CUDA_R_32F
computeType, not cublasComputeType_t's CUBLAS_COMPUTE_32F) succeeds
where cublasSgemmEx returned CUBLAS_STATUS_NOT_SUPPORTED for bf16.
test/ops/linear.py --device iluvatar now passes for all shapes/
dtypes. All 8 ops pass on the actual Iluvatar box as of this commit.
Removed the temporary status asserts/printfs and the now-unused
<cassert> include added while diagnosing the bf16 cublasSgemmEx
failure. Matches the existing NVIDIA linear_cuda.cu style: status is
computed but not checked.
Documents the standard cuBLAS trick to remove the separate
add_bias_kernel launch: broadcast bias into out before the GEMM call,
then use beta=1.0f so the matrix product accumulates onto the
already-bias-filled out instead of a follow-up elementwise add. No
functional change - just leaves the plan in comments across all three
dtype branches for whoever implements it next.
qwen2.cc:251 read a GPU device pointer (max_idx->data()) directly from
host code via reinterpret_cast, which is an illegal memory access on
any real GPU device - it only "worked" on CPU because CPU device
pointers and host pointers share the same address space there. This
masked the bug since full end-to-end inference was never re-verified
on NVIDIA after linear/self_attention switched to cuBLAS/cuDNN.
Fixed by copying the single int64 back to host via memcpy_sync
(mirrors the existing pattern in Tensor::debug(), tensor.cpp:149-164)
before returning it on non-CPU devices.

Found while building test/benchmark_infer.py, a new script that times
prefill vs decode steps separately (via an optional step_context hook
added to qwen2.py's generate(), a no-op by default) and can optionally
wrap each step in an NVTX range for nsys. Verified test_infer.py
--device nvidia --test now passes end-to-end (32-token match against
HF) in release mode.
…ode bottleneck

benchmark_infer.py showed decode running at 640ms/token (1.56 tok/s)
on the real 1.5B model. A standalone micro-benchmark at the actual
decode shape isolated self_attention at 20.36ms/call vs linear's
0.012-0.039ms/call (negligible) - 28 layers x ~20ms accounts for
nearly all of the observed per-token latency. Confirms the existing
no-graph-caching TODO is the dominant real cost, not linear (which
was the other open question after the cuBLAS switch).
Tangjinchi added 11 commits August 5, 2026 18:50
Replaces the TODO-only draft with actual environment details, per-op
and per-platform status (NVIDIA 8/8, Iluvatar 8/8), the linear bf16
cuBLAS-compat root cause and fix, the qwen2.cc NVIDIA segfault
found/fixed, and the performance-analysis findings so far (self_attention's
per-call cuDNN graph rebuild as the confirmed decode bottleneck).
Meant to keep growing as the performance-optimization phase continues.
…d-KV paths both blocked, bucketing+padding-mask is the chosen next step
…ernel

Per-call cudnn_frontend graph rebuild was the dominant decode-time cost
(~20ms/call, ~570ms/token across 28 layers). A proper fix needed bucketing
total_len plus padded/masked K/V, which was never worked out safely; on top
of that, cuDNN has real cross-platform fragility (9.24.0 SDPA crashes on
this machine's sm_120 GPU, and Iluvatar's cuDNN 7.6.5 predates the Graph
API entirely). Drop cuDNN from self_attention on both NVIDIA and Iluvatar,
unify on the V1 hand-written kernel, and stop linking cudnn/nvrtc.

Verified: xmake build llaisys --nv-gpu=y builds clean with no cuDNN linked,
all 8 ops' test/ops/*.py --device nvidia pass, and test_infer.py --device
nvidia --test still matches HF token-for-token.
…path

self_attention's cuDNN graph-cache exploration concluded cuDNN acceleration
isn't a good fit here (per-call graph rebuild dominates decode time, and a
proper fix needs bucketing+padding-mask that was never worked out safely,
plus real cross-platform fragility). self_attention is back to the V1
hand-written kernel on both NVIDIA and Iluvatar, and neither platform links
cudnn/nvrtc anymore.
…ify report

Split KV direction across warps for decode when total_len > 256 (flash_attention_decode_splitkv_cuda.cu), fixing the SM-occupancy gap left by the single-warp-per-head decode kernel on long KV caches. Verified against the existing decode kernel (bit-for-bit within fp tolerance, F32/F16/BF16, total_len 1-4000) and benchmarked at both kernel level (up to 7.3x at total_len=16000) and end-to-end (~1.29x decode speedup on a real generation).

Also folds in an existing pass of comment cleanup/dead-code removal across the CPU/NVIDIA/Iluvatar op implementations (no functional changes), and trims REPORT.md for the upcoming submission.
…verification

Rebuilds against latest code, re-runs the 8 op tests, auto-locates the local model weights if present, and runs test_infer.py --test on the Iluvatar box.
…tar instance

The previous remote Iluvatar instance was destroyed. This script covers the full path on a fresh instance of the same course image: install xmake, clone the repo over HTTPS, install the lightweight platform-agnostic Python deps (leaves the Iluvatar-patched torch build alone), build, and run the op + end-to-end inference tests, falling back to a HuggingFace download if no local model weights are found.
…ce recreation

The original remote Iluvatar instance was destroyed; bootstrap_and_verify.sh was used to stand up a fresh instance of the same course image and re-verify the full stack: build, all 8 ops, and test_infer.py --test (model auto-downloaded from Hugging Face since the fresh instance had no local weights) all passed, confirming the earlier cuDNN-removal change didn't regress Iluvatar.
add_cugencodes("native") silently adds no -gencode flags if no GPU is
visible at build time, which could produce a binary with no A100
(sm_80) machine code if the build environment can't see the GPU.
Keep native detection for local dev but always also target sm_80.
…ary table

Note the actual grading hardware (A100 / Iluvatar TG150-200) differs from
this session's dev hardware, and record the sm_80 gencode fallback already
applied for that gap.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant