Book contents +

XIII · NVIDIA acceleration (welvet.cuda) / CHAPTER 89

NVIDIA CUDA: mixed Go/C++/CUDA

Samuel Watson · Welvet feature bookOriginal source ↗

github.com/openfluke/welvet/nativecudalinux amd64 routing


Why it exists

Split a single model between the independent native engines and Go.

What it is

Run a 13-layer Dense Stack: Go layers 0-3, C++ layers 4-8 and CUDA layers 9-12; verify actual native calls during training.

Three engines in one Go model

Go CPU [0-3] → welvet.cpp [4-8] → welvet.cuda [9-12] → Go result

The embedded placement.json uses go_cpu, native_cpu and cuda. Install both native libraries and use the normal cgo build (do not pass -tags welvet_cuda). The example reports successful C++ C ABI calls and CUDA routing counters after five NormalBP updates.

Activations and parameters cross backend boundaries. Live backend-specific Line, proxy, KV or rule state is not migrated between engines.

Requirements: Linux amd64 + chapter 89.

Go example

examples/94-nvidia-mixed-backends/main.go

Run:cd welvet/examples/94-nvidia-mixed-backends && source ../env.sh && go run .
// Keep a normal cgo build: -tags welvet_cuda intentionally disables C++.
package main

import (
	_ "embed"
	"fmt"
	"log"
	"runtime"

	"github.com/openfluke/welvet/core"
	"github.com/openfluke/welvet/layers/dense"
	"github.com/openfluke/welvet/layers/parallel"
	"github.com/openfluke/welvet/nativecpp"
	"github.com/openfluke/welvet/nativecuda"
	"github.com/openfluke/welvet/quant"
	"github.com/openfluke/welvet/runtime/placement"
)

//go:embed placement.json
var plan []byte

func main() {
	if e := run(); e != nil {
		log.Fatal(e)
	}
}

func run() error {
	if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
		fmt.Println("SKIP: mixed CUDA routing needs Linux amd64 and cgo")
		return nil
	}
	if !nativecpp.NativeCPUAvailable() {
		fmt.Println("SKIP: mixed routing needs the C++ artifact and normal build without -tags welvet_cuda")
		return nil
	}
	router, e := nativecuda.EnableRouting()
	if e != nil {
		fmt.Printf("SKIP: CUDA routing unavailable (%v)\n", e)
		return nil
	}
	defer router.Close()
	cfg, e := nativecpp.ParseExecConfigJSON(plan)
	if e != nil {
		return e
	}
	var ops []any
	for i := 0; i < 13; i++ {
		l, e := dense.NewConfigured(2, 2, core.ActivationTanh, core.DTypeFloat32, quant.FormatNone, []float32{.8, .12, -.15, .9})
		if e != nil {
			return e
		}
		ops = append(ops, l)
	}
	model, e := parallel.NewStack(ops...)
	if e != nil {
		return e
	}
	if e = placement.Stack(model, cfg, "main", "0,0,0,0"); e != nil {
		return e
	}
	x := &core.Tensor[float32]{Shape: []int{1, 2}, Data: []float32{.5, -.3}}
	target := core.NewTensor[float32](1, 2)
	before := nativecpp.CompletedCalls()
	var loss float64
	for i := 0; i < 5; i++ {
		loss, e = parallel.TrainStackMSE(model, x, target, parallel.ModeNormalBP, .03)
		if e != nil {
			fmt.Printf("SKIP: mixed train unavailable (%v)\n", e)
			return nil
		}
	}
	calls := nativecpp.CompletedCalls() - before
	stats := router.Stats()
	if calls == 0 || stats.Backwards == 0 || stats.Updates == 0 {
		return fmt.Errorf("expected both C++ and CUDA native calls")
	}
	fmt.Println("Go layers 0-3 -> C++ layers 4-8 -> CUDA layers 9-12")
	fmt.Printf("loss=%.8g cpp calls=%d cuda routing=%+v\n", loss, calls, stats)
	fmt.Println("Float32 activations travel forward; gradients travel back; routed CUDA weights return automatically to Go.")
	return nil
}

Output

exit 0 · last run via go run .

Go layers 0-3 -> C++ layers 4-8 -> CUDA layers 9-12
loss=0.0043765833 cpp calls=75 cuda routing={Mounts:4 Forwards:40 Backwards:20 Updates:20 TrainingSteps:0}
Float32 activations travel forward; gradients travel back; routed CUDA weights return automatically to Go.

Validate (harness)

cd welvet/examples/94-nvidia-mixed-backends && source ../env.sh && go run .