بنقرة واحدة
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