ワンクリックで
add-primitive
Add a new primitive operation to anvl (prim_* function with stablehlo, reverse rules, and tests)
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Add a new primitive operation to anvl (prim_* function with stablehlo, reverse rules, and tests)
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | add-primitive |
| description | Add a new primitive operation to anvl (prim_* function with stablehlo, reverse rules, and tests) |
| user_invocable | true |
Read vignettes/extending_primitive.Rmd first — it is the primary guide with a complete walkthrough (primitive creation via new_primitive(), stablehlo rule, reverse rule, nv_* API, file organization). This skill covers additional details not in the vignette.
../stablehlo/R/ for an op-<name>.R file (e.g. op-add.R).stablehlo::hlo_<name>() or it can be expressed as a combination of existing StableHLO operations.../stablehlo/SPEC.md) for the operation's semantics and constraints.Use templates from man-roxygen/ where applicable:
@template param_prim_operand_any (or _float, _signed_numeric)@template params_prim_lhs_rhs_any (or _numeric, _float)@template return_prim_unary, return_prim_binary, return_prim_compare, return_prim_reduce@templateVar primitive_id <name> + @template section_rules@section StableHLO:\n Lowers to [stablehlo::hlo_<name>()].@export to the roxygen block.The vignette shows the manual graph_desc_add() approach. For simple ops without extra parameters, pass a body produced by make_unary_op() / make_binary_op() to new_primitive(). Both helpers take a stablehlo type-inference function and rely on the lexically-bound self installed by new_primitive():
# Simple unary (e.g. prim_abs, prim_negate):
prim_<name> <- new_primitive("<name>", make_unary_op(stablehlo::infer_types_<name>))
# Simple binary (e.g. prim_add, prim_mul):
prim_<name> <- new_primitive("<name>", make_binary_op(stablehlo::infer_types_<name>))
Beyond what the vignette covers:
prim_* primitives — never use R arithmetic directly.abs at 0, floor everywhere), follow PyTorch conventions (subgradients, zero gradients, etc.). Read existing rules in R/rules-reverse.R for examples.If the primitive should also run under local_backend("quickr"), add a quickr lowering in R/rules-quickr.R via quickr_register_prim_lowerer(prim_<name>, function(...) { ... }). This emits plain R code for the quickr backend. If you skip it, the primitive still works on the xla backend — the quickr meta test simply excludes it from coverage.
nv_*)Follow the /add-api-function skill for this step — it covers design principles (R naming, semantics, generics), implementation, documentation, _pkgdown.yml placement, and testing.
prim_* primitives are auto-included under the "Primitives" section in _pkgdown.yml via starts_with("prim_").
The vignette covers file organization and the torch-vs-manual decision. This section adds concrete patterns.
inst/extra-tests/) when the operation has a torch equivalent and the reverse rule is non-trivial.tests/testthat/test-primitives-stablehlo.R and tests/testthat/test-primitives-reverse.R) when no torch equivalent exists or the expected output can be stated analytically.Choose one approach, not both.
Use describe() / it() blocks. Cover:
dims, permutation values)inst/extra-tests/test-primitives-stablehlo-torch.R)describe("prim_foo", {
gen_foo <- function(shp, dtype) {
n <- if (!length(shp)) 1L else prod(shp)
vals <- c(0, -1, 1, 0.5, -0.5, 100, -100, sample(rnorm(100), n - 7L))
vals <- vals[seq_len(n)]
if (!length(shp)) vals else array(vals, shp)
}
it("works for scalars", {
expect_jit_torch_unary(prim_foo, torch::torch_foo, integer(), gen = gen_foo)
})
it("works for vectors", {
expect_jit_torch_unary(prim_foo, torch::torch_foo, 10L, gen = gen_foo)
})
it("works for matrices", {
expect_jit_torch_unary(prim_foo, torch::torch_foo, c(3, 4), gen = gen_foo)
})
})
For binary ops, use expect_jit_torch_binary with gen_x / gen_y.
inst/extra-tests/test-primitives-reverse-torch.R)describe("prim_foo", {
gen_foo <- function(shp, dtype) {
n <- if (!length(shp)) 1L else prod(shp)
vals <- c(0.5, -0.5, 1, -1, 2, -2, sample(rnorm(100), max(0, n - 6)))
vals <- vals[seq_len(n)]
if (!length(shp)) vals else array(vals, shp)
}
it("scalar gradient", {
verify_grad_uni(prim_foo, torch::torch_foo, gen = gen_foo)
})
it("tensor gradient", {
verify_grad_uni_tensor(prim_foo, torch::torch_foo, shape = c(3, 4), gen = gen_foo)
})
})
For binary reverse tests, use verify_grad_biv / verify_grad_biv_tensor with gen_lhs / gen_rhs.
| Helper | File | Purpose |
|---|---|---|
expect_jit_torch_unary | inst/extra-tests/torch-helpers.R | Compare unary forward with torch |
expect_jit_torch_binary | inst/extra-tests/torch-helpers.R | Compare binary forward with torch |
verify_grad_uni | inst/extra-tests/test-primitives-reverse-torch.R | Compare unary gradient (scalar + tensor) |
verify_grad_uni_tensor | inst/extra-tests/test-primitives-reverse-torch.R | Compare unary gradient (tensor only) |
verify_grad_biv | inst/extra-tests/test-primitives-reverse-torch.R | Compare binary gradient (scalar + tensor) |
verify_grad_biv_tensor | inst/extra-tests/test-primitives-reverse-torch.R | Compare binary gradient (tensor only) |
generate_test_data | inst/extra-tests/torch-helpers.R | Random input sampling by dtype |
Custom generators (gen, gen_x, gen_y, gen_lhs, gen_rhs) have signature function(shp, dtype) and return an R array (or scalar for integer() shape).
tests/testthat/test-primitives-meta.R automatically checks that every prim_* primitive has corresponding stablehlo and reverse tests. Your new primitive will be flagged if tests are missing. Tests must use the full prim_<name> identifier as the describe() / test_that() label (e.g. describe("prim_foo", { ... })).
devtools::document()
devtools::load_all()
devtools::test() # or run specific test files
prim_<name> <- new_primitive("<name>", function(...) { graph_desc_add(self, ...) }) with roxygen docs and @export (auto-registered into the internal primitive registry). For simple shapes, use make_unary_op() / make_binary_op() / make_reduce_op() / make_compare_op() instead of writing the body by hand.prim_<name>[["stablehlo"]] in R/rules-stablehlo.Rprim_<name>[["reverse"]] in R/rules-reverse.Rquickr_register_prim_lowerer(prim_<name>, ...) in R/rules-quickr.Rnv_<name> added via /add-api-function skilldescribe("prim_<name>", { ... }))devtools::document() rundevtools::test() passes