[None][feat] Add AD custom models for Jamba (Mamba v1 + Attention hybrid)#200
[None][feat] Add AD custom models for Jamba (Mamba v1 + Attention hybrid)#200govind-ramnarayan wants to merge 5 commits into
Conversation
ff0d687 to
f530ac8
Compare
Onboard AI21-Jamba-Reasoning-3B and AI21-Jamba-Mini-1.5 for AutoDeploy.
These hybrid models use Mamba v1 selective scan (per-element state matrix)
plus GQA attention layers, with optional Sparse MoE.
New files:
- modeling_jamba.py: prefill-only custom model with torch_causal_conv1d
and torch_mamba_v1_selective_scan custom ops
- torch_mamba_v1.py: Mamba v1 selective scan reference op
- torch_backend_mamba_v1.py: cached backend with AttentionDescriptor
for autoregressive generation (prefill + decode paths)
- test_jamba_modeling.py: 21 hierarchical equivalence tests
- test_torch_mamba_v1_cached_op.py: 6 cached op tests including
prefill-to-decode continuity validation
- Config YAMLs for both models (Reasoning-3B single GPU, Mini-1.5 EP)
Bug fix: corrected mutates_args={} to declare actual cache mutations
in torch_cached_mamba_v1, torch_cached_ssm, and
torch_cached_causal_conv1d custom ops.
Signed-off-by: Gram Narayan <gramnarayan@nvidia.com>
Signed-off-by: Govind Ramnarayan <105831528+govind-ramnarayan@users.noreply.github.com>
Signed-off-by: Govind Ramnarayan <105831528+govind-ramnarayan@users.noreply.github.com>
f530ac8 to
2b4a450
Compare
|
[AGENT] Addressed review comments: Config cleanup (remove defaults, minimize):
All 27 unit tests pass after these changes: |
- Strip all defaults from jamba_mini_1.5.yaml and jamba_reasoning_3b.yaml: trust_remote_code, enable_block_reuse (auto-disabled by AD runtime for non-paged SSM models), free_gpu_memory_fraction, max_batch_size, max_seq_len, max_num_tokens, compile_model cuda_graph settings, and chat-templated prompts - Delete jamba_mini_1.5_reduced.yaml (test-only, no longer needed) Signed-off-by: Govind Ramnarayan <105831528+govind-ramnarayan@users.noreply.github.com>
| yaml_extra: ['dashboard_default.yaml', 'world_size_4.yaml', 'deepseek_v2_ep.yaml'] | ||
| - name: ai21labs/AI21-Jamba-1.5-Mini | ||
| yaml_extra: ['dashboard_default.yaml', 'world_size_2.yaml'] | ||
| yaml_extra: ['dashboard_default.yaml', 'world_size_2.yaml', 'jamba_mini_1.5.yaml'] |
There was a problem hiding this comment.
This seems duplicated. Can we get rid of it? I think it is not actually a different model...
| # Returns (seq_len, seq_start, slot_idx) | ||
| @torch.library.custom_op("auto_deploy::torch_cached_causal_conv1d", mutates_args={}) | ||
| @torch.library.custom_op( | ||
| "auto_deploy::torch_cached_causal_conv1d", mutates_args={"conv_state_cache"} |
There was a problem hiding this comment.
I think this makes sense, but why was this working before?
AI21-Jamba-1.5-Mini and AI21-Jamba-Mini-1.5 are the same checkpoint (identical config.json). Keep only AI21-Jamba-Mini-1.5 (the canonical name per AI21's own README new_version pointer). Signed-off-by: Govind Ramnarayan <105831528+govind-ramnarayan@users.noreply.github.com>
- Remove AI21-Jamba-Large-1.7 from registry (too large, no Mamba v1 backend config available yet) - torch_cached_mamba_v1: extract use_initial_states and raise ValueError for chunked prefill (consistent with torch_cached_ssm behavior) - modeling_jamba: add assert position_ids is not None in both forward methods (CA1 compliance) - test_jamba_modeling: add MoE equivalence tests for JambaSparseMoeBlock and Mamba+MoE decoder layer (num_experts=4 config) Signed-off-by: Govind Ramnarayan <105831528+govind-ramnarayan@users.noreply.github.com>
|
|
||
| # Infer dtype from hidden_states if available, else from A | ||
| hs_node = source_attn_node.args[0] | ||
| hs_dtype = hs_node.meta["val"].dtype if "val" in hs_node.meta else A_fake.dtype |
There was a problem hiding this comment.
I think this might always be there - we do not check this in the torch_backend_mamba implementation, we just access this, right?
| hs_dtype = hs_node.meta["val"].dtype if "val" in hs_node.meta else A_fake.dtype | ||
|
|
||
| # Map to SSMResourceHandler: num_heads=D_inner, head_dim=1, d_state=d_state | ||
| ssm_state_dtype = cls.resolve_cache_dtype(cache_config.mamba_ssm_cache_dtype, hs_dtype) |
There was a problem hiding this comment.
Hmm, does this make sense? I thought that we did not have proper heads for Mamba 1- but I guess that Mamba 1 mathematically maps to Mamba2 with heads of size 1, is that correct? Since Mamba 2 has different values per head
|
|
||
| @classmethod | ||
| def get_constants(cls, source_attn_node: Node) -> List[Constant]: | ||
| # No constants for Mamba v1 (unlike v2 which has time_step_limit, chunk_size) |
There was a problem hiding this comment.
Is the lack of chunk_size here because we are not supporting chunked prefill yet, or is it intrinsic to the op? If the former, can you clarify this comment?
| ) | ||
|
|
||
|
|
||
| def _create_moe_config(num_experts=4): |
There was a problem hiding this comment.
Maybe you could make this cleaner by getting the jamba config with _create_small_config() and then editing certain parameters?
| ) | ||
|
|
||
|
|
||
| def _create_moe_config(num_experts=4): |
There was a problem hiding this comment.
Check against other classes to see if this sort of config-per-test is necessary. I feel like this can just be one config with some potential overrides.
| def test_jamba_mamba_decoder_layer_equivalence(B, S, dtype): | ||
| """Test mamba decoder layer against HF.""" | ||
| HFLayer = _get_hf_mamba_decoder_layer() | ||
| if HFLayer is None: |
| def test_jamba_full_model_equivalence(B, S, dtype): | ||
| """Test full model logits against HF (slow_forward path) on CUDA.""" | ||
| HFModel = _get_hf_jamba_model() | ||
| if HFModel is None: |
There was a problem hiding this comment.
Ditto for all the pytest skips. Let's let it error if it is missing.
| except ImportError: | ||
| from transformers import AutoConfig, PretrainedConfig | ||
|
|
||
| class JambaConfig(PretrainedConfig): |
There was a problem hiding this comment.
Is there a reason we have a fallback here? Is it possible that it is not in transformers? What am I missing?
Either way, in modeling code we don't want to create this config if we don't have to, if AutoConfig will pick it up. Let's see if we can get rid of this.
| for i in range(self.num_hidden_layers) | ||
| ] | ||
|
|
||
| AutoConfig.register("jamba", JambaConfig, exist_ok=True) |
There was a problem hiding this comment.
Ditto, let's get rid of it.
| hs, A, B, C, D, dt = _random_v1_params(device, dtype, batch, seq_len, d_inner, d_state) | ||
|
|
||
| # Compute reference final state using _mamba_v1_prefill directly | ||
| from tensorrt_llm._torch.auto_deploy.custom_ops.mamba.torch_backend_mamba_v1 import ( |
There was a problem hiding this comment.
Put test imports on top of the file.
| rtol = 1e-2 | ||
| torch.manual_seed(42) | ||
| torch.cuda.empty_cache() | ||
| return {"device": device, "dtype": dtype, "atol": atol, "rtol": rtol} |
There was a problem hiding this comment.
nit: a bit overly long, just put those directly in here.
| ResourceHandlerDict, | ||
| SSMResourceHandler, | ||
| ) | ||
| from .torch_mamba_v1 import _torch_mamba_v1_selective_scan # noqa: F401 — ensures op registered |
There was a problem hiding this comment.
This looks weird. What is going on here? We are not using the op in this file?
|
|
||
|
|
||
| @torch.library.custom_op("auto_deploy::torch_cached_ssm", mutates_args={}) | ||
| @torch.library.custom_op("auto_deploy::torch_cached_ssm", mutates_args={"ssm_state_cache"}) |
There was a problem hiding this comment.
Ditto. Let us see if we can revert these mutates_args changes even if we think it might be a bug, because Mamba2 has been working well. Can you explain what this might effect?
| ssm_cache, | ||
| ) | ||
|
|
||
| torch.testing.assert_close(y_cached, y_ref, atol=env["atol"], rtol=env["rtol"]) |
There was a problem hiding this comment.
Actually just use hard-coded tolerances here, it is clearer.
| atol = 1e-2 | ||
| rtol = 1e-2 | ||
| torch.manual_seed(42) | ||
| torch.cuda.empty_cache() |
There was a problem hiding this comment.
can you explain this empty_cache() call?
|
|
||
| def test_flattened_multi_sequence_prefill(self, env): | ||
| """Multiple sequences flattened into [1, total_len, D]: each segment matches reference.""" | ||
| device, dtype = env["device"], env["dtype"] |
There was a problem hiding this comment.
Ditto, think it is just clearer to have these in the test. Maybe get rid of the env except to set random seed and similar things.
| [sum(lens[:i]) for i in range(len(lens))], device=device, dtype=torch.int32 | ||
| ) | ||
| use_initial = torch.zeros(len(lens), device=device, dtype=torch.bool) | ||
| batch_info = torch.tensor([len(lens), total, 0], device=device, dtype=torch.int32) |
There was a problem hiding this comment.
Can you put a comment here as to what this means? There is an implicit assumption here on what the various entries of batch_info mean, and it will be helpful for later refactoring to comment what the interpretation here is.
| B_seg = B[:, offset : offset + ln, :] | ||
| C_seg = C[:, offset : offset + ln, :] | ||
| dt_seg = dt[:, offset : offset + ln, :] | ||
| y_seg = torch.ops.auto_deploy.torch_mamba_v1_selective_scan( |
There was a problem hiding this comment.
I'm a little confused where the "state" is here. It almost looks like each part of y_ref is independently computed.
| """Multi-batch prefill (flattened) then multi-batch decode.""" | ||
| device, dtype = env["device"], env["dtype"] | ||
| d_inner, d_state = 16, 4 | ||
| lens = [6, 4] |
There was a problem hiding this comment.
maybe rename to prefill_lens?
|
|
||
| if s == 1: | ||
| # === Decode-only: single token per sequence === | ||
| ssm_batch = cache_3d.index_select(0, slot_idx_t) # [num_seq, D, N] |
There was a problem hiding this comment.
Can you comment here what this is doing?
|
|
||
| seq_start = cu_seqlen[:num_seq] | ||
|
|
||
| for i in range(num_seq): |
There was a problem hiding this comment.
Why is this a for loop? It seems like for each batch we are doing the same thing, so can we vectorize this?
There was a problem hiding this comment.
Can you see if it is a for loop in the mamba 2 cached op? If not then it seems like there should be no issue here. Also please check the PyTorch backend implementation for mamba 1 prefill as a reference.
Summary
ai21labs/AI21-Jamba-Reasoning-3Bandai21labs/AI21-Jamba-Mini-1.5for AutoDeploytorch_mamba_v1_selective_scan) and cached backend (torch_cached_mamba_v1) with AttentionDescriptor — Mamba v1 uses per-element state matrix A:[D_inner, d_state], fundamentally different from Mamba v2/SSD's scalar A per headmodeling_jamba.py) usingtorch_causal_conv1d+torch_mamba_v1_selective_scancustom opsmutates_args={}to declare actual cache mutations intorch_cached_mamba_v1,torch_cached_ssm, andtorch_cached_causal_conv1dTest plan
🤖 Generated with Claude Code