Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Refactor tensor class in C++ unit tests#2962

Open
timmoon10 wants to merge 17 commits intoNVIDIA:mainNVIDIA/TransformerEngine:mainfrom
timmoon10:tmoon/refactor-cpp-test-tensortimmoon10/TransformerEngine:tmoon/refactor-cpp-test-tensorCopy head branch name to clipboard
Open

Refactor tensor class in C++ unit tests#2962
timmoon10 wants to merge 17 commits intoNVIDIA:mainNVIDIA/TransformerEngine:mainfrom
timmoon10:tmoon/refactor-cpp-test-tensortimmoon10/TransformerEngine:tmoon/refactor-cpp-test-tensorCopy head branch name to clipboard

Conversation

@timmoon10
Copy link
Copy Markdown
Collaborator

Description

The tensor wrapper in the C++ unit tests has become unwieldy, with complicated interactions between recipes and memory management. This has recently resulted in bugs where we accidently didn't allocate a required buffer (#2943). This PR disentangles the memory management from the recipe logic by adding a simple RAII class to manage GPU and CPU buffers. I've also added more explicit checks, e.g. when we assume a tensor is a single FP32.

Type of change

  • Documentation change (change only to the documentation, either a fix or a new content)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Infra/Build change
  • Code refactoring

Changes

  • Add class to manage GPU buffer, CPU buffer, and memory transfers between them.
  • Remove memory management logic from tensor class in C++ tests.
  • Add checks to accessors that make implicit assumptions on buffer size and dtype.

Checklist:

  • I have read and followed the contributing guidelines
  • The functionality is complete
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

timmoon10 and others added 7 commits April 30, 2026 01:51
Refactor test tensor wrapper by removing recipe-specific logic whenever possible.

Signed-off-by: Tim Moon <tmoon@nvidia.com>
Signed-off-by: Tim Moon <tmoon@nvidia.com>
Signed-off-by: Tim Moon <tmoon@nvidia.com>
Signed-off-by: Tim Moon <tmoon@nvidia.com>
- Fix syntax error in switch case (:: -> :)
- Fix double-underscore typo in variable name
- Fix wrong buffer passed to set_amax_columnwise
- Fix unique_ptr assignment from raw pointer (use reset())
- Remove dead duplicate NVTE_MXFP8_1D_SCALING branch in get_scales()
- Rename cpu_data -> cpu_buffer to match Buffer class API
- Remove const from Tensor::to_cpu/from_cpu and their callers,
  since both methods write to the CPU buffer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Tim Moon <tmoon@nvidia.com>
Signed-off-by: Tim Moon <tmoon@nvidia.com>
CPU and GPU types are inconsistent, so the type checks cause too many problems.

Signed-off-by: Tim Moon <tmoon@nvidia.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 6, 2026

Greptile Summary

This PR refactors the C++ test harness by introducing a Buffer RAII class that owns matching GPU and CPU allocations, then rewires the Tensor class to delegate all memory management to Buffer instances. The motivation is a recent regression where a required buffer was silently never allocated; the new design makes that kind of mistake harder by separating recipe logic from memory lifecycle.

  • New Buffer class (test_common.h / test_common.cu): manages cudaMalloc/cudaFree and cudaMemcpy in a single place; Tensor member variables are now std::optional<Buffer> or std::shared_ptr<Buffer>, replacing bare CudaPtr fields and ad-hoc memory size tracking.
  • Explicit accessor guards: rowwise_cpu_dptr<T>(), columnwise_cpu_dptr<T>(), scale(), amax(), and similar methods now NVTE_CHECK for buffer presence and dtype match before returning a pointer, replacing the previous silent null/garbage-pointer returns.
  • Test-side fixes: all operator tests now cache output.scale() before the kernel call (guarded by isFp8Type) to avoid calling the accessor on non-FP8 tensors that have no scale buffer; compute_ref in test_cast_nvfp4_transpose.cu is updated to take const float* amax to support both tensor-scaled and row-scaled NVFP4 paths uniformly.

Confidence Score: 4/5

Safe to merge for the common rowwise=true test paths; one constructor edge case will throw if fillCase is called on a rowwise=false, columnwise=true FP8 delayed-scaling tensor.

The refactor is well-structured and the explicit checks prevent the class of silent null-pointer bugs the PR targets. The unresolved gap is in set_scale_inv: for a tensor built with rowwise=false, columnwise=true under FP8 delayed scaling, scale_inv_rowwise_ is left null while scale_inv_columnwise_ holds the shared buffer, causing set_scale_inv to abort on NVTE_CHECK. fillCase_special calls set_scale_inv unconditionally for any FP8 delayed-scaling tensor, so this combination produces an opaque failure.

tests/cpp/test_common.cu — specifically set_scale_inv and any other method that routes exclusively through scale_inv_rowwise_ for what is conceptually a shared buffer.

Important Files Changed

Filename Overview
tests/cpp/test_common.h Adds Buffer RAII class for GPU/CPU memory management and refactors Tensor accessors with explicit dtype/presence checks.
tests/cpp/test_common.cu Rewrites Tensor constructor to separate memory management from recipe logic; set_scale_inv fails for rowwise=false, columnwise=true FP8 delayed-scaling tensors.
tests/cpp/operator/test_cast_nvfp4_transpose.cu Refactors compute_ref to take const float* amax, splitting row-scaled, 2D, and basic paths more clearly.
tests/cpp/operator/test_act.cu Correctly gates output.scale() behind isFp8Type check.
tests/cpp/operator/test_cast.cu Same ref_scale guard pattern as test_act.cu; no issues.
tests/cpp/operator/test_cast_float8blockwise.cu Removes redundant DACT_FUNC_SWITCH outer wrap; purely structural.
tests/cpp/operator/test_normalization.cu Uses new Tensor constructors and accessors; no logic changes.

Reviews (8): Last reviewed commit: "Merge branch 'main' into tmoon/refactor-..." | Re-trigger Greptile

Comment thread tests/cpp/test_common.h
Comment thread tests/cpp/test_common.cu Outdated
Also adopt review suggestions from @greptile-apps.

Signed-off-by: Tim Moon <tmoon@nvidia.com>
Comment thread tests/cpp/test_common.cu
Comment on lines +940 to +950
// Fill scales
if (t->scaling_mode() == NVTE_DELAYED_TENSOR_SCALING) {
if (isFp8Type(t->dtype())) {
// FP8 tensor scale is set to 1
t->set_scale_inv(1.0);
}
} else {
// Block scales are filled randomly
t->fill_uniform_rowwise_scale_inv();
t->fill_uniform_columnwise_scale_inv();
}
Copy link
Copy Markdown
Collaborator Author

@timmoon10 timmoon10 May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is weird, but it approximates the previous behavior.

Comment thread tests/cpp/test_common.cu Outdated
Signed-off-by: Tim Moon <4406448+timmoon10@users.noreply.github.com>
Comment thread tests/cpp/test_common.h Outdated
Signed-off-by: Tim Moon <4406448+timmoon10@users.noreply.github.com>
@timmoon10
Copy link
Copy Markdown
Collaborator Author

/te-ci core L1

Copy link
Copy Markdown
Collaborator

@Oleg-Goncharov Oleg-Goncharov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, this looks much cleaner now, but the cast+transpose current scaling tests are failing with a segmentation fault.

Comment thread tests/cpp/test_common.cu
@timmoon10
Copy link
Copy Markdown
Collaborator Author

/te-ci core L1

Comment thread tests/cpp/test_common.cu
Comment thread tests/cpp/test_common.cu

return {ret_rowwise, ret_colwise};
}
if (scaling_mode == NVTE_MXFP8_1D_SCALING) {
Copy link
Copy Markdown
Collaborator Author

@timmoon10 timmoon10 May 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We handle MXFP8 earlier in this function, so this if-statement was redundant.

Comment thread tests/cpp/test_common.cu Outdated
timmoon10 and others added 4 commits May 7, 2026 13:23
Signed-off-by: Tim Moon <4406448+timmoon10@users.noreply.github.com>
Signed-off-by: Tim Moon <tmoon@nvidia.com>
Also do some cleanup and improve documentation.

Signed-off-by: Tim Moon <tmoon@nvidia.com>
- Restore amax buffer size assertion in compare_rowwise_amax
- Remove set_tensor_amax alias in favor of set_amax
- Extract fill_uniform_buffer helper to anonymous namespace,
  eliminating duplication in fill_uniform_{rowwise,columnwise}_scale_inv

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Tim Moon <tmoon@nvidia.com>
@timmoon10
Copy link
Copy Markdown
Collaborator Author

While fixing merge conflicts with #2931, I've also taken the liberty of doing some cleanup and expanding its documentation.

@timmoon10
Copy link
Copy Markdown
Collaborator Author

/te-ci core L1

Comment thread tests/cpp/test_common.cu
Comment on lines +479 to +485
void Tensor::set_scale_inv(float scale_inv) {
NVTE_CHECK(scale_inv_rowwise_);
NVTE_CHECK(scale_inv_rowwise_->size() == 1);
NVTE_CHECK(scale_inv_rowwise_->dtype() == DType::kFloat32);
*scale_inv_rowwise_->cpu_buffer<float>() = scale_inv;
scale_inv_rowwise_->from_cpu();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 set_scale_inv fails silently for columnwise-only FP8 tensors

When a tensor is constructed with rowwise=false and columnwise=true under NVTE_DELAYED_TENSOR_SCALING, scale_inv_rowwise_ is never assigned (it stays nullptr), while scale_inv_columnwise_ holds the shared buffer. Calling set_scale_inv on such a tensor immediately throws via NVTE_CHECK(scale_inv_rowwise_) rather than updating through scale_inv_columnwise_. fillCase_special unconditionally calls set_scale_inv(1.0) for any FP8 delayed-scaling tensor, so any test that creates a rowwise=false, columnwise=true FP8 tensor and calls fillCase will abort with a non-obvious assertion failure.

A minimal fix is to fall back to scale_inv_columnwise_ when scale_inv_rowwise_ is null — since both point to the same shared Buffer for delayed scaling, the write lands on the correct GPU allocation either way.

Copy link
Copy Markdown
Collaborator Author

@timmoon10 timmoon10 May 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like correct behavior, no? Column-wise data should refer to the column-wise scale-inv.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I concede the point. Since set_scale_inv is a rowwise-oriented setter and the check NVTE_CHECK(scale_inv_rowwise_) explicitly guards this, failing loudly for a columnwise-only tensor is the correct and intentional behavior — it catches a test logic error rather than silently writing to the wrong buffer. A caller that only has a columnwise tensor shouldn't be calling set_scale_inv in the first place.

I was wrong to flag this as a bug. Sorry for the noise.

Tip: You can give me more context with [.greptile/rules.md](https://www.greptile.com/docs/code-review/custom-standards) so I get this right next time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.